Search code examples
wpfwpf-controlsbindingwpfdatagrid

How to add horizontal separator in a dynamically created ContextMenu?


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); 

Solution

  • 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.