WPF: MenuItem icon in the wrong place when using data template - c#

I have the following code:
<Menu>
<MenuItem ItemsSource="{Binding SomethingMenuItems}" Header="Something"/>
</Menu>
Where MenuItems is a collection of objects of type SomethingMenuItem.
I also have:
<DataTemplate DataType="{x:Type SomethingMenuItem}">
<MenuItem Header="{Binding OrderTypeName}">
<MenuItem.Icon>
<Image Source="{Binding IconName}"/>
</MenuItem.Icon>
</MenuItem>
</DataTemplate>
I'd expect to get (I get something like this when I hardcode the menu items):
What I get instead is:
What am I doing wrong?

You might want to use ItemContainerStyle instead of DataTemplate. You have to style the container of the data item than just providing a template for data item.
With your DataTemplate, you basically displaying another nested MenuItem as content for each MenuItem generated for your Menu Something, and your inner MenuItem has the image in the correct place. I am attaching VisualTree from Snoop here for your reference.
Below is the Style for the container of the data item (in this case a MenuItem):
<MenuItem ItemsSource="{Binding SomethingMenuItems}"
Header="Something">
<MenuItem.Resources>
<Image Source="{Binding IconPath}" x:Key="IconImage" x:Shared="False"/>
<Style TargetType="MenuItem" >
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Icon" Value="{StaticResource IconImage}" />
</Style>
</MenuItem.Resources>
</MenuItem>
You can see no nested MenuItems when you apply the above style, have added image here
With the above style applied, this is how the Menu looks:
Refer to this MSDN page to know more about ItemContainerStyle.

So, sthotakura's answer set me on the right track, but the code he posted didn't quite work because the style got applied to the parent menuitem as well.
In case someone else has a similar problem and stumbles on this question, here's the code that works:
<MenuItem ItemsSource="{Binding SomethingMenuItems}"
Header="Something">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" >
<Style.Resources>
<Image Source="{Binding IconPath}" x:Key="IconImage" x:Shared="False"/>
</Style.Resources>
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Icon" Value="{StaticResource IconImage}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

Related

C# WPF - How to create submenu form BindingList without overwriting MenuItem style?

I'm trying to write just some sample application using MVVM. In my view, I have two MenuItems with submenus
<Menu >
<--!first submenu-->
<MenuItem Header="{Binding AppViewModel.AppSubtitles[top_menu_navigation]}" >
<MenuItem Header="{Binding AppViewModel.AppSubtitles[top_menu_go_forward]}" Command="{Binding NavigateForward}" />
<MenuItem Header="{Binding AppViewModel.AppSubtitles[top_menu_go_back]}" Command="{Binding NavigateBack}" />
</MenuItem>
<--!second submenu-->
<MenuItem ItemsSource="{Binding AppViewModel.AllLanguagesItems}" Header="{Binding AppViewModel.AppSubtitles[top_menu_language]}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" >
<Setter Property="Header" Value="{Binding DisplayName}"/>
<Setter Property="Command" Value="{Binding ChangeLanguage}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
(items in AllLanguagesItems list have properties DisplayName and ChangeLanguage)
I also dynamically change application style by adding/removing ResourceDictionaries (with styles for buttons, menuitems etc.) to/from application MergedDictionaries.
The problem is that changing styles works well with first submenu, menuitems styles are updated correctly, but in second submenu, only partially - mainly, OnHover effect doesn't change.
Before adding dictionary
First submenu beforeSecond submenu before
After adding dictionary
First submenu afterSecond submenu after
I assume it has something to do with overwriting styles, but how would you define bindings otherwise?
I tried
<MenuItem ItemsSource="{Binding AppViewModel.AllLanguagesItems}" Header="{Binding AppViewModel.AppSubtitles[top_menu_language]}" >
<MenuItem.ItemTemplate>
<DataTemplate >
<MenuItem Header="{Binding DisplayName}" MaxWidth="200" MaxHeight="20"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
but it just creates MenuItem inside MenuItem
MenuItem inside MenuItem
EDIT:
Styles: https://github.com/Kaszub09/MVVM-WPF-DI-Sample/tree/master/WPFSampleApplication/ApplicationData/Themes
Use the BasedOn property in your style

MenuCollection Binding twice MVVM + Databinding Child menu items

As you can see in the image below, you can see 2 hover states. Here is the XAML
<Menu ItemsSource="{Binding Data.MenuCollection}">
<Menu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem Header="{Binding Header}" Command="{Binding Command}" ItemsSource="{Binding Children}"/>
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
The Collection of data works on the header. However I can't get the Children nodes to appear.
public void CreateTempMenuList()
{
MenuCollection = new ObservableCollection<MenuItem>()
{
new MenuItem()
{
Header = "File",
Children = new ObservableCollection<MenuItem>()
{
new MenuItem()
{
Header = "Exit"
}
}
}
};
}
The MenuItem class is something I created. Each property has a setter that called the OnPropertiesChanged Function. I can add the class if needed, but I am pretty sure thats not the problem.
So my question is. How do i get rid of the 'double' hover. In the image you can see 2 borders. An outer border which i hover over. the hover stays until focused on something else.
My second question is how can i get the child items to work? The itemssource on the menuitem tag could be wrong but its all i could think of.
Define an HierarchicalDataTemplate:
<Menu ItemsSource="{Binding Data.MenuCollection}">
<Menu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.Resources>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
A System.Windows.Controls.MenuItem container is implicitly created for each item so you shouldn't add another MenuItem element in the template.
Also make sure that you don't bind to an ObservableCollection<System.Windows.Controls.MenuItem> because the ItemTemplate won't be applied to built-in MenuItem elements.
To make your current code work, right click your Menu control > Edit Additional Template > Edit ItemContainerStyle > Edit Copy.
And in the generated Style,
Search for this piece of code :
<Trigger Property="Role" Value="TopLevelItem">
<Setter Property="Padding" Value="7,2,8,3"/>
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=TopLevelItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}"/>
</Trigger>
And change Padding to 0 instead of 7,2,8,3 .

How do I create a "Window" menu in XAML?

I am creating an MVVM Wpf client application. I want create menu in the main View for the application that his a menu item called "Window" on it. That menu item will dynamically update itself with a submenu of menuitems who are made up of the list of active windows running in the application. I created a ViewManager whom each View registers itself with to compile a list of active windows.
I am trying to do this in XAML but getting an error when I click on "Window"
<MenuItem Header="Window">
<ItemsControl ItemsSource="{Binding ViewMgr.Views}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd ,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</MenuItem>
How do I create a dynamically updated list of menuitems on my menu in XAML using a MVVM style of data bindings and commands?
You are adding a new ItemsControl as a single child of the menu item, instead of adding each view as one child of the menu item itself. You probably get the error because the styles TargetType doesn't match. MenuItem inherits from ItemsControl itself and exposes a property ItemsSource. Try the following:
<MenuItem ItemsSource="{Binding ViewMgr.Views}" DisplayMemberPath="Title">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

Loop xaml element

I have an array of strings. For each of these strings, I'd like to create a seperate xaml element (<menuitem> is from an external library):
<MenuItem Header="Update">
<MenuItem Header="arrayvalue1" Command="{Binding UpdateCommand}" />
<MenuItem Header="arrayvalue2" Command="{Binding UpdateCommand}" />
<MenuItem Header="arrayvalue3" Command="{Binding UpdateCommand}" />
</MenuItem>
Instead of hardcoding 3 elements, I'd like to create these from the array of strings.
Is this possible and if so, how?
MenuItem is an ItemsControl, so you can bind any collection to the ItemsSource property and it will generate the children for you. In the case of MenuItem, the children generated are also MenuItems. To apply bound values to properties on those children you can set an ItemContainerStyle which will be applied to each. Since the Command you want to use is on the top level DataContext you will need to use more of an indirect binding, which may be different depending on which technology you're using. Here's how it looks for WPF:
<MenuItem Header="Update" ItemsSource="{Binding Strings}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Path=DataContext.UpdateCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Menu}}}" />
<Setter Property="Header" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
What you're looking for is called an ItemsControl. You can use it to present a bunch of items in whatever form you like by adding an ItemTemplate to it.

WPF - MenuItem missing Icon/Image

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:

Categories