Bind to property of parent from an explicit enumerable in XAML - wpf

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.

Related

WPF Custom Control library : trying to bind to templatedparent property from a keyed resource, ancestor way fails

How can I make this custom control library binding work? The code is heavily simplified for the sake of brevity, the Listview chooses its current View among several Views thank to a Datatrigger in its style, I'm only showing one for the sake of brevity. The failing binding is the GridViewColumn.Width one
Themes/Generic.Xaml :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<DataTemplate x:Key ="myDataTemplate">
<!--This visibility binding works-->
<CheckBox Width="16" Height="16" x:Name="ItemCheckbox"
Visibility="{Binding CheckBoxVisibility,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:CustomControl1}}}" />
</DataTemplate>
<GridView x:Name="myView" x:Key="myView" x:Shared="False">
<!--this width binding fails : Cannot find source for binding-->
<GridViewColumn x:Name="NameColumn" Header="Name"
CellTemplate="{StaticResource myDataTemplate}"
Width="{Binding NameColumnWidth,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:CustomControl1}}}" />
</GridView>
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<ListView ItemsSource="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ItemsSource}" >
<ListView.Style>
<Style TargetType="{x:Type ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ViewDetails}" Value="True">
<Setter Property="View"
Value="{StaticResource myView}" />
</DataTrigger>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ViewDetails}" Value="False ">
<Setter Property="View"
Value="{StaticResource myView2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
CustomControl1.cs
public class CustomControl1 : Control
{
//these properties should be DependencyProperties, simplified here for the sake of brevity
ObservableCollection<Item> items;
public object ItemsSource { get => items; }
//this is the property I fail to bind to
public double NameColumnWidth { get => this.Width; }
//this one works
public Visibility CheckBoxVisibility { get => Visibility.Visible; }
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1),
new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
Edit:
I actually ended up changing my approach and instead of binding the column width to a Property in the templated parent I made it auto resizing to its content by subclassing GridView. This is an overall better solution. If anyone finds a solution to this binding puzzle I'll still be happy to test and accept it.
public class AutoAdjustingGridView: GridView
{
protected override void PrepareItem(ListViewItem item)
{
base.PrepareItem(item);
foreach(var column in Columns)
{
if (double.IsNaN(column.Width))
column.Width = column.ActualWidth;
column.Width = double.NaN;
}
}
}

How to bind DataGridTemplateColumn.Visibility to a property outside of DataGrid.ItemsSource?

I need to bind the Visibility of a DataGridTemplateColumn to a property outside of the DataGrid.ItemsSource,because i need to bind this column in the all the rows to one property inside the ViewModel,but as far as i know you just can bind that to something inside the ItemsSource or you should use ElementStyle and EditingElementStyle
I've Already tried this code:
<DataGridTemplateColumn Header="post"
Visibility="{Binding DataContext.ProjectPostVisibility
, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MvvmCommonControl:DataGrid}}"/>
And i'm Sure my binding is correct because it works fine when i bind the DataGridCell.Visibility like below:
<DataGridTemplateColumn Header="post">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Visibility" Value="{Binding DataContext.ProjectPostVisibility,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MvvmCommonControl:DataGrid}}"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn >
Your binding is correct, but it won't work with DataGridTemplateColumn directly because it's not in the visual tree. So it's not inherting DataContext.
You need to bind the DataGridTemplateColumn from code behind. Here is a demo that shows a way of doing it.
As mentionned in other answers, the column isn't part of the visual/logical tree and doesn't inherit from FrameworkElement meaning it has no DataContext. That's why your binding doesn't work.
However you can add a dummy (collapsed) FrameworkElement at a level where the DataContext is what you're looking for (so taking your example, it'd be at the DataGrid's level), collapse it and use it as the Source of your Binding with the x:Reference markup extension.
Here's an example :
<FrameworkElement x:Name="Proxy" Visibility="Collapsed"/>
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn Header="post"
Visibility="{Binding DataContext.ProjectPostVisibility, Source={x:Reference Name=Proxy}}"/>
</DataGrid.Columns>
</DataGrid>
Add this setter in the DataGridTemplateColumn.CellStyle and done:
<Setter Property="Visibility" Value="{Binding DataContext.isVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
If you need more help look at my example below.
I want the Remove button to not be visible at the project level. First you have to make sure you have a isVisible property in your view model:
private System.Windows.Visibility _isVisible;
public System.Windows.Visibility isVisible
{
get { return _isVisible; }
set
{
if (_isVisible != value)
{
_isVisible = value;
OnPropertyChanged("isVisible");
}
}
}
Then:
if (isProj == false)
this.model.isVisible = Visibility.Visible;
else
this.model.isVisible = Visibility.Collapsed;
XAML:
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<Button x:Name="btnRemove" Content="X">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="50" />
</Style>
</Button.Style>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="Red"/>
<Setter Property="Visibility" Value="{Binding DataContext.isVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
</Style>
</DataGridTemplateColumn.CellStyle>

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

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