相互排斥的可检查菜单项?
给出以下代码:
<MenuItem x:Name="MenuItem_Root" Header="Root"> <MenuItem x:Name="MenuItem_Item1" IsCheckable="True" Header="item1" /> <MenuItem x:Name="MenuItem_Item2" IsCheckable="True" Header="item2"/> <MenuItem x:Name="MenuItem_Item3" IsCheckable="True" Header="item3"/> </MenuItem>
在XAML中,是否有创build可互相排斥的可检查菜单项的方法? 用户在哪里检查项目2,项目1和3自动取消选中。
我可以通过监视菜单上的点击事件,确定哪个项目被检查,并取消选中其他菜单项来完成代码。 我想有一个更简单的方法。
有任何想法吗?
这可能不是你正在寻找的,但是你可以写一个MenuItem
类的扩展,它允许你使用类似于RadioButton
类的GroupName
属性。 我稍微修改了这个方便的例子,用于类似地扩展ToggleButton
控件,并针对您的情况稍微修改了一下,并提出了这个问题:
using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; namespace WpfTest { public class MenuItemExtensions : DependencyObject { public static Dictionary<MenuItem, String> ElementToGroupNames = new Dictionary<MenuItem, String>(); public static readonly DependencyProperty GroupNameProperty = DependencyProperty.RegisterAttached("GroupName", typeof(String), typeof(MenuItemExtensions), new PropertyMetadata(String.Empty, OnGroupNameChanged)); public static void SetGroupName(MenuItem element, String value) { element.SetValue(GroupNameProperty, value); } public static String GetGroupName(MenuItem element) { return element.GetValue(GroupNameProperty).ToString(); } private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { //Add an entry to the group name collection var menuItem = d as MenuItem; if (menuItem != null) { String newGroupName = e.NewValue.ToString(); String oldGroupName = e.OldValue.ToString(); if (String.IsNullOrEmpty(newGroupName)) { //Removing the toggle button from grouping RemoveCheckboxFromGrouping(menuItem); } else { //Switching to a new group if (newGroupName != oldGroupName) { if (!String.IsNullOrEmpty(oldGroupName)) { //Remove the old group mapping RemoveCheckboxFromGrouping(menuItem); } ElementToGroupNames.Add(menuItem, e.NewValue.ToString()); menuItem.Checked += MenuItemChecked; } } } } private static void RemoveCheckboxFromGrouping(MenuItem checkBox) { ElementToGroupNames.Remove(checkBox); checkBox.Checked -= MenuItemChecked; } static void MenuItemChecked(object sender, RoutedEventArgs e) { var menuItem = e.OriginalSource as MenuItem; foreach (var item in ElementToGroupNames) { if (item.Key != menuItem && item.Value == GetGroupName(menuItem)) { item.Key.IsChecked = false; } } } } }
然后,在XAML中,你会写:
<MenuItem x:Name="MenuItem_Root" Header="Root"> <MenuItem x:Name="MenuItem_Item1" YourNamespace:MenuItemExtensions.GroupName="someGroup" IsCheckable="True" Header="item1" /> <MenuItem x:Name="MenuItem_Item2" YourNamespace:MenuItemExtensions.GroupName="someGroup" IsCheckable="True" Header="item2"/> <MenuItem x:Name="MenuItem_Item3" YourNamespace:MenuItemExtensions.GroupName="someGroup" IsCheckable="True" Header="item3"/> </MenuItem>
这有点痛苦,但是它提供了不强制你写任何额外的程序代码(当然除了扩展类)来实现它的好处。
值得一提的是Brad Cunningham撰写的原始ToggleButton解决scheme。
你也可以使用行为。 像这个:
<MenuItem Header="menu"> <MenuItem x:Name="item1" Header="item1" IsCheckable="true" ></MenuItem> <MenuItem x:Name="item2" Header="item2" IsCheckable="true"></MenuItem> <MenuItem x:Name="item3" Header="item3" IsCheckable="true" ></MenuItem> <i:Interaction.Behaviors> <local:MenuItemButtonGroupBehavior></local:MenuItemButtonGroupBehavior> </i:Interaction.Behaviors> </MenuItem> public class MenuItemButtonGroupBehavior : Behavior<MenuItem> { protected override void OnAttached() { base.OnAttached(); GetCheckableSubMenuItems(AssociatedObject) .ToList() .ForEach(item => item.Click += OnClick); } protected override void OnDetaching() { base.OnDetaching(); GetCheckableSubMenuItems(AssociatedObject) .ToList() .ForEach(item => item.Click -= OnClick); } private static IEnumerable<MenuItem> GetCheckableSubMenuItems(ItemsControl menuItem) { var itemCollection = menuItem.Items; return itemCollection.OfType<MenuItem>().Where(menuItemCandidate => menuItemCandidate.IsCheckable); } private void OnClick(object sender, RoutedEventArgs routedEventArgs) { var menuItem = (MenuItem)sender; if (!menuItem.IsChecked) { menuItem.IsChecked = true; return; } GetCheckableSubMenuItems(AssociatedObject) .Where(item => item != menuItem) .ToList() .ForEach(item => item.IsChecked = false); } }
在底部添加这个,因为我还没有声望…
帕特里克的答案是有帮助的,但并不能确保项目不能被取消选中。 为此,Checked处理程序应该更改为Click处理程序,并更改为以下内容:
static void MenuItemClicked(object sender, RoutedEventArgs e) { var menuItem = e.OriginalSource as MenuItem; if (menuItem.IsChecked) { foreach (var item in ElementToGroupNames) { if (item.Key != menuItem && item.Value == GetGroupName(menuItem)) { item.Key.IsChecked = false; } } } else // it's not possible for the user to deselect an item { menuItem.IsChecked = true; } }
是的,这可以通过使每个MenuItem是一个RadioButton来轻松完成。 这可以通过编辑MenuItem模板来完成。
-
右键单击Document-Outline左窗格> EditTemplate> EditCopy中的MenuItem。 这将添加在Window.Resources下编辑的代码。
-
现在,你只能做两个非常简单的修改。
一个。 用一些资源添加RadioButton来隐藏它的圆圈部分。
湾 为MenuItem边框部分更改BorderThickness = 0。
这些更改在下面显示为注释,生成的样式的其余部分应按原样使用:
<Window.Resources> <LinearGradientBrush x:Key="MenuItemSelectionFill" EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#34C5EBFF" Offset="0"/> <GradientStop Color="#3481D8FF" Offset="1"/> </LinearGradientBrush> <Geometry x:Key="Checkmark">M 0,5.1 L 1.7,5.2 L 3.4,7.1 L 8,0.4 L 9.2,0 L 3.3,10.8 Z</Geometry> <ControlTemplate x:Key="{ComponentResourceKey ResourceId=SubmenuItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}" TargetType="{x:Type MenuItem}"> <Grid SnapsToDevicePixels="true"> <Rectangle x:Name="Bg" Fill="{TemplateBinding Background}" RadiusY="2" RadiusX="2" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1"/> <Rectangle x:Name="InnerBorder" Margin="1" RadiusY="2" RadiusX="2"/> <!-- Add RadioButton around the Grid --> <RadioButton Background="Transparent" GroupName="MENUITEM_GRP" IsHitTestVisible="False" IsChecked="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=MenuItem}}"> <RadioButton.Resources> <Style TargetType="Themes:BulletChrome"> <Setter Property="Visibility" Value="Collapsed"/> </Style> </RadioButton.Resources> <!-- Add RadioButton Top part ends here --> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition MinWidth="24" SharedSizeGroup="MenuItemIconColumnGroup" Width="Auto"/> <ColumnDefinition Width="4"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="37"/> <ColumnDefinition SharedSizeGroup="MenuItemIGTColumnGroup" Width="Auto"/> <ColumnDefinition Width="17"/> </Grid.ColumnDefinitions> <ContentPresenter x:Name="Icon" ContentSource="Icon" Margin="1" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/> <!-- Change border thickness to 0 --> <Border x:Name="GlyphPanel" BorderBrush="#CDD3E6" BorderThickness="0" Background="#E6EFF4" CornerRadius="3" Height="22" Margin="1" Visibility="Hidden" Width="22"> <Path x:Name="Glyph" Data="{StaticResource Checkmark}" Fill="#0C12A1" FlowDirection="LeftToRight" Height="11" Width="9"/> </Border> <ContentPresenter Grid.Column="2" ContentSource="Header" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> <TextBlock Grid.Column="4" Margin="{TemplateBinding Padding}" Text="{TemplateBinding InputGestureText}"/> </Grid> </RadioButton> <!-- RadioButton closed , thats it ! --> </Grid> ... </Window.Resources>
-
应用样式,
<MenuItem IsCheckable="True" Header="Open" Style="{DynamicResource MenuItemStyle1}"
在XAML中没有内置的方法来实现这一点,您将需要推出自己的解决scheme或获得现有解决scheme(如果可用)。
我只是以为我会抛出我的解决scheme,因为没有答案满足我的需求。 我的完整解决scheme在这里…
WPF MenuItem作为一个RadioButton
但是,基本的想法是使用ItemContainerStyle。
<MenuItem.ItemContainerStyle> <Style TargetType="MenuItem"> <Setter Property="Icon" Value="{DynamicResource RadioButtonResource}"/> <EventSetter Event="Click" Handler="MenuItemWithRadioButtons_Click" /> </Style> </MenuItem.ItemContainerStyle>
并且应该添加以下事件点击,以便在单击MenuItem时检查RadioButton(否则,您必须单击RadioButton):
private void MenuItemWithRadioButtons_Click(object sender, System.Windows.RoutedEventArgs e) { MenuItem mi = sender as MenuItem; if (mi != null) { RadioButton rb = mi.Icon as RadioButton; if (rb != null) { rb.IsChecked = true; } } }
由于没有类似的答案,我在这里发布我的解决scheme:
public class RadioMenuItem : MenuItem { public string GroupName { get; set; } protected override void OnClick() { var ic = Parent as ItemsControl; if (null != ic) { var rmi = ic.Items.OfType<RadioMenuItem>().FirstOrDefault(i => i.GroupName == GroupName && i.IsChecked); if (null != rmi) rmi.IsChecked = false; IsChecked = true; } base.OnClick(); } }
在XAML中,将其用作通常的MenuItem:
<MenuItem Header="OOO"> <local:RadioMenuItem Header="111" GroupName="G1"/> <local:RadioMenuItem Header="222" GroupName="G1"/> <local:RadioMenuItem Header="333" GroupName="G1"/> <local:RadioMenuItem Header="444" GroupName="G1"/> <local:RadioMenuItem Header="555" GroupName="G1"/> <local:RadioMenuItem Header="666" GroupName="G1"/> <Separator/> <local:RadioMenuItem Header="111" GroupName="G2"/> <local:RadioMenuItem Header="222" GroupName="G2"/> <local:RadioMenuItem Header="333" GroupName="G2"/> <local:RadioMenuItem Header="444" GroupName="G2"/> <local:RadioMenuItem Header="555" GroupName="G2"/> <local:RadioMenuItem Header="666" GroupName="G2"/> </MenuItem>
很简单,干净。 当然,你可以通过一些额外的代码使GroupName
成为一个依赖属性,这与其他代码是一样的。
顺便说一句,如果你不喜欢这个复选标记,你可以把它改成任何你喜欢的东西:
public override void OnApplyTemplate() { base.OnApplyTemplate(); var p = GetTemplateChild("Glyph") as Path; if (null == p) return; var x = p.Width/2; var y = p.Height/2; var r = Math.Min(x, y) - 1; var e = new EllipseGeometry(new Point(x,y), r, r); // this is just a flattened dot, of course you can draw // something else, eg a star? ;) p.Data = e.GetFlattenedPathGeometry(); }
如果您在程序中使用了大量的RadioMenuItem
,那么下面显示了另一个更高效的版本。 文字数据从e.GetFlattenedPathGeometry().ToString()
在前面的代码片段中获得。
private static readonly Geometry RadioDot = Geometry.Parse("M9,5.5L8.7,7.1 7.8,8.3 6.6,9.2L5,9.5L3.4,9.2 2.2,8.3 1.3,7.1L1,5.5L1.3,3.9 2.2,2.7 3.4,1.8L5,1.5L6.6,1.8 7.8,2.7 8.7,3.9L9,5.5z"); public override void OnApplyTemplate() { base.OnApplyTemplate(); var p = GetTemplateChild("Glyph") as Path; if (null == p) return; p.Data = RadioDot; }
最后,如果你打算把它包装在项目中使用,你应该从基类中隐藏IsCheckable
属性,因为MenuItem
类的自动检查机制将导致无线电检查状态标记错误的行为。
private new bool IsCheckable { get; }
因此,如果新手尝试像这样编译XAML,VS会出错:
//注意这是一个错误的用法!
<local:RadioMenuItem Header="111" GroupName="G1" IsCheckable="True"/>
//注意这是一个错误的用法!
我使用几行代码实现了这一点:
首先声明一个variables:
MenuItem LastBrightnessMenuItem =null;
当我们考虑一组菜单项时,有一个使用单个事件处理程序的概率。 在这种情况下,我们可以使用这个逻辑:
private void BrightnessMenuClick(object sender, RoutedEventArgs e) { if (LastBrightnessMenuItem != null) { LastBrightnessMenuItem.IsChecked = false; } MenuItem m = sender as MenuItem; LastBrightnessMenuItem = m; //Handle the rest of the logic here }
当将MenuItem.IsChecked绑定到一个variables时,我发现我得到了互斥的菜单项。
但它有一个怪癖:如果你点击所选的菜单项,它会变得无效,由通常的红色矩形显示。 我通过添加MenuItem.Click的处理程序来解决这个问题,通过将IsChecked设置为true来防止取消select。
代码…我绑定到一个枚举types,所以我使用一个枚举转换器返回true,如果绑定属性等于提供的参数。 这里是XAML:
<MenuItem Header="Black" IsCheckable="True" IsChecked="{Binding SelectedColor, Converter={StaticResource EnumConverter}, ConverterParameter=Black}" Click="MenuItem_OnClickDisallowUnselect"/> <MenuItem Header="Red" IsCheckable="True" IsChecked="{Binding SelectedColor, Converter={StaticResource EnumConverter}, ConverterParameter=Red}" Click="MenuItem_OnClickDisallowUnselect"/>
这里是代码背后:
private void MenuItem_OnClickDisallowUnselect(object sender, RoutedEventArgs e) { var menuItem = e.OriginalSource as MenuItem; if (menuItem == null) return; if (! menuItem.IsChecked) { menuItem.IsChecked = true; } }
只需为MenuItem创build一个模板,该模板将包含一个GroupName设置为某个值的RadioButton。 您还可以将RadioButton的模板更改为MenuItem的默认检查字形(可以使用Expression Blend轻松提取)。
而已!
你可以做这样的事情:
<Menu> <MenuItem Header="File"> <ListBox BorderThickness="0" Background="Transparent"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <StackPanel /> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <MenuItem IsCheckable="True" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Header="{Binding Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" /> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListBox.ItemContainerStyle> <ListBox.Items> <ListBoxItem Content="Test" /> <ListBoxItem Content="Test2" /> </ListBox.Items> </ListBox> </MenuItem> </Menu>
它有一些奇怪的副作用(你会看到,当你使用它),但它仍然工作
这是另一种使用RoutedUICommands,一个公共枚举属性和DataTriggers的方法。 这是一个相当详细的解决scheme。 我不幸的看不到任何使Style.Triggers变小的方法,因为我不知道怎么说Binding Value是唯一不同的东西? (顺便说一下,对MVVM来说这是一个很糟糕的例子,为了保持简单,我把所有东西都放在了MainWindow类中。)
MainWindow.xaml:
<Window x:Class="MutuallyExclusiveMenuItems.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:view="clr-namespace:MutuallyExclusiveMenuItems" Title="MainWindow" Height="350" Width="525"> <Window.CommandBindings> <CommandBinding Command="{x:Static view:MainWindow.MenuItem1Cmd}" CanExecute="CanExecute" Executed="MenuItem1Execute" /> <CommandBinding Command="{x:Static view:MainWindow.MenuItem2Cmd}" CanExecute="CanExecute" Executed="MenuItem2Execute" /> <CommandBinding Command="{x:Static view:MainWindow.MenuItem3Cmd}" CanExecute="CanExecute" Executed="MenuItem3Execute" /> </Window.CommandBindings> <Window.InputBindings> <KeyBinding Command="{x:Static view:MainWindow.MenuItem1Cmd}" Gesture="Ctrl+1"/> <KeyBinding Command="{x:Static view:MainWindow.MenuItem2Cmd}" Gesture="Ctrl+2"/> <KeyBinding Command="{x:Static view:MainWindow.MenuItem3Cmd}" Gesture="Ctrl+3"/> </Window.InputBindings> <DockPanel> <DockPanel DockPanel.Dock="Top"> <Menu> <MenuItem Header="_Root"> <MenuItem Command="{x:Static view:MainWindow.MenuItem1Cmd}" InputGestureText="Ctrl+1"> <MenuItem.Style> <Style> <Style.Triggers> <DataTrigger Binding="{Binding CurrentMenuItem, Mode=OneWay}" Value="{x:Static view:MainWindow+CurrentItemEnum.EnumItem1}"> <Setter Property="MenuItem.IsChecked" Value="True"/> </DataTrigger> </Style.Triggers> </Style> </MenuItem.Style> </MenuItem> <MenuItem Command="{x:Static view:MainWindow.MenuItem2Cmd}" InputGestureText="Ctrl+2"> <MenuItem.Style> <Style> <Style.Triggers> <DataTrigger Binding="{Binding CurrentMenuItem, Mode=OneWay}" Value="{x:Static view:MainWindow+CurrentItemEnum.EnumItem2}"> <Setter Property="MenuItem.IsChecked" Value="True"/> </DataTrigger> </Style.Triggers> </Style> </MenuItem.Style> </MenuItem> <MenuItem Command="{x:Static view:MainWindow.MenuItem3Cmd}" InputGestureText="Ctrl+3"> <MenuItem.Style> <Style> <Style.Triggers> <DataTrigger Binding="{Binding CurrentMenuItem, Mode=OneWay}" Value="{x:Static view:MainWindow+CurrentItemEnum.EnumItem3}"> <Setter Property="MenuItem.IsChecked" Value="True"/> </DataTrigger> </Style.Triggers> </Style> </MenuItem.Style> </MenuItem> </MenuItem> </Menu> </DockPanel> </DockPanel> </Window>
MainWindow.xaml.cs:
using System.Windows; using System.Windows.Input; using System.ComponentModel; namespace MutuallyExclusiveMenuItems { public partial class MainWindow : Window, INotifyPropertyChanged { public MainWindow() { InitializeComponent(); DataContext = this; } #region Enum Property public enum CurrentItemEnum { EnumItem1, EnumItem2, EnumItem3 }; private CurrentItemEnum _currentMenuItem; public CurrentItemEnum CurrentMenuItem { get { return _currentMenuItem; } set { _currentMenuItem = value; OnPropertyChanged("CurrentMenuItem"); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } #endregion Enum Property #region Commands public static RoutedUICommand MenuItem1Cmd = new RoutedUICommand("Item_1", "Item1cmd", typeof(MainWindow)); public void MenuItem1Execute(object sender, ExecutedRoutedEventArgs e) { CurrentMenuItem = CurrentItemEnum.EnumItem1; } public static RoutedUICommand MenuItem2Cmd = new RoutedUICommand("Item_2", "Item2cmd", typeof(MainWindow)); public void MenuItem2Execute(object sender, ExecutedRoutedEventArgs e) { CurrentMenuItem = CurrentItemEnum.EnumItem2; } public static RoutedUICommand MenuItem3Cmd = new RoutedUICommand("Item_3", "Item3cmd", typeof(MainWindow)); public void MenuItem3Execute(object sender, ExecutedRoutedEventArgs e) { CurrentMenuItem = CurrentItemEnum.EnumItem3; } public void CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } #endregion Commands } }
这里还有另一种方法 – 不容易,但它是MVVM兼容的,可绑定的,并且可以高度unit testing。 如果您可以自由地将Converter添加到您的项目中,并且每次打开上下文菜单时都不介意以新项目列表的forms呈现一些小垃圾,这种方式非常有效。 它满足了如何在上下文菜单中提供相互排斥的一组项目的原始问题。
我想如果你想把所有这些都提取到一个用户控件中,你可以把它变成一个可重用的库组件,以便在应用程序中重用。 使用的组件是Type3.Xaml,带有一个简单的网格,一个文本块和上下文菜单。 用鼠标右键单击网格中的任何位置即可显示菜单。
名为AllValuesEqualToBooleanConverter的值转换器用于将每个菜单项的值与组的当前值进行比较,并在当前所选菜单项旁边显示复选标记。
一个代表菜单select的简单类用于说明。 示例容器使用带有string和整数属性的元组,使得与人机可读的文本紧密结合的可读代码片段相当容易。 您可以单独使用string,也可以使用string和枚举来跟踪当前值的决定。 Type3VM.cs是分配给Type3.Xaml的DataContext的ViewModel。 但是,您可以设法在现有的应用程序框架中分配数据上下文,这里使用相同的机制。 正在使用的应用程序框架依赖于INotifyPropertyChanged将更改的值传递给WPF及其绑定的goo。 如果你有依赖属性,你可能需要稍微调整一下代码。
除了转换器及其长度,这个实现的缺点是每次打开上下文菜单时都会创build一个垃圾列表。 对于单用户应用程序,这可能是好的,但你应该知道这一点。
该应用程序使用了一个RelayCommand实现,该实现可以从Haacked网站或任何其他可用的ICommand兼容辅助类中随时获得。
公共类Type3VM:INotifyPropertyChanged {private List menuData = new List(new [] {new MenuData(“Zero”,0),new MenuData(“One”,1),new MenuData(“Two”,2),new MenuData “三”,3),});
public IEnumerable<MenuData> MenuData { get { return menuData.ToList(); } } private int selected; public int Selected { get { return selected; } set { selected = value; OnPropertyChanged(); } } private ICommand contextMenuClickedCommand; public ICommand ContextMenuClickedCommand { get { return contextMenuClickedCommand; } } private void ContextMenuClickedAction(object clicked) { var data = clicked as MenuData; Selected = data.Item2; OnPropertyChanged("MenuData"); } public Type3VM() { contextMenuClickedCommand = new RelayCommand(ContextMenuClickedAction); } private void OnPropertyChanged([CallerMemberName]string propertyName = null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; } public class MenuData : Tuple<String, int> { public MenuData(String DisplayValue, int value) : base(DisplayValue, value) { } }
<UserControl x:Class =“SampleApp.Views.Type3”xmlns =“http://schemas.microsoft.com/winfx/2006/xaml/presentation”xmlns:x =“http://schemas.microsoft.com/winfx / 2006 / xaml“xmlns:mc =”http://schemas.openxmlformats.org/markup-compatibility/2006“xmlns:d =”http://schemas.microsoft.com/expression/blend/2008“xmlns:Views =“clr-namespace:SampleApp.Views”xmlns:Converters =“clr-namespace:SampleApp.Converters”xmlns:ViewModels =“clr-namespace:SampleApp.ViewModels”mc:Ignorable =“d”d:DesignHeight =“300” d:DesignWidth =“300”d:DataContext =“{d:DesignInstance ViewModels:Type3VM}”> <UserControl.Resources> <Converters:AllValuesEqualToBooleanConverter x:Key =“IsCheckedVisibilityConverter”EqualValue =“True”NotEqualValue =“False”/> </UserControl.Resources> <Grid> <Grid.ContextMenu> <ContextMenu ItemsSource =“{Binding MenuData,Mode = OneWay}”> <ContextMenu.ItemContainerStyle> <Style TargetType =“MenuItem”> <Setter Property =“Header”Value =“{Binding Item1}”/> <Setter Property =“IsCheckable”Value =“True”/> <Setter Prop erty =“IsChecked”> <Setter.Value> <MultiBinding Converter =“{StaticResource IsCheckedVisibilityConverter}”Mode =“OneWay”> <Binding Path =“DataContext.Selected”RelativeSource =“{RelativeSource FindAncestor,AncestorType = {x:Type Views :Type3}}“/> <Binding Path =”Item2“/> </ MultiBinding> </Setter.Value> </ Setter> <Setter Property =”Command“Value =”{Binding Path = DataContext.ContextMenuClickedCommand,RelativeSource = {RelativeSource FindAncestor,AncestorType = {x:Type Views:Type3}}}“/> <Setter Property =”CommandParameter“Value =”{Binding。}“/> </ Style> </ContextMenu.ItemContainerStyle> </ ContextMenu> </Grid.ContextMenu> <Grid.RowDefinitions> <RowDefinition Height =“ ”/> </ Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width =“ ”/> </ Grid.ColumnDefinitions> <TextBlock Grid.Row = “0”Grid.Column =“0”FontSize =“30”Text =“右击菜单”/> </ Grid> </ UserControl>
public class AreAllValuesEqualConverter<T> : IMultiValueConverter { public T EqualValue { get; set; } public T NotEqualValue { get; set; } public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { T returnValue; if (values.Length < 2) { returnValue = EqualValue; } // Need to use .Equals() instead of == so that string comparison works, but must check for null first. else if (values[0] == null) { returnValue = (values.All(v => v == null)) ? EqualValue : NotEqualValue; } else { returnValue = (values.All(v => values[0].Equals(v))) ? EqualValue : NotEqualValue; } return returnValue; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } [ValueConversion(typeof(object), typeof(Boolean))] public class AllValuesEqualToBooleanConverter : AreAllValuesEqualConverter<Boolean> { }