如何在动态创建的 ContextMenu 中添加水平分隔符?
Posted
技术标签:
【中文标题】如何在动态创建的 ContextMenu 中添加水平分隔符?【英文标题】:How to add horizontal separator in a dynamically created 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);
【问题讨论】:
【参考方案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创建派生类> / ContextMenu 和 MenuItem,在这些类中覆盖以下虚拟保护方法:
ItemsControl.PrepareContainerForItemOverride
ItemsControl.ClearContainerForItemOverride
ItemsControl.GetContainerForItemOverride
ItemsControl.IsItemItsOwnContainerOverride
确保此方法为您的新派生自 MenuItem 类型的 IMenuItem 容器类型的项目创建并绑定所有需要的属性,在这里你可以区分不同类型的 IMenuItem 以显示普通项目、分隔符或其他一些细项。对于未知类型调用基实现。
现在,如果您要绑定从 Menu/ContextMenu 派生的新的 ItemsSource 属性 带有 IMenuItem 集合的控件,它会向您显示预期的结果,而无需在 ViewModel 端查看 View-stuff。
【讨论】:
以上是关于如何在动态创建的 ContextMenu 中添加水平分隔符?的主要内容,如果未能解决你的问题,请参考以下文章
C#.NET中如何设置CONTEXTMENU菜单项的点击事件
如何使用下面的类从web服务中动态读取下面给出的JSON数据,并为收到的数据创建水平列表视图。
SwiftUI contextMenu - 如何添加红色(危险)动作[重复]