How to add horizontal separator in a dynamically created ContextMenu?
Solution 1
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.
Solution 2
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>
Solution 3
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
Solution 4
Use ItemTemplateSelector:
public class MenuItemTemplateSelector : DataTemplateSelector
{
public DataTemplate SeparatorTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var menuItem = container.GetVisualParent<MenuItem>();
if (menuItem == null)
{
throw new Exception("Unknown MenuItem type");
}
if (menuItem.DataContext == null)
{
return SeparatorTemplate;
}
return menuItem.ItemTemplate;
}
}
Xaml:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"
ItemsSource="{Binding Path=ViewContentMenuItems}" >
<ContextMenu.ItemTemplateSelector>
<templateSelectors:MenuItemTemplateSelector>
<templateSelectors:MenuItemTemplateSelector.SeparatorTemplate>
<DataTemplate>
<Separator />
</DataTemplate>
</templateSelectors:MenuItemTemplateSelector.SeparatorTemplate>
</templateSelectors:MenuItemTemplateSelector>
</ContextMenu.ItemTemplateSelector>
</ContextMenu>
In model:
public ObservableCollection<MenuItem> ViewContentMenuItems
{
get
{
var temp = new ObservableCollection<MenuItem>();
temp.Add(null);
temp.Add(CreateFolderMenuItem);
return temp;
}
}
private MenuItem CreateFolderMenuItem
{
get
{
var createFolderMenuItem = new MenuItem()
{
Header = "New Folder",
Icon = new Image
{
Source = new BitmapImage(new Uri("/icons/folderWinCreate.png", UriKind.Relative)),
Height = 16,
Width = 16
}
};
Message.SetAttach(createFolderMenuItem, "CreateDocumentsFolder");//Caliburn example
return createFolderMenuItem;
}
}
vladc77
Updated on February 02, 2020Comments
-
vladc77 over 4 years
I was looking for the solution on the internet but was not able to find it within my sample. I need to add a separator between Context menu item that are generated from code behind. I tried to add it with such code lines like below but without success.
this.Commands.Add(new ToolStripSeparator());
I am wondering if someone can help. Thank you in advance.
Context Menu XAML:
<Style x:Key="DataGridCellStyle" TargetType="{x:Type DataGridCell}"> <Setter Property="ContextMenu"> <Setter.Value> <ContextMenu ItemsSource="{Binding Commands}"> <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> </ContextMenu.ItemContainerStyle> </ContextMenu> </Setter.Value> </Setter>
C# that added in the method:
this.Commands = new ObservableCollection<ICommand>(); this.Commands.Add(MainWindow.AddRole1); this.Commands.Add(MainWindow.AddRole2); this.Commands.Add(MainWindow.AddRole3); this.Commands.Add(MainWindow.AddRole4); //this.Add(new ToolStripSeparator()); this.Commands.Add(MainWindow.AddRole5); this.Commands.Add(MainWindow.AddRole6); this.Commands.Add(MainWindow.AddRole7);
-
vladc77 over 13 yearsThank you for the idea. However, it does not work yet. I cannot insert separator yet. I changed value to be 'Value="{DynamicResource MenuSeparatorTemplate}"' and was able to debug the solution. The result is that separator is not visible and that area has rollover state over empty menuitem. I am wondering if it is possible to fix.
-
Rachel over 13 yearsYou need to create the
MenuSeparatorTemplate
. Since it isn't created, nothing is being shown. -
vladc77 over 13 yearsHow to access generic Separator template from datatrigger?
-
vladc77 over 13 yearsI added ControlTemplate and named it 'MenuSeparatorTemplate'. However, the solution is crashing when I am trying to open context menu. I believe it is very close and great solution. I just have some syntax problem. Ideally, I'd like to be able to access generic separator style that used througout the project.
-
Rachel over 13 years@vladc77: I added an example ControlTemplate to the answer. I've tested the code and it works fine without any crashes.
-
vladc77 over 13 yearsGrear. It works. However, the style is different from hormal generic separator style. There is no kind of 3D effect. Do you have any ideas why it can be missed? Thank you again.
-
vladc77 over 13 yearsI just draw separator there without adding <Separator /> code line. It works great now. I am just wondering why its style missed.
-
vladc77 almost 13 yearsThank you for sharing this technique.
-
H.B. over 11 yearsNot such a great idea if you want to maintain a model-view-separation though.
-
samneric over 11 yearsNot sure why. This code is located in the ViewModel so is separate from the View. True is contains View-related UI controls and if you wanted to keep those out of the ViewModel you could put all the functionality into a behavior and have that bind to a list of commands for the menu items with a dummy command for the separator.
-
aaronburro almost 8 yearsDownvoted because you shouldn't put MenuItems in a ViewModel. It's a ViewModel, not a view.
-
aaronburro almost 8 yearsDo not put View elements in a View Model, much less a model.
-
Adrian S about 7 yearsYes, the separator looks weird - it goes across the entire menu (it should not cover the icon area on the left) and it is much thicker than a normal separator. Other than this, the solution looks promising.
-
Brett DeWoody over 6 yearsWelcome to Stack Overflow! Seems this is almost an answer, so I'd recommend removing the part about it being a comment, and add more context to make it a complete solution.
-
IgorStack over 4 yearsI used
{x:Null}
as a value to indicate separator. However if user manage to click exactly on the separator program crashed. Apparently it tried to invokeCommand
on the null value. I created special static value instead of null: `{x:Static MyItem.MySpecialStatic} and it worked.