How to add menu items to menu control (not contextmenu) in WPF from a database table with Bindings and Observable collections?. I have this menu:
<Menu HorizontalAlignment="Left" Height="27" VerticalAlignment="Top" Width="649">
<MenuItem Header="_File">
<MenuItem Header="_Exit" Command="{Binding ExitCommand}"/>
</MenuItem>
<MenuItem Header="_MyMenu">
<MenuItem Header="_SubMenu1" Command="{Binding SubMenu1Command}" />
<MenuItem Header="_SubMenu2" Command="{Binding SubMenu2Command}" />
</MenuItem>
</Menu>
The "SubMenu1" and "_SuMenu2" are values from the database table:
codSubMenu | SubMenuColum | CommandColumn
1__________|SubMenu1_____|SubMenu1Command
2__________|SubMenu2_____|_SubMenu2Command
I need something this:
<Menu HorizontalAlignment="Left" Height="27" VerticalAlignment="Top" Width="649"
ItemsSource="{Binding ObservableCollectionMenu}">
<MenuItem Header="_File">
<MenuItem Header="_Exit" Command="{Binding ExitCommand}"/>
</MenuItem>
<MenuItem Header="_MyMenu">
<MenuItem Header="{Binding ObservableCollectionMenu.SubMenuColumn}" Command="{Binding ObservableCollectionMenu.CommandColumn}" />
</MenuItem>
</Menu>
When I run the app the menu must show this when I press the options File and MyMenu:
File | MyMenu
Exit | SubMenu1
___| SubMenu2
Use the ItemsSource property of the Menu and the MenuItems (in a style) to bind your collections:
<Menu ItemsSource="{Binding YourCollection}" />
and
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="ItemsSource" Value="{Binding Path=Children}" />
</Style>
Edit: For command binding do the following:
Add a setter like this to the template of the MenuItem:
<Setter Property="Command" Value="{Binding Path=Command}" />
Use this structure for a MenuItem view model:
public class BindableMenuItem
{
public string Name { get; set; }
public BindableMenuItem[] Children { get; set; }
public ICommand Command { get; set; }
}
Add the root items to a collection of BindableMenuItems and bind this collection to the menu.
This is how I solved it,
I created a MenuItem class (notice that it has a list of Items so you can build sub-menus):
public class MenuItem : ModelBase<MenuItem>
{
private List<MenuItem> _Items;
public MenuItem(string header, ICommand command)
{
Header = header;
Command = command;
}
public MenuItem()
{
}
public string Header { get; set; }
public List<IMenuItem> Items
{
get { return _Items ?? (_Items = new List<IMenuItem>()); }
set { _Items = value; }
}
public ICommand Command { get; set; }
public string CommandName { get; set; }
public object Icon { get; set; }
public bool IsCheckable { get; set; }
private bool _IsChecked;
public bool IsChecked
{
get { return _IsChecked; }
set
{
_IsChecked = value;
NotifyPropertyChanged(m=>m.IsChecked);
}
}
public bool Visible { get; set; }
public bool IsSeparator { get; set; }
public string InputGestureText { get; set; }
public string ToolTip { get; set; }
public int MenuHierarchyID { get; set; }
public int ParentMenuHierarchyID { get; set; }
public string IconPath { get; set; }
public bool IsAdminOnly { get; set; }
public object Context { get; set; }
public IMenuItem Parent { get; set; }
public int int_Sequence { get; set; }
public int int_KeyIndex { get; set; }
}
And a View:
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MainMenu}">
<Menu.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding Path=Header}" />
<Setter Property="MenuItem.ItemsSource" Value="{Binding Path=Items}" />
<Setter Property="MenuItem.Icon" Value="{Binding Path=Icon}" />
<Setter Property="MenuItem.IsCheckable" Value="{Binding Path=IsCheckable}" />
<Setter Property="MenuItem.IsChecked" Value="{Binding Path=IsChecked,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="MenuItem.Command" Value="{Binding Path=Command}" />
<!--<Setter Property="MenuItem.CommandParameter" Value="{Binding Path=IsChecked}"/>-->
<Setter Property="MenuItem.CommandParameter" Value="{Binding Path=.}"/>
<Setter Property="MenuItem.InputGestureText" Value="{Binding Path=InputGestureText}"/>
<Setter Property="MenuItem.ToolTip" Value="{Binding Path=ToolTip}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=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>
Where MainMenu is an ObservableCollection property in my main ViewModel, which you can populate from your database.
public ObservableCollection<MenuItem> MainMenu
{
get { return _MainMenu; }
set
{
_MainMenu = value;
NotifyPropertyChanged(x => x.MainMenu);
}
}
I don't have a quick solution in XAML. I needed get submenus items from database, according specific profiles, some users had all items others only 2 or 3 items. The unique way is create the menu in XAML with disabled items, pass the menu reference to ViewModel(if is MVVM App) and compare with the ObservableCollection, only the items equals are enabled:
<menu horizontalalignment="Left" height="27" verticalalignment="Top" width="649" name="menu1">
<menuitem header="_File">
<menuitem header="_Exit" command="{Binding ExitCommand}" />
</menuitem>
<menuitem header="_MyMenu">
<menuitem header="_SubMenu1" command="{Binding Command1}" isenabled="False" />
<menuitem header="_SubMenu2" command="{Binding Command2}" isenabled="False" />
</menuitem>
</menu>
ViewModel:
for (int i = 0; i < ObservableCollectionMenu.Count; i++)
{
for (int j = 0; j < ((MenuItem)menu1.Items[1]).Items.Count; j++)
{
if (((MenuItem)((MenuItem)menu1.Items[1]).Items[j]).Header.ToString().Equals(ObservableCollectionMenu[i].SubMenuColumn))
{
((MenuItem)((MenuItem)menu1.Items[1]).Items[j]).IsEnabled = true;
break;
}
}
}
Thanks to all who answered my question, stackoverflow has better help that codeproject.
Related
I have this object of class type HouseInfo that contains a list property:
public class HouseInfo
{
public string House
{
get;
set;
}
public List<String> Details
{
get;
set;
}
}
public List<HouseInfo> HouseInfos { get; set; }
I am successfully binding the House property to main items of combo box using ItemSource property in xaml but can't figure out the binding of Details to their respective submenus.
<ComboBox x:Name="Houses1"
Grid.Row="1"
Grid.Column="4"
ItemsSource="{Binding HouseInfos}"
Padding="0"
DisplayMemberPath="House"
VerticalContentAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Stretch"
Margin="0,0,0,2">
</ComboBox>
I tried customizing menuitems in xaml but I get the error "itemsCollection must be empty before using items Source."
How do I get the Details list in each menu item as submenu items?
Any help would be appreciated. Thanks in advance.
Update:
I have bound submenu items as well but they are not visible. I am sure they have bound successfully as it generates submenu items equal to the count of the list inside the details property list of the object. This is the updated xaml for the menu:
<Menu x:Name="menu"
VerticalAlignment="Top"
Grid.Row="1"
Grid.Column="4"
Height="19">
<MenuItem ItemsSource="{Binding HouseInfos}"
Padding="0"
Background="#0068FF11"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5"
Height="19"
Width="105">
<MenuItem.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform X="0.5" />
</TransformGroup>
</MenuItem.RenderTransform>
<MenuItem.Header>
<Label x:Name="headerYears"
Margin="0"
Padding="0"
Content="Houses"
Background="#00FF0000"
MaxHeight="18"
UseLayoutRounding="False"
RenderTransformOrigin="0,0"
HorizontalContentAlignment="Center" />
</MenuItem.Header>
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding House}" />
<Setter Property="ItemsSource"
Value="{Binding InfoPoints}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
Here is the image of menu which is populated but not visible.
Bound but invisible submenu items
Try using the DataSource property of the combobox. You can assign HouseInfos.House1.
What I did was I dynamically assign them to the combobox
comboBox1.DataSource = HouseInfo.House1.Details;
comboBox1.DisplayMember = "HouseDetails";
comboBox1.ValueMember = "HouseDetailsID";
Or you can try something like the above.
Use this structure. I matched the names with your own names.
MainWindw.xaml
<Window x:Class="MyNameSpace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyNameSpace"
mc:Ignorable="d"
Title="TestMenu" Height="450" Width="800">
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:HouseInfo}" ItemsSource="{Binding Path=Details}">
<TextBlock Text="{Binding House}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<Grid>
</Grid>
</DockPanel>
</Window>
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace MyNameSpace
{
/// <summary>
/// Interaction logic for MainWindw.xaml
/// </summary>
public partial class MainWindw : Window
{
public List<HouseInfo> MenuItems { get; set; }
public MainWindw()
{
InitializeComponent();
MenuItems = new List<HouseInfo>();
HouseInfo houseInfo1 = new HouseInfo();
houseInfo1.House = "Header A";
houseInfo1.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header A1" }, new HouseInfo() { House = "Header A2" } };
HouseInfo houseInfo2 = new HouseInfo();
houseInfo2.House = "Header B";
houseInfo2.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header B1" }, new HouseInfo() { House = "Header B2" } };
MenuItems.Add(houseInfo1);
MenuItems.Add(houseInfo2);
DataContext = this;
}
}
public class HouseInfo
{
public string House
{
get;
set;
}
public List<HouseInfo> Details { get; set; }
private readonly ICommand _command;
public HouseInfo()
{
_command = new CommandViewModel(Execute);
}
public ICommand Command
{
get
{
return _command;
}
}
private void Execute()
{
// (NOTE: In a view model, you normally should not use MessageBox.Show()).
MessageBox.Show("Clicked at " + House);
}
}
public class CommandViewModel : ICommand
{
private readonly Action _action;
public CommandViewModel(Action action)
{
_action = action;
}
public void Execute(object o)
{
_action();
}
public bool CanExecute(object o)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
}
you can gave style to every element with this code
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
for example add this line to HouseInfo class
public Thickness Margin { get; set; }
and MainWindow.cs
MenuItems = new List<HouseInfo>();
HouseInfo houseInfo1 = new HouseInfo();
houseInfo1.House = "Header A";
houseInfo1.Margin = new Thickness(5);
houseInfo1.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header A1" }, new HouseInfo() { House = "Header A2", Margin=new Thickness(10) } };
and set Style in xaml
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Margin" Value="{Binding Margin}" />
</Style>
</Menu.ItemContainerStyle>
test:
I'm wondering if is possible bound a Command to the ComboBox, I've actually implemented the Command logic on a Menu, in this way:
<Menu HorizontalAlignment="Left" VerticalAlignment="Stretch">
<MenuItem Header="Theme" Width="100"
ItemContainerStyle="{StaticResource ThemeColorMenuItemStyle}"
ItemsSource="{Binding Themes, Mode=OneTime}" />
</Menu>
where the ItemContainerStyle have this structure:
<Style x:Key="AccentColorMenuItemStyle"
BasedOn="{StaticResource MetroMenuItem}" TargetType="{x:Type MenuItem}">
<Setter Property="CommandParameter" Value="{Binding }" />
<Setter Property="Command" Value="{Binding DataContext.ApplyAccentCommand,
RelativeSource={RelativeSource AncestorType=Window}}" />
<Setter Property="Header" Value="{Binding Name, Mode=OneWay}" />
<Setter Property="Icon" Value="{StaticResource AccentMenuIcon}" />
</Style>
and this is the command:
public ICommand ApplyAccentCommand { get; } = new SimpleCommand(o => ApplyAccent((Swatch)o));
private static void ApplyAccent(Swatch swatch)
{
new PaletteHelper().ReplaceAccentColor(swatch);
}
this MenuItem bound a Theme collection provided by MaterialDesignInXaml as Swatch model, that have this class:
public class Swatch
{
public Swatch(string name, IEnumerable<Hue> primaryHues, IEnumerable<Hue> accentHues);
public string Name { get; }
public Hue ExemplarHue { get; }
public Hue AccentExemplarHue { get; }
public IEnumerable<Hue> PrimaryHues { get; }
public IEnumerable<Hue> AccentHues { get; }
public bool IsAccented { get; }
public override string ToString();
}
so, returning to the question: is possible have this logic on a ComboBox? 'cause the MenuItem doesn't have the SelectedItem property, and I need this.
You can use Blend Behaviors and bind an event to a command. You will need to refer System.Windows.Interactivity namespace, which you can get by installing the Expression.Blend.Sdk NuGet package.
Once installed, add the following XAML namespace to your page:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And then use it as follows:
<ComboBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
The MvvmLight toolkit also offers more advanced version of InvokeCommandAction called EventToCommand that allows you to specify a EventArgsConverter as well to be able to get a specific value from the event's EventArgs instance.
My plan is to build a large WPF application containing multiple user controls as modules which are loaded at need. These modules provide a own list of menu items to be displayed in the main window.
The menu provided is a list of MenuTab objects.
public class MenuTab
{
private string _label;
private List<MenuGroup> _menuGroups = new List<MenuGroup>();
public string Label
{
get { return _label; }
set { _label = value; }
}
public List<MenuGroup> MenuGroups
{
get { return _menuGroups; }
}
public MenuTab(string label)
{
Label = label;
}
}
public class MenuGroup
{
private string _label;
private string _description;
private List<MenuEntry> _menuEntries = new List<MenuEntry>();
public string Label
{
get { return _label; }
set { _label = value; }
}
public string Description
{
get { return _description; }
set { _description = value; }
}
public List<MenuEntry> MenuEntries
{
get { return _menuEntries; }
}
public MenuGroup(string label)
{
Label = label;
}
}
public class MenuEntry
{
private string _label;
private BitmapSource _largeImage;
private BitmapSource _smallImage;
private ICommand _command;
public string Label
{
get { return _label; }
set { _label = value; }
}
public BitmapSource LargeImage
{
get { return _largeImage; }
set { _largeImage = value; }
}
public BitmapSource SmallImage
{
get { return _smallImage; }
set { _smallImage = value; }
}
public ICommand Command
{
get { return _command; }
set { _command = value; }
}
public MenuEntry(string label)
{
Label = label;
}
}
I found multiple hints in the internet how to build the menu to add additional static menus and so on.
Here how I build the menu and the HierarchicalDataTemplate which basically result in the correct number of tabs, groups and items.
Even command binding is working fine.
<RibbonWindow.Resources>
<Style x:Key="ModuleGroup" TargetType="RibbonGroup">
<Setter Property="Background" Value="AntiqueWhite" />
<Setter Property="Foreground" Value="Green" />
</Style>
<HierarchicalDataTemplate DataType="{x:Type CommonModel:MenuTab}" ItemsSource="{Binding Path=MenuGroups}">
<RibbonTab Header="{Binding Path=Label}" Background="Orange" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type CommonModel:MenuGroup}" ItemsSource="{Binding Path=MenuEntries}">
<RibbonGroup Header="{Binding Path=Label}" Style="{StaticResource ModuleGroup}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type CommonModel:MenuEntry}">
<RibbonButton Label="{Binding Path=Label}" LargeImageSource="{Binding Path=LargeImage}" SmallImageSource="{Binding Path=SmallImage}" Command="{Binding Path=Command}" />
</DataTemplate>
<CollectionViewSource x:Key="ModuleMenuTabs" Source="{Binding ModuleMenu}"/>
</RibbonWindow.Resources>
And then between my static tabs:
<CollectionContainer Collection="{Binding Source={StaticResource ModuleMenuTabs}}"/>
But the tab header and background is not shown as excepted. And also the background of the generated groups is not as excepted (as example I added some background to one of the static groups which works fine.)
Can anybody give me a hint how to get the ribbons generated correctly?
After there were now responses I did further research and tests by myself.
I found the solution working for me - maybe this helps other.
My resources section in the XAML looks know like that:
<RibbonWindow.Resources>
<DataTemplate x:Key="buttonTempl">
<RibbonButton Label="{Binding Path=Label}" LargeImageSource="{Binding Path=LargeImage}" SmallImageSource="{Binding Path=SmallImage}" Command="{Binding Path=Command}" />
</DataTemplate>
<Style TargetType="RibbonGroup" x:Key="groupStyle">
<Setter Property="Header" Value="{Binding Label}"/>
<Setter Property="ItemsSource" Value="{Binding MenuEntries}"/>
<Setter Property="ItemTemplate" Value="{StaticResource buttonTempl}"/>
</Style>
<Style TargetType="RibbonTab" x:Key="tabStyle">
<Setter Property="Header" Value="{Binding Label}"/>
<Setter Property="ItemsSource" Value="{Binding MenuGroups}"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource groupStyle}"/>
</Style>
<CollectionViewSource x:Key="StaticMenuSrc" Source="{Binding StaticMenu}"/>
<CollectionViewSource x:Key="ModuleMenuSrc" Source="{Binding ModuleMenu}"/>
And the ribbon is defined as:
<Ribbon DockPanel.Dock="Top" ItemContainerStyle="{StaticResource tabStyle}" >
<Ribbon.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource StaticMenuSrc}}" />
<CollectionContainer Collection="{Binding Source={StaticResource ModuleMenuSrc}}" />
</CompositeCollection>
</Ribbon.ItemsSource>
</Ribbon>
This way I can provide a static application menu and a module-specific menu using MVVM.
I have a menu which is built from a collection at runtime. This is all working as shown.
But if the menu contains child items (Child1, Child2 etc) the ReactiveCommand MenuCommand is never called.
If I remove all children from the menu so that the menu only contains parent items then MenuCommand is called. I am fairly new to WPF. I have re-created the problem in a sample app (code below). There are no visible binding errors in VS.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
public class Service
{
public Service(string menuHeading, string menuSubHeading)
{
MenuHeading = menuHeading;
MenuSubHeading = menuSubHeading;
}
public string MenuHeading { get; set; }
public string MenuSubHeading { get; set; }
}
public static class MenuBuilder
{
public static ReactiveList<MenuItem> Build(ReactiveList<Service> services)
{
ReactiveList<MenuItem> menuItems = new ReactiveList<MenuItem>();
foreach (var service in services)
{
AddOrUpdate(menuItems, service);
}
return menuItems;
}
private static void AddOrUpdate(ReactiveList<MenuItem> menu, Service service)
{
if (menu.Any((_ => _.Header.ToString() == service.MenuHeading)))
{
var item = menu.FirstOrDefault(x => x.Header.ToString() == service.MenuHeading);
item.Items.Add(new MenuItem() { Header = service.MenuSubHeading });
//if above line removed MenuCommand works
}
else
{
menu.Add(new MenuItem() { Header = service.MenuHeading });
var item = menu.FirstOrDefault(x => x.Header.ToString() == service.MenuHeading);
item.Items.Add(new MenuItem() { Header = service.MenuSubHeading });
//if above line removed MenuCommand works
}
}
}
public class MainWindowViewModel : ReactiveObject
{
public MainWindowViewModel()
{
MenuCommand = ReactiveCommand.Create<Object>(selectedItem => OnMenuItemSelected(selectedItem));
MenuCommand.Execute().Subscribe();
}
public ReactiveCommand<Object, Unit> MenuCommand { get; }
private ReactiveList<MenuItem> servicesMenu;
private ReactiveList<Service> Services = new ReactiveList<Service>()
{
new Service("Parent1", "Child1"),
new Service("Parent2", "Child1"),
new Service("Parent2", "Child2"),
};
public ReactiveList<MenuItem> ServicesMenu
{
get
{
if (servicesMenu == null)
{
servicesMenu = MenuBuilder.Build(Services);
return servicesMenu;
}
else
{
return servicesMenu;
}
}
}
private void OnMenuItemSelected(Object selectedItem)
{
//This method is only called when the menu does not contain any child items
}
}
<Grid>
<StackPanel Orientation="Vertical">
<Button Name="Button" Content="Button" Padding="5" HorizontalAlignment="Left"
Tag="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}">
<Button.ContextMenu>
<ContextMenu x:Name="MainMenu" ItemsSource="{Binding ServicesMenu}"
DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding DataContext.MenuCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}}" />
<Setter Property="CommandParameter"
Value="{Binding RelativeSource={RelativeSource Self}}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</Grid>
Updated XAML after suggestions form Glenn
<Grid>
<StackPanel Orientation="Vertical">
<Button Name="Button" Content="Button" Padding="5" HorizontalAlignment="Left"
Tag="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}">
<Button.ContextMenu>
<ContextMenu x:Name="MainMenu" ItemsSource="{Binding ServicesMenu}"
DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Command" Value="{Binding Command}" />
<!--<Setter Property="Command" Value="{Binding MenuCommand}" /> was also tried-->
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</Grid>
I suspect this is because child items placement target wouldn't be the Button like you expect, it would be the parent MenuItem.
One way I've gotten around this in the past is using MVVM approach for these type of menu items.
Create a Menu Item VM (you call them Service above) for your items (similar to what you already doing). In the VM have a Command property and pass in your command as part of it's constructor. Then you can just do {Binding MenuCommand} from your Item Container Style.
Also don't create the MenuItem's directly in your ViewModel, instead just bind direct to the Services. I would also recommend creating your sub-services as a ObservableCollection directly inside your Service, then in your item container set the ItemsSource property to bind to the sub-children of your Services.
I have a TreeView and have created a basic TreeItem type. Each TreeItem has a header, a TreeItem Collection for children and a collection for a possible context menu. The TreeItem class has those objects:
public delegate void dExecute(TreeItem item);
public dExecute ExecuteTarget { get; set; }
public object Tag { get; set; }
public string Header { get; set; }
public List<TreeItem> Children { get; set; }
public List<TreeItem> ContextMenu { get; set; }
The context menu uses again a HierarchicalDataTemplate to display TreeItem objects (I use the TreeItem class for the items in the treeview AND in the context menu). The context menu looks like this:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" Visibility="{Binding ShowContextMenu}" ItemsSource="{Binding ContextMenu}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Execute}"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
The context menu is rendered as I want it to be. I have created a context menu that I just attach to some of my items in the tree view. This is its content.
public List<TreeItem> ContextMenu
{
get
{
List<TreeItem> list = new List<TreeItem>();
TreeItem ti = new TreeItem("Some Action") { ExecuteTarget = targetMethod};
list.Add(ti);
ti = new TreeItem("test");
ti.Children.Add(new TreeItem("foo") { ExecuteTarget = targetMethod});
ti.Children.Add(new TreeItem("bar") { ExecuteTarget = targetMethod});
ti.Children.Add(new TreeItem("foo") { ExecuteTarget = targetMethod});
TreeItem ti2 = new TreeItem("inner"){ ExecuteTarget = targetMethod};
ti.Children.Add(ti2);
ti2.Children.Add(new TreeItem("foo") { ExecuteTarget = targetMethod});
list.Add(ti);
return list;
}
}
The context menu looks like this.
It looks as it should be. The commands work as they should. EXCEPT for the command on the highest level of the context menu. When I click on "Some Action" nothing happens. I assume that I have to add something to XAML, but I have no idea where.
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" Visibility="{Binding ShowContextMenu}" ItemsSource="{Binding ContextMenu}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Execute}"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
<!-- this is what you're missing -->
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Execute}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>