How to add horizontal separator in a dynamically created ContextMenu?
I have modified the solution provided by Rachel above to correct the Separator style. I realize this post is old, but still one of the top results on Google. In my situation, I was using it for a Menu vs a ContextMenu, but the same should work.
XAML
<Menu ItemsSource="{Binding MenuItems}">
<Menu.Resources>
<ControlTemplate x:Key="MenuSeparatorTemplate">
<Separator>
<Separator.Style>
<Style TargetType="{x:Type Separator}" BasedOn="{StaticResource ResourceKey={x:Static MenuItem.SeparatorStyleKey}}"/>
</Separator.Style>
</Separator>
</ControlTemplate>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding MenuItemHeader}" />
<Setter Property="Command" Value="{Binding MenuItemCommand}" />
<Setter Property="CommandParameter" Value="{Binding MenuItemCommandParameter}" />
<Setter Property="ItemsSource" Value="{Binding MenuItemCollection}" />
<Style.Triggers>
<DataTrigger Binding="{Binding }" Value="{x:Null}">
<Setter Property="Template" Value="{StaticResource MenuSeparatorTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.Resources>
</Menu>
Without Separator Style Change
With Separator Style Change
I did this once and used a null
as my separator. From the XAML, I then styled the template to use a separator if the datacontext was null
Code behind:
this.Commands.Add(MainWindow.AddRole4);
this.Add(null);
this.Commands.Add(MainWindow.AddRole5);
XAML was something like this:
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding}" />
<Setter Property="Header" Value="{Binding Path=Text}" />
<Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
<Style.Triggers>
<DataTrigger Binding="{Binding }" Value="{x:Null}">
<Setter Property="Template" Value="{StaticResource MenuSeparatorTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContextMenu.ItemContainerStyle>
Hope I got the syntax right - I don't have an IDE on this machine to verify the code
EDIT
Here is an example template for the context menu separator. I am putting it in ContextMenu.Resources
, although you could put this anywhere you want in your application as long as the ContextMenu can access it.
<ContextMenu.Resources>
<ControlTemplate x:Key="MenuSeparatorTemplate">
<Separator />
</ControlTemplate>
</ContextMenu.Resources>
EDIT:
My first answer to this question, though it actually worked, does not follow the MVVM design principle. I am now providing an MVVM approach and leaving the original answer below for reference.
You can create a behavior to solve this problem.
XAML:
<Menu>
<MenuItem Header="_File" menu:MenuBehavior.MenuItems="{Binding Path=MenuItemViewModels, Mode=OneWay}">
</MenuItem>
</Menu>
ViewModel:
public IEnumerable<MenuItemViewModelBase> MenuItemViewModels => new List<MenuItemViewModelBase>
{
new MenuItemViewModel { Header = "Hello" },
new MenuItemSeparatorViewModel(),
new MenuItemViewModel { Header = "World" }
};
Behavior:
public class MenuBehavior
{
public static readonly DependencyProperty MenuItemsProperty =
DependencyProperty.RegisterAttached("MenuItems",
typeof(IEnumerable<MenuItemViewModelBase>), typeof(MenuBehavior),
new FrameworkPropertyMetadata(MenuItemsChanged));
public static IEnumerable<MenuItemViewModelBase> GetMenuItems(DependencyObject element)
{
if (element == null)
{
throw (new ArgumentNullException("element"));
}
return (IEnumerable<MenuItemViewModelBase>)element.GetValue(MenuItemsProperty);
}
public static void SetMenuItems(DependencyObject element, IEnumerable<MenuItemViewModelBase> value)
{
if (element == null)
{
throw (new ArgumentNullException("element"));
}
element.SetValue(MenuItemsProperty, value);
}
private static void MenuItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menu = (MenuItem)d;
if (e.OldValue != e.NewValue)
{
menu.ItemsSource = ConvertViewModelsToFrameworkElements((IEnumerable<MenuItemViewModelBase>)e.NewValue);
}
}
private static IEnumerable<FrameworkElement> ConvertViewModelsToFrameworkElements(IEnumerable<MenuItemViewModelBase> viewModels)
{
var frameworkElementList = new List<FrameworkElement>();
foreach (var viewModel in viewModels)
{
switch (viewModel)
{
case MenuItemViewModel mi:
frameworkElementList.Add(new MenuItem
{
Header = mi.Header,
Command = mi.Command,
Icon = mi.Icon
});
break;
case MenuItemSeparatorViewModel s:
frameworkElementList.Add(new Separator());
break;
}
}
return frameworkElementList;
}
}
Classes:
public class MenuItemViewModelBase
{
}
public class MenuItemViewModel : MenuItemViewModelBase
{
public object Header { get; set; }
public ICommand Command { get; set; }
public object Icon { get; set; }
}
public class MenuItemSeparatorViewModel : MenuItemViewModelBase
{
}
Original Answer:
Or, instead of having your ContextMenu bind to a collection of commands, bind it to a collection of FrameworkElements then you can add either MenuItems or Separators directly to the collection and let the Menu control do all the templating....
<Style x:Key="DataGridCellStyle" TargetType="{x:Type DataGridCell}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu ItemsSource="{Binding Commands}" />
</Setter.Value>
</Setter>
</Style>
C#:
this.Commands = new ObservableCollection<FrameworkElement>();
this.Commands.Add(new MenuItem {Header = "Menuitem 2", Command = MainWindow.AddRole1});
this.Commands.Add(new MenuItem {Header = "Menuitem 2", Command = MainWindow.AddRole2});
this.Commands.Add(new MenuItem {Header = "Menuitem 3", Command = MainWindow.AddRole3});
this.Commands.Add(new MenuItem {Header = "Menuitem 4", Command = MainWindow.AddRole4});
this.Commands.Add(new Separator);
this.Commands.Add(new MenuItem {Header = "Menuitem 5", Command = MainWindow.AddRole5});
this.Commands.Add(new MenuItem {Header = "Menuitem 6", Command = MainWindow.AddRole6});
this.Commands.Add(new MenuItem {Header = "Menuitem 7", Command = MainWindow.AddRole7});
Just used this approach in my app - the separator looks better this way also.