Reputation: 1064
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);
Upvotes: 22
Views: 30234
Reputation: 10468
WPF provides exactly what you're looking for - it's called "Separator":
this.Commands.Add(new Separator());
Upvotes: 0
Reputation: 3218
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.
Upvotes: 14
Reputation: 121
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
Upvotes: 12
Reputation: 1
To do this correctly for MVVM you have to define your own item interface (f.e. IMenuItem), create derived classes for Menu / ContextMenu and for MenuItem, in these classes override following virtual protected methods :
ItemsControl.PrepareContainerForItemOverride
ItemsControl.ClearContainerForItemOverride
ItemsControl.GetContainerForItemOverride
ItemsControl.IsItemItsOwnContainerOverride
Ensure that this methods create for items of type IMenuItem containers of your new derived from MenuItem type with binding all needed properties, here you can differentiate different types of IMenuItem to show normal items, separator or some thins else. For unknown types call base implementation.
Now, if you will bind ItemsSource property of your new derived from Menu/ContextMenu control with collection of IMenuItem, it will show you expected result without need to now View-stuff on ViewModel side.
Upvotes: -1
Reputation: 15
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;
}
}
Upvotes: 1
Reputation: 132568
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>
Upvotes: 52