MenuItem IsChecked Property NOT Binding to VM Property - wpf

Hello I am a new WPF/MVVM programmer and having trouble with a MenuItem.
I have a menu who's ItemsSourced are binded to an object I created;
<Menu Height="23" HorizontalAlignment="Left" Name="menuProfile" VerticalAlignment="Top" Width="58" >
<MenuItem Header="Profiles" ItemsSource="{Binding Path=ProfileList}" DisplayMemberPath="ProfileName" >
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="IsChecked" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
When I run this I can see all my items in the menu but my property IsSelected isn't getting updated.
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
GetProfileConfigInfo();
}
}
If I take the style container out and put the IsCheckable and IsChecked values in line with the MenuItem....
<MenuItem Header="Profiles" ItemsSource="{Binding Path=ProfileList}" DisplayMemberPath="ProfileName" IsCheckable="True" IsChecked="{Binding IsSelected}" />
my property IsSelected gets updated but I can't see any of the items in my menu just the header Profiles.
And idea on what I'm doing wrong????

In your setter for IsSelected, you need to call OnPropertyChanged("IsSelected").
I think this could fix your problem.
I copied your exact code (below) into Blend, and created a sample data source with your exact property names, and the checkboxes in the menu worked properly.
<Menu Height="23" HorizontalAlignment="Left" Name="menuProfile" VerticalAlignment="Top" Width="58" >
<MenuItem Header="Profiles" ItemsSource="{Binding Path=ProfileList}" DisplayMemberPath="ProfileName" >
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="IsChecked" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>

Related

Bind to property of parent from an explicit enumerable in XAML

Not sure if this is possible. I have an observable collection "OptionsList" with simple objects that have a "Name" and "IsEnabled" property.
Theres a menu that looks like
Configuration
|--Option1
|--Option2
|--Option3
|--Enabled
The first sub menu "Option1,Option2,Option3" bind correctly but then from within the I try to access those items from the first sub menu and bind to their data context but i cant seem to access them via RelativeSource for some reason.
<MenuItem Header="Configuration">
<MenuItem Header="Service" ItemsSource="{Binding OptionsList}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ItemsSource">
<Setter.Value>
<x:Array Type="MenuItem">
<MenuItem Header="Enabled" IsCheckable="True"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MenuItem}}, Path=DataContext}"
IsChecked="{Binding Path=IsEnabled}"/>
</x:Array>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</MenuItem>
The IsChecked is bound with wrong Path. The implicit source of the Binding here is already a DataContext which is bound to the DataContext of the parent MenuItem. So with the Path DataContext.IsEnabled - it will actually look for DataContext.DataContext.IsEnabled - of course that cannot be resolved.
You can simply remove the DataContext.:
IsChecked="{Binding IsEnabled}"
Another problem is the DataContext will auto flow down the child MenuItem, so you don't need to set the DataContext for the inner MenuItems, which actually does not work (I've tried it and somehow the RelativeSource binding does not work - it's not some kind of disconnected visual tree - because the DataContext is flown down OK - so it is very strange in this case):
<MenuItem Header="Enabled" IsCheckable="True" IsChecked="{Binding IsEnabled}"/>
Here is a safer approach in which we use a HierarchicalDataTemplate. Note that the ItemsSource is set to a dummy array with 1 element. Inside the ItemTemplate we will use a Binding walking up to the parent element for the IsChecked property
<MenuItem Header="Service" ItemsSource="{Binding OptionsList}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="ItemTemplate">
<Setter.Value>
<HierarchicalDataTemplate>
<HierarchicalDataTemplate.ItemsSource>
<x:Array Type="{x:Type sys:Int32}">
<sys:Int32>0</sys:Int32>
</x:Array>
</HierarchicalDataTemplate.ItemsSource>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="Enabled"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="IsChecked"
Value="{Binding DataContext.IsEnabled, RelativeSource={RelativeSource AncestorType=MenuItem}}"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
I think the item display doesn't work because of some kind of reuse of the MenuItems in WPF (Not sure whether this is a bug in MenuItem). Playing around with x:Shared="False" didn't fixed it.
There is a different approach to achieve your goal:
Create a helper class that is a child of your option class and provide one instance of this helper as child of the option.
Bind in XAML to this helper class
Here is some code that shows in detail how to do:
XAML:
<MenuItem Header="Service" ItemsSource="{Binding OptionsList}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="ItemsSource" Value="{Binding ToggleItem}" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="IsCheckable" Value="True" />
<Setter Property="IsChecked" Value="{Binding IsEnabled}" />
</Style>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
C#
public class OptionHelper
{
private readonly Option owner;
public OptionHelper(Option owner)
{
this.owner = owner;
}
public bool IsEnabled
{
get { return this.owner.IsEnabled; }
set { this.owner.IsEnabled = value; }
}
}
public class Option : INotifyPropertyChanged
{
public ObservableCollection<OptionHelper> ToggleItem { get; private set; }
public Option(string name, bool isEnabled)
{
this.ToggleItem = new ObservableCollection<OptionHelper>() { new OptionHelper(this) };
this.name = name;
this.isEnabled = isEnabled;
}
// your code here...
}
I know this is not the perfect solution but it works... Wonder if someone find a solution without the helper.

WPF: Add a command to auto-generated by binding menu items

MVVM is used. I created separate menu 'Recent files' which gets its items from binding. It looks like that:
<MenuItem Header="_Recent files" ItemsSource="{Binding RecentFiles, Converter={StaticResource RecentFilesToListOfStringsConverter}, Mode=OneWay}" >
</MenuItem>
Now, I would like to add Command to each of the those auto-generated items, which should get the path as command parameter and execute import file action by click.
Could you please suggest how can it be done in MVVM way?
Again, found the solution by myself. I tried to put the command in wrong way like below, and it doesn't work:
<MenuItem Header="_Recent files" ItemsSource="{Binding RecentFiles, Converter={StaticResource RecentFilesToListOfStringsConverter}, Mode=OneWay}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding ImportRecentItemCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Here is the right approach. Still don't understand how it works, have to learn WPF deeply!
<MenuItem Header="_Recent files" ItemsSource="{Binding RecentFiles, Converter={StaticResource RecentFilesToListOfStringsConverter}, Mode=OneWay}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.ImportRecentItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type MenuItem}, AncestorLevel=1}}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
EDIT: The final version
XAML:
<MenuItem Header="_Recent files" ItemsSource="{Binding RecentFiles, Converter={StaticResource RecentFilesToListOfStringsConverter}, Mode=OneWay}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.ImportRecentItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type MenuItem}, AncestorLevel=1}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
ViewModel: MVVM Light Toolkit is used, RelayCommand goes from there:
private ICommand _importRecentItemCommand;
public ICommand ImportRecentItemCommand
{
get { return _importRecentItemCommand ?? (_importRecentItemCommand = new RelayCommand<object>(ImportRecentItemCommandExecuted)); }
}
private void ImportRecentItemCommandExecuted(object parameter)
{
MessageBox.Show(parameter.ToString());
}
Enjoy

Why can't this Image trigger binding in MenuItem.Icon find the MenuItem as a DataSource?

I've been stuck on this for a while now and I can't work out why. Basically, I have a ContextMenu with some MenuItem objects in it. I have declared Image objects for the MenuItem.Icon properties. I have Command objects bound to the MenuItems and that all works fine... in particular, when the Command.CanExecute method returns false, the MenuItem is correctly disabled and the MenuItem.Header text is greyed out.
I've been trying to set the Image.Opacity of the MenuItem Images to 0.5 when the MenuItem is disabled and this is where the problem is. For some reason, a binding in a DataTrigger in the Image.Style cannot find the MenuItem that I am trying to bind to. I have added a simplified example of my problem below.
<UserControl.Resources>
<Style x:Key="MenuItemIconStyle" TargetType="{x:Type Image}">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Style.Triggers>
<!--This Binding is not working-->
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type MenuItem}}}" Value="False">
<Setter Property="Image.Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
<!--This is all working just fine-->
<ContextMenu x:Key="ContextMenu" DataContext="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource Self}}">
<MenuItem Header="Open" Command="{Binding Open}" CommandParameter="{Binding
PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.Icon>
<Image Source="/Application;component/Images/Actions/FolderOpen_16.png"
Style="{StaticResource MenuItemIconStyle}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
...
</UserControl.Resources>
Please note that this example is simplified... there are many MenuItems in my application. I am aware that I could individually name each MenuItem and use ElementName to find them, but there must be a better way.
Any help would be greatly appreciated.
UPDATE >>>
Thanks to punker76's answer, I realised that all I needed to do was to change the Image Trigger to the following:
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
Instead of trying to bind to the MenuItem.IsEnabled property with a DataTrigger, we can bind to the Image.IsEnabled property directly... this is because the when the MenuItem becomes disabled, it also disables its children. Much simpler!
try this one
<Style x:Key="MenuItemIconStyle" TargetType="{x:Type Image}">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
<ContextMenu x:Key="ContextMenu" DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<!-- IsEnabled="False" is only for testing (tested with kaxaml) -->
<MenuItem IsEnabled="False" Header="Open" Command="{Binding Open}" CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.Icon>
<Image Source="http://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Disambig-dark.svg/25px-Disambig-dark.svg.png"
Style="{StaticResource MenuItemIconStyle}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
EDIT
here is another solution that works (the button gets the DataContext) with this tip that i found:
How to Solve Execution Problems of RoutedCommands in a WPF ContextMenu
The problem was, that the commands could not be
executed, even if the CommandBinding on the parent window allowed it.
The reason is, that ContextMenus are separate windows with their own
VisualTree and LogicalTree. The reason is that the CommandManager
searches for CommandBindings within the current focus scope. If the
current focus scope has no command binding, it transfers the focus
scope to the parent focus scope. The simplest solution is to initially
set the logical focus of the parent window that is not null. When the
CommandManager searches for the parent focus scope it finds the window
and handels the CommandBinding correctly. Another solution is to
manually bind the CommandTarget to the parent ContextMenu.
<Window.Resources>
<Style x:Key="MenuItemIconStyle"
TargetType="{x:Type Image}">
<Setter Property="Width"
Value="16" />
<Setter Property="Height"
Value="16" />
<Style.Triggers>
<Trigger Property="IsEnabled"
Value="False">
<Setter Property="Opacity"
Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button Content="With ContextMenu"
DataContext="{Binding ElementName=window, Path=DataContext}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Enabled"
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}"
Command="{Binding Open}"
CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.Icon>
<Image Source="http://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Disambig-dark.svg/25px-Disambig-dark.svg.png"
Style="{StaticResource MenuItemIconStyle}" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Disabled"
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}"
Command="{Binding NotOpen}"
CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.Icon>
<Image Source="http://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Disambig-dark.svg/25px-Disambig-dark.svg.png"
Style="{StaticResource MenuItemIconStyle}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</Grid>
code behind
public partial class Window11 : Window
{
public static readonly DependencyProperty OpenProperty =
DependencyProperty.Register("Open", typeof(ICommand), typeof(Window11), new PropertyMetadata(default(ICommand)));
public static readonly DependencyProperty NotOpenProperty =
DependencyProperty.Register("NotOpen", typeof(ICommand), typeof(Window11), new PropertyMetadata(default(ICommand)));
public ICommand NotOpen {
get { return (ICommand)this.GetValue(NotOpenProperty); }
set { this.SetValue(NotOpenProperty, value); }
}
public ICommand Open {
get { return (ICommand)this.GetValue(OpenProperty); }
set { this.SetValue(OpenProperty, value); }
}
public Window11() {
this.DataContext = this;
this.InitializeComponent();
this.Open = new RoutedCommand("Open", typeof(Window11));
this.CommandBindings.Add(new CommandBinding(this.Open, null, (sender, args) =>
{
args.CanExecute = true;
}));
this.NotOpen = new RoutedCommand("NotOpen", typeof(Window11));
this.CommandBindings.Add(new CommandBinding(this.NotOpen, null, (sender, args) =>
{
args.CanExecute = false;
}));
}
}
hope this works

Disable the whole Context Menu

I know there is a way to block or now show context menu by using ContextMenuOpening event.
But I still want to show the context menu, just disable everything in it, is there a way to do it?
How can I disable all the menu item at a same time?
<DataTemplate x:Key="ItemDataTemplate">
<Grid Background="Transparent">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="New" Click="New_Click" />
<Separator />
<MenuItem Header="Duplicate" Click="Duplicate_Click"/>
<MenuItem Header="Delete" Click="Delete_Click" />
<MenuItem Header="Rename" Click="Rename_Click" />
<Separator />
<MenuItem Header="Export..." Click="Export_Click" />
<MenuItem Header="Print..."
Command="ApplicationCommands.Print"
InputGestureText="" />
<Separator />
<MenuItem Header="Properties" Click="Properties_Click" />
</ContextMenu>
</Grid.ContextMenu>
<StackPanel Orientation="Horizontal"
Margin="0,0,10,0"
HorizontalAlignment="Stretch"
Background="Transparent"
IsHitTestVisible="False">
</StackPanel>
</Grid>
</DataTemplate>
<Style x:Key="z3r0_Style_TextBox_Default" BasedOn="{x:Null}"
TargetType="{x:Type TextBox}">
<Setter Property="FontSize" Value="11"/>
<Setter Property="Background" Value="{StaticResource z3r0_SolidColorBrush_DarkerGray}"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="{StaticResource z3r0_SolidColorBrush_Green}"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="MinWidth" Value="10"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu IsEnabled="False" Visibility="Hidden">
</ContextMenu>
</Setter.Value>
</Setter>
For me, setting IsEnabled to False still caused a small empty context menu to be visible. After changing its visibility attribute, it was truly disabled.
IsEnabled = false on the ContextMenu?
Edit: As there appear to be closing problems when doing this i would suggest a container style:
<ContextMenu>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsEnabled" Value="False"/>
</Style>
</ContextMenu.ItemContainerStyle>
<MenuItem Header="Test"/>
</ContextMenu>
Of course the actual value can be bound as well but in the container style the context is the individual item, so to bind to the parent context a RelativeSource binding is necessary (unless the data object also has a connection to the parent).
The entire context menu can be disabled at the root of the ContextMenu by setting the IsEnabled property to false; there is no need to loop through each menu item in code.
<ContextMenu IsEnabled="False">
Or
<ContextMenu IsEnabled={Binding Path=SomeModelBooleanProperty}">
This will enable the context menu to be viewed, but all items will be disabled.
I used ContextMenu="{x:Null}" to disable the Context Menu in a TextBox.
Here is how I solve my question.
Still use ContextMenuOpening event. Use a loop.
This why can help me disable certain context menu item in code, but not in XAML.
private void SetContextMenuItemsEnableStatus(object sender)
{
FrameworkElement element = sender as FrameworkElement;
if (element == null)
{
return;
}
ContextMenu contextMenu = element.ContextMenu;
if (contextMenu == null)
return;
//Use this way.
//You can disable every item or certain item in context menu.
foreach (Control item in contextMenu.Items)
{
item.IsEnabled = false;
}
}
Here is how I solve my question.
<ContextMenu x:Key="TreeViewEmptyContextMenu" Width="0" Height="0"/>

How can I bind an ObservableCollection of ViewModels to a MenuItem?

When I bind Menu Items with an ObservableCollection, only the "inner" area of the MenuItem is clickable:
alt text http://tanguay.info/web/external/mvvmMenuItems.png
In my View I have this menu:
<Menu>
<MenuItem
Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
</Menu>
Then I bind it with this DataTemplate:
<DataTemplate x:Key="MainMenuTemplate">
<MenuItem
Header="{Binding Title}"
Command="{Binding DataContext.SwitchPageCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"
Background="Red"
CommandParameter="{Binding IdCode}"/>
</DataTemplate>
Since each ViewModel in the ObservableCollection ManageMenuPageItemViewModels has a property Title and IdCode, the above code works fine at first sight.
HOWEVER, the problem is that the MenuItem in the DataTemplate is actually inside another MenuItem (as if it is being bound twice) so that in the above DataTemplate with Background="Red" there is a Red box inside each menu item and only this area can be clicked, not the whole menu item area itself (e.g. if the user clicks on the area where the checkmark is or to the right or left of the inner clickable area, then nothing happens, which, if you don't have a separate color is very confusing.)
What is the correct way to bind MenuItems to an ObservableCollection of ViewModels so that the whole area inside each MenuItem is clickable?
UPDATE:
So I made the following changes based on advice below and now have this:
alt text http://tanguay.info/web/external/mvvmMenuItemsYellow.png
I have only a TextBlock inside my DataTemplate, but I still can't "color the whole MenuItem" but just the TextBlock:
<DataTemplate x:Key="MainMenuTemplate">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
And I put the Command binding into Menu.ItemContainerStyle but they don't fire now:
<Menu DockPanel.Dock="Top">
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Command" Value="{Binding DataContext.SwitchPageCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/>
<Setter Property="CommandParameter" Value="{Binding IdCode}"/>
</Style>
</Menu.ItemContainerStyle>
<MenuItem
Header="MVVM" ItemsSource="{Binding MvvmMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
<MenuItem
Header="Application" ItemsSource="{Binding ApplicationMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
<MenuItem
Header="Manage" ItemsSource="{Binding ManageMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
</Menu>
I found using MVVM with MenuItems to be very challenging. The rest of my application uses DataTemplates to pair the View with the ViewModel, but that just doesn't seem to work with Menus because of exactly the reasons you've described. Here's how I eventually solved it. My View looks like this:
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=(local:MainViewModel.MainMenu)}">
<Menu.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding Path=(contracts:IMenuItem.Header)}"/>
<Setter Property="MenuItem.ItemsSource" Value="{Binding Path=(contracts:IMenuItem.Items)}"/>
<Setter Property="MenuItem.Icon" Value="{Binding Path=(contracts:IMenuItem.Icon)}"/>
<Setter Property="MenuItem.IsCheckable" Value="{Binding Path=(contracts:IMenuItem.IsCheckable)}"/>
<Setter Property="MenuItem.IsChecked" Value="{Binding Path=(contracts:IMenuItem.IsChecked)}"/>
<Setter Property="MenuItem.Command" Value="{Binding}"/>
<Setter Property="MenuItem.Visibility" Value="{Binding Path=(contracts:IMenuItem.Visible),
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Setter Property="MenuItem.ToolTip" Value="{Binding Path=(contracts:IMenuItem.ToolTip)}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(contracts:IMenuItem.IsSeparator)}" Value="true">
<Setter Property="MenuItem.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Separator Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>
</Menu>
</DockPanel>
If you notice, I defined an interface called IMenuItem, which is the ViewModel for a MenuItem. Here's the code for that:
public interface IMenuItem : ICommand
{
string Header { get; }
IEnumerable<IMenuItem> Items { get; }
object Icon { get; }
bool IsCheckable { get; }
bool IsChecked { get; set; }
bool Visible { get; }
bool IsSeparator { get; }
string ToolTip { get; }
}
Notice that the IMenuItem defines IEnumerable Items, which is how you get sub-menus. Also, the IsSeparator is a way to define separators in the menu (another tough little trick). You can see in the xaml how it uses a DataTrigger to change the style to the existing separator style if IsSeparator is true. Here's how MainViewModel defines the MainMenu property (that the view binds to):
public IEnumerable<IMenuItem> MainMenu { get; set; }
This seems to work well. I assume you could use an ObservableCollection for the MainMenu. I'm actually using MEF to compose the menu out of parts, but after that the items themselves are static (even though the properties of each menu item are not). I also use an AbstractMenuItem class that implements IMenuItem and is a helper class to instantiate menu items in the various parts.
UPDATE:
Regarding your color problem, does this thread help?
Don't put the MenuItem in the DataTemplate. The DataTemplate defines the content of the MenuItem. Instead, specify extraneous properties for the MenuItem via the ItemContainerStyle:
<Menu>
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Title}"/>
...
</Style>
</Menu.ItemContainerStyle>
<MenuItem
Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}"
ItemTemplate="{StaticResource MainMenuTemplate}"/>
</Menu>
Also, take a look at HierarchicalDataTemplates.
Here is how I have done my menus. It may not be precisely what you need, but I think it is pretty close.
<Style x:Key="SubmenuItemStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding MenuName}"></Setter>
<Setter Property="Command" Value="{Binding Path=MenuCommand}"/>
<Setter Property="ItemsSource" Value="{Binding SubmenuItems}"></Setter>
</Style>
<DataTemplate DataType="{x:Type systemVM:TopMenuViewModel}" >
<Menu>
<MenuItem Header="{Binding MenuName}"
ItemsSource="{Binding SubmenuItems}"
ItemContainerStyle="{DynamicResource SubmenuItemStyle}" />
</Menu>
</DataTemplate>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Menus}" />
TopMenuViewModel is a collection of the menus that will appear on the menu bar. They each contain the MenuName that will be displayed and a collection called SubMenuItems that I set to be the ItemsSource.
I control the way the SubMenuItems are displayed by way of the style SumMenuItemStyle. Each SubMenuItem has its own MenuName property, Command property of type ICommand, and possibly another collection of SubMenuItems.
The result is that I am able to store all my menu information in a database and dynamically switch what menus are displayed at runtime. The entire menuitem area is clickable and displays correctly.
Hope this helps.
Just make your DataTemplate to be a TextBlock (or maybe a stack panel with an icon and a TextBlock).

Resources