wpf 如何在动态创建的 ContextMenu 中添加水平分隔符?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4823760/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 22:32:53  来源:igfitidea点击:

How to add horizontal separator in a dynamically created ContextMenu?

wpfwpf-controlsbindingwpfdatagrid

提问by vladc77

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:

上下文菜单 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:

在方法中添加的 C#:

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

采纳答案by samneric

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.

我对这个问题的第一个回答,虽然它确实有效,但并不遵循 MVVM 设计原则。我现在提供一种 MVVM 方法,并在下面留下原始答案以供参考。

You can create a behavior to solve this problem.

您可以创建一个行为来解决这个问题。

XAML:

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

或者,不是将 ContextMenu 绑定到命令集合,而是将其绑定到 FrameworkElements 集合,然后您可以将 MenuItems 或 Separators 直接添加到集合中,并让 Menu 控件完成所有模板...

<Style x:Key="DataGridCellStyle" TargetType="{x:Type DataGridCell}">
    <Setter Property="ContextMenu">
        <Setter.Value>
            <ContextMenu ItemsSource="{Binding Commands}" />
        </Setter.Value>
    </Setter>
</Style>

C#:

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.

刚刚在我的应用程序中使用了这种方法 - 分隔符也以这种方式看起来更好。

回答by Rachel

I did this once and used a nullas my separator. From the XAML, I then styled the template to use a separator if the datacontext was null

我做了一次,并使用 anull作为我的分隔符。从 XAML 中,如果 datacontext 为空,我将模板设置为使用分隔符

Code behind:

后面的代码:

this.Commands.Add(MainWindow.AddRole4);
this.Add(null); 
this.Commands.Add(MainWindow.AddRole5);

XAML was something like this:

XAML 是这样的:

<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

希望我的语法正确 - 我在这台机器上没有 IDE 来验证代码

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,尽管你可以把它放在你的应用程序中任何你想要的地方,只要 ContextMenu 可以访问它。

<ContextMenu.Resources>
    <ControlTemplate x:Key="MenuSeparatorTemplate">
        <Separator />
    </ControlTemplate>
</ContextMenu.Resources>

回答by Tin Man

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.

我已经修改了上面 Rachel 提供的解决方案以更正 Separator 样式。我意识到这篇文章很旧,但仍然是谷歌上最好的结果之一。在我的情况下,我将它用于 Menu 和 ContextMenu,但同样应该可以工作。

XAML

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

带分隔符样式更改

回答by user2622162

Use ItemTemplateSelector:

使用 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:

Xml:

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

回答by ArtPost

To do this correctly for MVVMyou have to define your own item interface (f.e. IMenuItem), create derived classes for Menu/ ContextMenuand for MenuItem, in these classes override following virtual protected methods :

要为MVVM正确执行此操作,您必须定义自己的项目接口(fe IMenuItem),为Menu/ ContextMenuMenuItem创建派生类,在这些类中覆盖以下虚拟受保护方法:

ItemsControl.PrepareContainerForItemOverride
ItemsControl.ClearContainerForItemOverride
ItemsControl.GetContainerForItemOverride
ItemsControl.IsItemItsOwnContainerOverride

Ensure that this methods create for items of type IMenuItemcontainers of your new derived from MenuItemtype with binding all needed properties, here you can differentiate different types of IMenuItemto show normal items, separator or some thins else. For unknown types call base implementation.

确保此方法为IMenuItem类型的项目创建了从MenuItem类型派生的新容器,并绑定了所有需要的属性,在这里您可以区分不同类型的IMenuItem以显示正常项目、分隔符或其他一些细项。对于未知类型调用基本实现。

Now, if you will bind ItemsSourceproperty of your new derived from Menu/ContextMenucontrol with collection of IMenuItem, it will show you expected result without need to now View-stuff on ViewModel side.

现在,如果您将新派生自Menu/ ContextMenu控件的ItemsSource属性与IMenuItem集合绑定,它将显示您预期的结果,而无需现在 ViewModel 端的 View-stuff。