c# - 从枚举值列表创建可检查上下文菜单的通用方法

标签 c# wpf xaml enums menuitem

我想创建一个上下文菜单,其中一个 menuItem 将是一个可以在枚举值中进行选择的子菜单。

我不想将枚举中的任何值硬编码到 xaml 中,因为我希望任何枚举值更改都会自动反射(reflect)在 UI 中,而无需任何干预。

我希望我的菜单是一个没有任何伪影的常规上下文菜单(我的意思是外观应该与常规 ContextMenu 一样)。

我尝试了很多方法都没有成功。我的每个试验总是遗漏一些东西,但主要遗漏的部分似乎是一个可以绑定(bind)到某些东西的 converterParamter。

我是红色的:

这是我多次试验及相关代码:

<Window x:Class="WpfContextMenuWithEnum.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpfContextMenuWithEnum="clr-namespace:WpfContextMenuWithEnum"
        xmlns:system="clr-namespace:System;assembly=mscorlib"
        xmlns:converter="clr-namespace:WpfContextMenuWithEnum.Converter"
        Title="MainWindow" Height="350" Width="525"
        Name="MyWindow">
    <Window.DataContext>
        <wpfContextMenuWithEnum:MainWindowModel></wpfContextMenuWithEnum:MainWindowModel>
    </Window.DataContext>

    <Window.Resources>
        <ObjectDataProvider x:Key="EnumChoiceProvider" MethodName="GetValues" ObjectType="{x:Type system:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="wpfContextMenuWithEnum:EnumChoice"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>

        <converter:EnumToBooleanConverter x:Key="EnumToBooleanConverter"></converter:EnumToBooleanConverter>
        <converter:MultiBind2ValueComparerConverter x:Key="MultiBind2ValueComparerConverter"></converter:MultiBind2ValueComparerConverter>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>

        <TextBox Text="Right click me">
            <TextBox.ContextMenu>
                <ContextMenu ItemsSource="{Binding Source={StaticResource EnumChoiceProvider}}">
                    <ContextMenu.ItemTemplate>
                        <DataTemplate>
                            <MenuItem IsCheckable="True" Header="{Binding Path=.}">
                                <MenuItem.IsChecked>
                                    <MultiBinding Converter="{StaticResource MultiBind2ValueComparerConverter}">
                                        <Binding Path="DataContext.ModelEnumChoice" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}" />
                                        <Binding Path="." Mode="OneWay"></Binding>
                                    </MultiBinding>
                                </MenuItem.IsChecked>
                            </MenuItem>
                        </DataTemplate>
                    </ContextMenu.ItemTemplate>
                </ContextMenu>
            </TextBox.ContextMenu>
        </TextBox>
    </Grid>
</Window>

枚举:

using System.ComponentModel;

    namespace WpfContextMenuWithEnum
    {
        public enum EnumChoice
        {
            [Description("Default")]
            ChoiceDefault = 0, // easier if the default have value = 0

            [Description("<1>")]
            Choice1 = 1,

            [Description("<2>")]
            Choice2 = 2,
        }
    }

转换器:

using System;
using System.Windows;
using System.Windows.Data;

namespace WpfContextMenuWithEnum.Converter
{
    public class ConverterWrapperWithDependencyParameterConverter : DependencyObject, IValueConverter
    {
        public static readonly DependencyProperty ParameterProperty = DependencyProperty.Register("Parameter",
            typeof(object), typeof(ConverterWrapperWithDependencyParameterConverter));

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (parameter != null)
            {
                throw new ArgumentException("The parameter should be set directly as a property not into the Binding object.");
            }

            return Converter.Convert(value, targetType, Parameter, culture);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (parameter != null)
            {
                throw new ArgumentException("The parameter should be set directly as a property not into the Binding object.");
            }

            return Converter.ConvertBack(value, targetType, Parameter, culture);
        }

        public object Parameter
        {
            get { return GetValue(ParameterProperty); }
            set { SetValue(ParameterProperty, value); }
        }

        public IValueConverter Converter { get; set; }
    }
}





using System;
using System.Windows.Data;

namespace WpfContextMenuWithEnum.Converter
{
    public class EnumToBooleanConverter : IValueConverter
    {
        // **********************************************************************
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value.Equals(parameter);
        }

        // **********************************************************************
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value.Equals(true) ? parameter : Binding.DoNothing;
        }

        // **********************************************************************
    }

}




   using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Data;

    namespace WpfContextMenuWithEnum.Converter
    {
        public class MultiBind2ValueComparerConverter : IMultiValueConverter
        {
            public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (values.Length != 2)
                {
                    throw new ArgumentException("Can compare only 2 values together fo equality");
                }

                return (values[0].Equals(values[1]));
            }

            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
            {
                // if ((bool)value == true)
                throw new NotImplementedException();
            }
        }
    }

试验 1:MultiBindConverter ConvertBack 无法工作,它会丢失信息。

<ContextMenu ItemsSource="{Binding Source={StaticResource EnumChoiceProvider}}">
            <ContextMenu.ItemTemplate>
                <DataTemplate>
                    <MenuItem IsCheckable="True" Header="{Binding Path=.}">
                        <MenuItem.IsChecked>
                            <MultiBinding Converter="{StaticResource MultiBind2ValueComparerConverter}">
                                <Binding Path="DataContext.ModelEnumChoice" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}" />
                                <Binding Path="."></Binding>
                            </MultiBinding>
                        </MenuItem.IsChecked>
                    </MenuItem>
                </DataTemplate>
            </ContextMenu.ItemTemplate>
        </ContextMenu>

试验 2:我的 ConverterParameter 绑定(bind)根本不起作用。它从未收到任何值(value)

<ContextMenu ItemsSource="{Binding Source={StaticResource EnumChoiceProvider}}">
                    <ContextMenu.ItemTemplate>
                        <DataTemplate>
                            <MenuItem IsCheckable="True" Header="{Binding Path=.}">
                                <MenuItem.IsChecked>
                                    <Binding Path="DataContext.ModelEnumChoice" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}">
                                        <Binding.Converter>
                                            <converter:ConverterWrapperWithDependencyParameterConverter Converter="{StaticResource EnumToBooleanConverter}"
                                                Parameter="{Binding Path=.}"/>
                                        </Binding.Converter>
                                    </Binding>
                                </MenuItem.IsChecked>
                            </MenuItem>
                        </DataTemplate>
                    </ContextMenu.ItemTemplate>
                </ContextMenu>

试验 3:

使用模板和 SelectedItem 的列表框,但 UI 不如应有的标准(出现额外的框架)。

最佳答案

所以你希望能够

  • 将任何 Enum 绑定(bind)到 ContextMenu 并显示它的 Description 属性
  • 在选定的Enum前打勾,在任何给定时间只有一个可以“激活”
  • 将所选值存储在 ViewModel 中并在选择更改时执行一些逻辑

像下面这样的东西?

imgur


MainWindow.xaml

<Window x:Class="WpfApplication1.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow"
        Height="300"
        Width="250">

    <!-- Set data context -->        
    <Window.DataContext>
      <viewModel:MainViewModel />
    </Window.DataContext>

    <!-- Converters -->
    <Window.Resources>
      <local:EnumDescriptionConverter x:Key="EnumDescriptionConverter" />
      <local:EnumCheckedConverter x:Key="EnumCheckedConverter" />
    </Window.Resources>

    <!-- Element -->    
    <TextBox Text="Right click me">
      <!-- Context menu -->
      <TextBox.ContextMenu>
        <ContextMenu ItemsSource="{Binding EnumChoiceProvider}">
          <ContextMenu.ItemTemplate>
            <DataTemplate>
              <!-- Menu item header bound to enum converter -->
              <!-- IsChecked bound to current selection -->
              <!-- Toggle bound to a command, setting current selection -->
              <MenuItem 
                IsCheckable="True"
                Width="150"
                Header="{Binding Path=., Converter={StaticResource EnumDescriptionConverter}}"
                Command="{Binding DataContext.ToggleEnumChoiceCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
                CommandParameter="{Binding}">
                <MenuItem.IsChecked>
                  <MultiBinding Mode="OneWay" 
                                NotifyOnSourceUpdated="True" 
                                UpdateSourceTrigger="PropertyChanged" 
                                Converter="{StaticResource EnumCheckedConverter}">
                    <Binding Path="DataContext.SelectedEnumChoice" 
                             RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}"  />
                    <Binding Path="."></Binding>
                  </MultiBinding>
                </MenuItem.IsChecked>    
              </MenuItem>
            </DataTemplate>
          </ContextMenu.ItemTemplate>
        </ContextMenu>
      </TextBox.ContextMenu>
    </TextBox>
</Window>

MainViewModel.cs

namespace WpfApplication1.ViewModel
{
    public class MainViewModel : ViewModelBase // where base implements INotifyPropertyChanged
    {
        private EnumChoice? _selectedEnumChoice;

        public MainViewModel()
        {
            EnumChoiceProvider = new ObservableCollection<EnumChoice>
                (Enum.GetValues(typeof(EnumChoice)).Cast<EnumChoice>());

            ToggleEnumChoiceCommand = new RelayCommand<EnumChoice>
                (arg => SelectedEnumChoice = arg);
        }

        // Selections    
        public ObservableCollection<EnumChoice> EnumChoiceProvider { get; set; }

        // Current selection    
        public EnumChoice? SelectedEnumChoice
        {
            get
            {
                return _selectedEnumChoice;
            }
            set
            {
                _selectedEnumChoice = value != _selectedEnumChoice ? value : null;
                RaisePropertyChanged();
            }
        }

        // "Selection changed" command    
        public ICommand ToggleEnumChoiceCommand { get; private set; }
    }
}

EnumChoice.cs

namespace WpfApplication1
{
    public enum EnumChoice
    {
        [Description("Default")]
        ChoiceDefault,
        [Description("<1>")]
        Choice1,
        [Description("<2>")]
        Choice2
    }
}

EnumDescriptionConverter.cs

namespace WpfApplication1
{
    // Extract enum description 
    public class EnumDescriptionConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            MemberInfo[] memberInfos = value.GetType().GetMember(value.ToString());

            if (memberInfos.Length > 0)
            {
                object[] attrs = memberInfos[0].GetCustomAttributes(typeof (DescriptionAttribute), false);
                if (attrs.Length > 0)
                    return ((DescriptionAttribute) attrs[0]).Description;
            }

            return value;

            // or maybe just
            //throw new InvalidEnumArgumentException(string.Format("no description found for enum {0}", value));
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

EnumCheckedConverter.cs

namespace WpfApplication1
{
    // Check if currently selected 
    public class EnumCheckedConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return !values.Contains(null) && values[0].ToString().Equals(values[1].ToString(), StringComparison.OrdinalIgnoreCase);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

关于c# - 从枚举值列表创建可检查上下文菜单的通用方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32256222/

相关文章:

c# - Blazor:IServiceCollection 不包含 AddDefaultIdentity 的定义

c# - XMLSerializer - 无参数构造

c# - 如果 c# 不工作,则使用测试字符串类型的对象属性

c# - 在单独的 dll WPF 中设置资源

wpf - 附加属性

c# - 如何在 ConfigureServices 中获取 Development/Staging/production Hosting Environment

c# - 已添加具有相同 key 的项目。在 C# 中反序列化对象时

xaml - Xamarin 在 Xaml 中继承 ContentPage

c# - 如何在 XAML 中分配 Control 类型的属性 - 错误

c# - 如何更新 MainWindow 中 StatusBarItem 中的 WPF UserControl 属性形成另一个具有不同 DataContexts 的 UserControl?