【问题标题】:How to add horizontal separator in a dynamically created ContextMenu?如何在动态创建的 ContextMenu 中添加水平分隔符?
【发布时间】:2011-06-16 23:06:24
【问题描述】:

我在互联网上寻找解决方案,但无法在我的示例中找到它。我需要在从后面的代码生成的上下文菜单项之间添加一个分隔符。我尝试使用如下代码行添加它,但没有成功。

this.Commands.Add(new ToolStripSeparator()); 

我想知道是否有人可以提供帮助。提前谢谢你。

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

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

【问题讨论】:

    标签: wpf wpf-controls binding wpfdatagrid


    【解决方案1】:

    我这样做过一次,并使用null 作为分隔符。从 XAML 中,如果数据上下文为空,我然后将模板设置为使用分隔符

    后面的代码:

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

    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>
    

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

    编辑

    这是上下文菜单分隔符的示例模板。我将它放在ContextMenu.Resources 中,尽管只要 ContextMenu 可以访问它,您可以将它放在应用程序中您想要的任何位置。

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

    【讨论】:

    • 感谢您的想法。但是,它还不起作用。我还不能插入分隔符。我将值更改为 'Value="{DynamicResource MenuSeparatorTemplate}"' 并能够调试解决方案。结果是分隔符不可见,并且该区域在空菜单项上具有翻转状态。我想知道是否可以修复。
    • 您需要创建MenuSeparatorTemplate。由于它没有被创建,所以没有显示任何内容。
    • 如何从 datatrigger 访问通用分隔符模板?
    • 我添加了 ControlTemplate 并将其命名为“MenuSeparatorTemplate”。但是,当我尝试打开上下文菜单时,解决方案崩溃了。我相信这是非常接近和很好的解决方案。我只是有一些语法问题。理想情况下,我希望能够访问在整个项目中使用的通用分隔符样式。
    • @vladc77:我在答案中添加了一个示例 ControlTemplate。我已经测试了代码,它运行良好,没有任何崩溃。
    【解决方案2】:

    编辑:

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

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

    XAML:

    <Menu>
        <MenuItem Header="_File" menu:MenuBehavior.MenuItems="{Binding Path=MenuItemViewModels, Mode=OneWay}">
    
        </MenuItem>
    </Menu>
    

    视图模型:

    public IEnumerable<MenuItemViewModelBase> MenuItemViewModels => new List<MenuItemViewModelBase>
    {
        new MenuItemViewModel { Header = "Hello" },
        new MenuItemSeparatorViewModel(),
        new MenuItemViewModel { Header = "World" }
    };
    

    行为:

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

    类:

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

    原答案:

    或者,不是让 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#:

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

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

    【讨论】:

    • 感谢您分享这项技术。
    • 如果您想保持模型-视图-分离,这不是一个好主意。
    • 不知道为什么。此代码位于 ViewModel 中,因此与 View 是分开的。 True 包含与 View 相关的 UI 控件,如果您想将这些控件排除在 ViewModel 之外,您可以将所有功能放入一个行为中,并将其绑定到菜单项的命令列表,并使用分隔符的虚拟命令。
    • 投了反对票,因为您不应该将 MenuItems 放在 ViewModel 中。它是 ViewModel,而不是视图。
    【解决方案3】:

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

    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

    【讨论】:

    • 欢迎来到 Stack Overflow!似乎这几乎是一个答案,所以我建议删除关于它作为评论的部分,并添加更多上下文以使其成为一个完整的解决方案。
    • 我用{x:Null}作为一个值来表示分隔符。但是,如果用户设法准确单击分隔符程序就会崩溃。显然它试图在空值上调用Command。我创建了特殊的静态值而不是 null:`{x:Static MyItem.MySpecialStatic} 并且它起作用了。
    【解决方案4】:

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

    在模型中:

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

    【讨论】:

    • 不要将视图元素放在视图模型中,更不用说模型了。
    【解决方案5】:

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

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

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

    现在,如果您要绑定从 Menu/ContextMenu 派生的新的 ItemsSource 属性 带有 IMenuItem 集合的控件,它会向您显示预期的结果,而无需在 ViewModel 端查看 View-stuff。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-05
      • 1970-01-01
      • 2018-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多