I have a custom Expander control called SpecialExpander. It is basically just a standard Expander with a fancy header and a couple properties (HeaderText and IsMarkedRead).
I began by creating a simple class:
public class SpecialExpander : Expander
{
public string HeaderText { get; set; }
public bool IsMarkedRead { get; set; }
}
Then I created a style that sets a couple properties on the expander (e.g., margins, padding, etc.) and, importantly, it also defines a custom DataTemplate for the HeaderTemplate property. The template is basically a grid with two rows.
As shown in the illustrations below...
for the top row, I'd like a fixed layout (it's always TextBlock TextBlock CheckBox)
for the bottom row, however, I want to be able to provide custom XAML for each expander.
I tried putting <ContentControl Grid.Row="1" ... /> in the DataTemplate, but I couldn't figure out how to hook it up properly.
alt text http://img85.imageshack.us/img85/1194/contentcontrolwithintem.jpg
Question
How can I build a DataTemplate for my SpecialExpander so that the header has some fixed content (top row) and a place-holder for custom content (bottom row)?
For the second illustration, I would want to be able to do something like this:
<SpecialExpander HeaderText="<Expander Header Text>" IsMarkedRead="True">
<SpecialExpander.Header>
<StackPanel Orientation="Horizontal">
<RadioButton Content="High" />
<RadioButton Content="Med" />
<RadioButton Content="Low" />
</StackPanel>
<SpecialExpander.Header>
<Grid>
<Label>Main Content Goes Here</Label>
</Grid>
</SpecialExpander>
It hit me this morning how to solve this: instead of building a SpecialExpander, I just need a normal Expander. Then, for the header, I will use a custom ContentControl called SpecialExpanderHeader.
Here's how it works...
SpecialExpanderHeader class:
public class SpecialExpanderHeader : ContentControl
{
public string HeaderText { get; set; }
public bool IsMarkedRead { get; set; }
}
SpecialExpanderHeader style:
<Style TargetType="custom:SpecialExpanderHeader">
<Setter Property="Padding" Value="10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="custom:SpecialExpanderHeader">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=custom:SpecialExpanderHeader}, Path=HeaderText}" />
<CheckBox Margin="100,0,0,0" IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=custom:SpecialExpanderHeader}, Path=IsMarkedRead}" />
</StackPanel>
<Separator Grid.Row="1" />
<ContentPresenter Grid.Row="2" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Expander style
<Style x:Key="Local_ExpanderStyle" TargetType="Expander" BasedOn="{StaticResource {x:Type Expander}}">
<Setter Property="Margin" Value="0,0,0,10" />
<Setter Property="Padding" Value="10" />
<Setter Property="FontSize" Value="12" />
</Style>
Usage
<Expander Style="{StaticResource Local_ExpanderStyle}">
<Expander.Header>
<custom:SpecialExpanderHeader IsMarkedRead="True" HeaderText="Test">
<StackPanel Orientation="Horizontal">
<RadioButton Content="High" />
<RadioButton Content="Medium" />
<RadioButton Content="Low" />
</StackPanel>
</custom:SpecialExpanderHeader>
</Expander.Header>
<Grid>
<!-- main expander content goes here -->
</Grid>
</Expander>
Related
I am working on a project in C# WPF. I have a tab container and I want to dynamically load different types of tabs into the tab container as the user requires. As an example I am doing something like the following:
tabContainer.Items.Add(new MyUserControl());
I want each tab to have a close button so the tab can be removed the container when the user no longer requires it.
I found this code project example but from what I can see you are a loading a user control which contains the xaml for the tab itself, not the tab content or am I missing something.
How can I load in my User Control into the tab container, but also have the tab closable.
Currently the tab that I am loading in uses some static binding to set the tab title using the following:
<TabControl x:Name="tabContainer" Grid.Column="2" Margin="10,45,0,0" RenderTransformOrigin="0.5,0.55" Grid.ColumnSpan="3">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
</TabControl>
My user control then has a `public string TabHeader{get;set;} which gets set in the constructor depending on what constructor of my user control is used.
You will have to define the close Button yourself. You could for example do this in the HeaderTemplate of the TabItem:
<TabControl x:Name="tabContainer">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button Content="x" Click="Button_Click_2"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TabItem}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The Tag property is bound to the UserControl in the Items collection which you can remove in the click event handler of the Button, like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
tabContainer.Items.Add(new MyUserControl());
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
tabContainer.Items.Remove(button.Tag);
}
}
If you want to add a close button to each tab, that would be in the TabItem style ControlTemplate. Normally you'd specify the data context (i.e. the data only that's driving the content) in Content and then specify the look in ContentTemplate. If your Content is a UserControl then you don't specify the ContentTemplate since a UserControl knows how to draw itself.
For my sins, I've added close-tab buttons to the WPF TabControl. I ended up putting the close button in the ItemTemplate. Here's a minimal version that works with the way you're populating the TabControl and the header content:
<TabControl
>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Grid.Column="0"
/>
<Button
VerticalAlignment="Center"
Grid.Column="1">
<Path
Data="M 0, 0 L 12, 12 M 12,0 L 0,12"
Stroke="Red"
StrokeThickness="2"
Width="12"
Height="12"
/>
</Button>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<local:UserControl1 TabHeader="First Item" />
<local:UserControl1 TabHeader="Second Item" />
</TabControl>
I have an issue with ListBoxItems. I am trying to make all controls in the ListBoxItem select it as well, so clicking on a TextBox, Label, etc will select the ListBoxItem. Pretty simple so far.
I am also changing the ListBoxItem Template to change the selection visualization from highlighting the background to just drawing a border. Also pretty simple.
The combination of these two, however, seems to cause some really irritating issues with MouseDown and PreviewMouseDown, specifically in my case regarding Labels in a Grid, where one creates a "void" occupied by Grid space.
Using snoop, I can see the PreviewMouseDown event stopping at the ScrollViewer inside the ListBox, and not going all the way to the ListBoxItem.
XAML:
<Window x:Class="ListBoxClickThroughTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<ListBox ItemsSource="{Binding Items}"
SelectionMode="Single">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Name="VerySuperLongLabel"
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Left"
Content="VerySuperLongLabel"
Padding="0" />
<TextBox Name="Textbox1"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox1 Text" />
<Label Name="ShortLabel"
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
Content="ShortLabel"
Padding="0" />
<TextBox Name="Textbox2"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox2 Text" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<EventSetter Event="MouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd"
BorderThickness="1">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Bd" Property="BorderBrush" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</Window>
Code-behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ListBoxClickThroughTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Items = new List<string>() { "1", "2" };
InitializeComponent();
DataContext = this;
}
public List<string> Items { get; set; }
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var listBoxItem = (ListBoxItem)sender;
listBoxItem.IsSelected = true;
}
}
}
However, if I remove the Template setter, all is well. Is there some magic in the template I'm missing? I tried renaming the border to "Bd" as that was what the default template border was named, but no luck. Any ideas?
If you change the horizontal alignment of the labels from "Left" to "Stretch" this will fix the problem and keep the visual formatting the same.
Mousedown events only work in areas where elements exist. By having the labels at a "left" horizontal alignment, you are creating the "void" you mentioned, where no element exists at that level that can be clicked. To visually see the difference, try temporarily setting the background property of the label elements that are giving you problems, and you'll see the element doesn't extend all the way to the textbox.
Is it possible to prevent an anchorable being docked into my documents pane? I want them to be draggable and moved around the screen, but sometimes users drag them into the documents pane which makes them look poor. Then they close the tab and I can't re-open the anchorable.
If it helps my Avalon code is below:
<avalonDock:DockingManager.Theme>
<avalonDock:VS2010Theme />
</avalonDock:DockingManager.Theme>
<avalonDock:DockingManager.DocumentHeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<!-- the TextBlock named Limiter is used to limit the height of the TextBlock for the workflow name. -->
<TextBlock x:Name="Limiter" TextWrapping="NoWrap" Visibility="Hidden"
TextTrimming="CharacterEllipsis">
L
</TextBlock>
<TextBlock Text="{Binding Path=Title}" VerticalAlignment="Center"
ToolTip="{StaticResource WorkflowTabItemToolTip}"
MaxHeight="{Binding ActualHeight, ElementName=Limiter}" MaxWidth="150"
TextWrapping="NoWrap" TextTrimming="CharacterEllipsis" Margin="0,0,2,0"
AutomationProperties.AutomationId="WorkflowTabTitleText"/>
<TextBlock Text=" *"
ToolTip="Has unsaved changes"
Visibility="{Binding Content.UnsavedEdits, Converter={StaticResource BoolToVis}}"
AutomationProperties.AutomationId="DirtyTabIndicator"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</avalonDock:DockingManager.DocumentHeaderTemplate>
<avalonDock:DockingManager.LayoutItemContainerStyleSelector>
<utilities1:PanesStyleSelector>
<utilities1:PanesStyleSelector.WebUIStyle>
<Style TargetType="{x:Type avalonDock:LayoutAnchorableItem}">
<Setter Property="Title" Value="{Binding Model.Title}"/>
<Setter Property="IconSource" Value="{Binding Model.IconSource}"/>
<Setter Property="Visibility" Value="{Binding Model.IsVisible, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter={x:Static Visibility.Hidden}}"/>
<Setter Property="ContentId" Value="{Binding Model.ContentId}"/>
<Setter Property="IsSelected" Value="{Binding Model.IsSelected, Mode=TwoWay}"/>
<Setter Property="IsActive" Value="{Binding Model.IsActive, Mode=TwoWay}"/>
</Style>
</utilities1:PanesStyleSelector.WebUIStyle>
<utilities1:PanesStyleSelector.DocumentStyle>
<Style TargetType="{x:Type avalonDock:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.WorkflowName}" />
<Setter Property="IsActive" Value="{Binding Model.IsActive}" />
<Setter Property="IsSelected" Value="{Binding Model.IsActive}" />
</Style>
</utilities1:PanesStyleSelector.DocumentStyle>
</utilities1:PanesStyleSelector>
</avalonDock:DockingManager.LayoutItemContainerStyleSelector>
<avalonDock:DockingManager.LayoutItemTemplateSelector>
<utilities1:PanesTemplateSelector>
<utilities1:PanesTemplateSelector.WorkflowDesignerViewTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" IsTabStop="False" />
</DataTemplate>
</utilities1:PanesTemplateSelector.WorkflowDesignerViewTemplate>
<utilities1:PanesTemplateSelector.WebUIViewTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" IsTabStop="False" />
</DataTemplate>
</utilities1:PanesTemplateSelector.WebUIViewTemplate>
</utilities1:PanesTemplateSelector>
</avalonDock:DockingManager.LayoutItemTemplateSelector>
<avalonDock:LayoutRoot>
<avalonDock:LayoutPanel Orientation="Horizontal">
<avalonDock:LayoutDocumentPaneGroup>
<avalonDock:LayoutDocumentPane AutomationProperties.AutomationId="AvalonDocumentPane"/>
</avalonDock:LayoutDocumentPaneGroup>
<avalonDock:LayoutAnchorablePane DockWidth="800" DockMinWidth="400" AutomationProperties.AutomationId="WebUIPane"/>
<avalonDock:LayoutAnchorablePane DockWidth="225" DockMinWidth="225" AutomationProperties.AutomationId="ActivitiesPane">
<avalonDock:LayoutAnchorable Title="Activities" AutoHideWidth="225" AutoHideMinWidth="225" CanClose="False" CanHide="False">
<toolbox:ToolboxControl Name="Toolbox" AutomationProperties.AutomationId="ActivitiesToolbox"
utilities1:ToolboxItemSource.ToolboxItems="{Binding ToolboxList}" />
</avalonDock:LayoutAnchorable>
</avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutPanel>
</avalonDock:LayoutRoot>
</avalonDock:DockingManager>
Although I didn't find a direct way of preventing the docking, I was able to get the basic problem fixed, namely customizing different tab headers for tool windows and document windows. My document windows show asterisk (*) in the tab header to indicate changes (just like VS), whereas the tool windows should not do so.
The solution was to use DocumentHeaderTemplateSelector and provide it with two different templates, one each for documents and tool windows. Here's the XAML:
<xcad:DockingManager.DocumentHeaderTemplateSelector>
<bd:DocumentHeaderTemplateSelector>
<bd:DocumentHeaderTemplateSelector.DocumentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="Resources\AppIcon.ico" Margin="0,0,4,0" Width="16" />
<TextBlock Text="{Binding Title}" />
<TextBlock Text=" *" Visibility="{Binding Content.IsDirty, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
</DataTemplate>
</bd:DocumentHeaderTemplateSelector.DocumentTemplate>
<bd:DocumentHeaderTemplateSelector.ToolWindowTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</bd:DocumentHeaderTemplateSelector.ToolWindowTemplate>
</bd:DocumentHeaderTemplateSelector>
</xcad:DockingManager.DocumentHeaderTemplateSelector>
The selector class is simply:
Public Class DocumentHeaderTemplateSelector
Inherits DataTemplateSelector
Public Property DocumentTemplate As DataTemplate
Public Property ToolWindowTemplate As DataTemplate
Public Overrides Function SelectTemplate(item As Object, container As System.Windows.DependencyObject) As System.Windows.DataTemplate
Dim itemAsLayoutContent = TryCast(item, Xceed.Wpf.AvalonDock.Layout.LayoutContent)
If TypeOf item Is Xceed.Wpf.AvalonDock.Layout.LayoutDocument AndAlso TypeOf DirectCast(item, Xceed.Wpf.AvalonDock.Layout.LayoutDocument).Content Is DocumentVM Then
Return DocumentTemplate
Else
Return ToolWindowTemplate
End If
End Function
End Class
Now my tool windows do not show asterisk (*) and icon even if they are moved into the documents pane.
Hope this helps someone down the road.
<avalonDock:LayoutRoot>
<avalonDock:LayoutPanel CanRepositionItems="False" Orientation="Vertical">
<avalonDock:LayoutAnchorablePane Name="ToolsPane" DockHeight="100" CanRepositionItems="False">
<avalonDock:LayoutAnchorable CanDockAsTabbedDocument="False"/>
</avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutPanel>
</avalonDock:LayoutRoot>
In the avalonDock:LayoutAnchorable set
CanDockAsTabbedDocument
property to False.
This will disable the anchorable view to dock in the document pane.
In the WPF XAML code below, if I am in the SelectTaskItemClick event for the templated Button, how do I get the ListBoxItem ItemSource object that is currently selected?
<!-- ListBox ITEMS -->
<TaskDash:ListBoxWithAddRemove x:Name="listBoxItems" Grid.Row="1" Grid.Column="3" Grid.RowSpan="3"
ItemsSource="{Binding}">
<!--ItemsSource="{Binding}" DisplayMemberPath="Description">-->
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=Selected}"/>
</Style>
<TaskDash:ListBoxWithAddRemove.ItemTemplate>
<DataTemplate>
<DockPanel>
<Button DockPanel.Dock="Left" Click="SelectTaskItemClick">SELECT</Button>
<TextBox DockPanel.Dock="Left" Name="EditableDescription" Text="{Binding Description}" Height="25" Width="100" />
<Button DockPanel.Dock="Left" Click="EditTaskItemClick">EDIT</Button>
</DockPanel>
</DataTemplate>
</TaskDash:ListBoxWithAddRemove.ItemTemplate>
</TaskDash:ListBoxWithAddRemove>
If I try to get the Parent or TemplateParent, it gives me the ContentPresenter or Style or something similar.
private void SelectTaskItemClick(object sender, RoutedEventArgs e)
{
Button taskItemButton = (Button) e.OriginalSource;
ContentPresenter taskItem = (ContentPresenter) taskItemButton.TemplatedParent;
taskItem = (ContentPresenter)taskItemButton.TemplatedParent;
Style taskItem2 = taskItem.TemplatedParent;
taskItem2 = taskItem.TemplatedParent;
DependencyObject taskItem3 = taskItem2.Parent;
//DependencyObject taskItem3 = taskItem2.TemplatedParent;
//TaskItem taskItemObj = taskItem2;
}
In the code above, I'm guessing it is grabbing that from App.XAML where that custom ListBoxWithAddRemove control is defined. How do I traverse the actual form's XAML instead [the first code shown above]?
<Style x:Key="{x:Type TaskDash:ListBoxWithAddRemove}" TargetType="{x:Type TaskDash:ListBoxWithAddRemove}">
<Setter Property="Margin" Value="3" />
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="MinWidth" Value="120"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TaskDash:ListBoxWithAddRemove}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Grid.Row="0"
Click="DeleteControlClick">Delete</Button>
<Button Grid.Column="1" Grid.Row="0"
Click="AddControlClick">Add</Button>
<Border
Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"
Name="Border"
Background="{StaticResource WindowBackgroundBrush}"
BorderBrush="{StaticResource SolidBorderBrush}"
BorderThickness="1"
CornerRadius="2">
<ScrollViewer
Margin="0"
Focusable="false">
<StackPanel Margin="0" IsItemsHost="True" />
</ScrollViewer>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can use the VisualTreeHelper to walk up the tree and stop if you have an object of the right type, e.g.
private void SelectTaskItemClick(object sender, RoutedEventArgs e)
{
var b = sender as Button;
DependencyObject item = b;
while (item is ListBoxItem == false)
{
item = VisualTreeHelper.GetParent(item);
}
var lbi = (ListBoxItem)item;
//...
}
(If you just want to select the item that can (and should) just be done via the established binding, e.g.)
private void SelectTaskItemClick(object sender, RoutedEventArgs e)
{
// The DataContext should be an item of your class that should
// have a Selected property as you bind to it in a style.
var data = (sender as FrameworkElement).DataContext as MyClass;
data.Selected = true;
}
Assuming that
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=Selected}"/>
</Style>
works the way that it appears you intended, you should be able to loop through the items in your DataContext used as the ItemsSource for your listbox and check the Selected property of each to find the one currently selected. The more typical way of determining the selected item from a ListBox is by using listBox.SelectedItem where listBox is a variable that refers to the ListBox in question. Alternatively you might be able to access it off the sender parameter to the SelectTaskItemClick method. Another method you might try is helper methods to traverse the visual tree such as described at The Coding Bloke, and The Code Project - LINQ to Visual Tree.
Im getting menuItem icon appearing only on last menuItem.
If i snoop the app only last menuItem has image in icon, while if i debug all MenuItems appear to have image in icon. Also if i add submenuItem the icon on menuItem dissapears once i open submenus and the last submenu gets the icon... Any idea? PS: also tooltips on menu item dont work.
Im using caliburn micro and fluent ribbon controls.
<ControlTemplate x:Key="dropDownButton">
<ef:DropDownButton Header="{Binding DisplayName}"
ItemsSource="{Binding Items}"
LargeIcon="{Binding LargeIconPath}"
cm:Message.Attach="ClickAction()"
ef:KeyTip.Keys="{Binding KeyTip}">
<ef:DropDownButton.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header"
Value="{Binding DisplayName}"/>
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding Path=IconPath}"/>
</Setter.Value>
</Setter>
<Setter Property="ItemsSource"
Value="{Binding Items}"/>
<Setter Property="cm:Message.Attach"
Value="ClickAction()"/>
<Setter Property="ef:KeyTip.Keys"
Value="{Binding KeyTip}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</Setter.Value>
</Setter>
</Style>
</ef:DropDownButton.ItemContainerStyle>
<ef:DropDownButton.ToolTip>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</ef:DropDownButton.ToolTip>
</ef:DropDownButton>
You are setting Icon property to an Image control in Style. Now, only one copy of Style is created and thus, only one copy of Image is created. Now, any control can have only one parent at a time. So, when it is assigned to last MenuItem, it is removed from previous MenuItem controls. To fix this, use Templates.
Instead of setting Header property, set HeaderTemplate:
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding Path=IconPath}" />
<TextBlock Grid.Column="1"
Text="{Binding DisplayName}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
I'm not sure of what properties are exposed by the control toolkit you are using. But, I'm sure they must have a template property.
After doing this, you don't need to set Icon property in style.
I successfully use the following entries in a ResourceDictionary:
<!-- Define non-shared image to avoid loss of menu icons -->
<Image x:Key="MenuIconImage" Height="16" Width="16" x:Shared="false">
<Image.Source>
<DrawingImage Drawing="{Binding Icon}" />
</Image.Source>
</Image>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Header" Value="{Binding DisplayName />
<Setter Property="Icon" Value="{StaticResource MenuIconImage}" />
</Style>
Works like this:
<DataTemplate x:Key="MenuItemHeaderTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Path=IconPath}" />
<Label Grid.Column="1" Content="{Binding DisplayName}" />
</Grid>
</DataTemplate>
<ControlTemplate x:Key="dropDownButton">
<ef:DropDownButton Header="{Binding DisplayName}"
ItemsSource="{Binding Items}"
LargeIcon="{Binding LargeIconPath}"
cm:Message.Attach="ClickAction()"
ef:KeyTip.Keys="{Binding KeyTip}">
<ef:DropDownButton.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="HeaderTemplate" Value="{StaticResource MenuItemHeaderTemplate}" />
<Setter Property="ItemsSource"
Value="{Binding Items}"/>
<Setter Property="cm:Message.Attach"
Value="ClickAction()"/>
<Setter Property="ef:KeyTip.Keys"
Value="{Binding KeyTip}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</Setter.Value>
</Setter>
</Style>
</ef:DropDownButton.ItemContainerStyle>
<ef:DropDownButton.ToolTip>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</ef:DropDownButton.ToolTip>
</ef:DropDownButton>
For some reason approach when Image is static resource with x:Shared = false doesn't work for me. Only last menu item shows icon. I've tried both StaticResource and DynamicResource. Here is my solution:
public class MenuItemIconHelper
{
#region ImageSource Icon
public static readonly DependencyProperty IconProperty = DependencyProperty.RegisterAttached("Icon", typeof(ImageSource), typeof(MenuItemIconHelper), new PropertyMetadata(default(ImageSource), IconPropertyChangedCallback));
private static void IconPropertyChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var i = (MenuItem)obj;
if (e.NewValue != null)
i.Icon = new Image() {Source = (ImageSource)e.NewValue};
else
i.Icon = null;
}
public static void SetIcon(DependencyObject element, ImageSource value)
{
element.SetValue(IconProperty, value);
}
public static ImageSource GetIcon(DependencyObject element)
{
return (ImageSource)element.GetValue(IconProperty);
}
#endregion
}
Sample:
<Style x:Key="CommandMenuItemStyle" TargetType="MenuItem">
<Setter Property="cb:MenuItemIconHelper.Icon" Value="car1.png" />
<Setter Property="Header" Value="{Binding Name}" />
I consider it to be more readable than using resource and you don't need to change MenuItem's HeaderTemplate. You can also implement some caching mechanism for ImageSource or Image.
1. Add Existing File... image file to resources (if you already have one, skip it).
2. In Solution Explorer select this image file.
3. Change Build Action to Resource.
And finally, you can add this image to XAML with simple call:
<Window.Resources>
<ContextMenu x:Key="contextMenu" >
<MenuItem Header="Restart" Name="menuItemRestart" Click="MenuItem_Click">
<MenuItem.Icon>
<Image Source="/Resources/restart.png"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem Header="Exit" Name="menuItemExit" Click="MenuItem_Click">
<MenuItem.Icon>
<Image Source="/Resources/window_close.png"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Window.Resources>
The Result: