从枚举值列表

时间:2015-08-27 17:49:09

标签: c# wpf xaml enums menuitem

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

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

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

我尝试过很多方法而没有成功。我的每个试验总是错过了一些东西,但主要是看起来主要的缺失部分是转换器参数,它可以绑定到某些东西。

我红了:

这是我的许多试验和相关代码:

<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绑定根本不起作用。它从未收到任何价值

<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的listBox但是UI不是它应该的标准(显示另一个框架)。

3 个答案:

答案 0 :(得分:10)

所以你希望能够

  • Enum绑定到ContextMenu并显示Description属性
  • 在所选Enum前面有一个复选标记,任何时候只有一个可以“有效”
  • 将选定的值存储在ViewModel&amp;选择更改时执行一些逻辑

如下所示?

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

答案 1 :(得分:3)

我添加我的解决方案作为参考。两种解决方案(接受答案和我的工作正常)。在此期间我创建了一个我正在等待有效的完整答案。我认为Mikko有更标准的工作方式,应该更容易维护。 Mikko解决方案也显示了很少的WPF技巧(Relaycommand,MultiBinding,...)。

我的解决方案的主要优点是提取&#34;复杂性&#34;通过使用通用代码模拟表示每个枚举值及其属性的项的集合(IsChecked,Name,DisplayName)。所有这些都是隐藏的,不需要模型中的任何内容。 但无论如何,就像额外的信息......

<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"
        Title="MainWindow" Height="350" Width="525"
        Name="MyWindow">
    <Window.DataContext>
        <wpfContextMenuWithEnum:MainWindowModel></wpfContextMenuWithEnum:MainWindowModel>
    </Window.DataContext>

    <Window.Resources>
        <wpfContextMenuWithEnum:EnumWrapperIteratorAndSelector x:Key="EnumWrapperIteratorAndSelector" 
                                                               Enum="{Binding DataContext.SelectedEnumChoice, Mode=TwoWay, ElementName=MyWindow}" />
    </Window.Resources>

    <Grid>
        <TextBox Text="Right click me">
            <TextBox.ContextMenu>
                <ContextMenu ItemsSource="{Binding Source={StaticResource EnumWrapperIteratorAndSelector}}">
                    <ContextMenu.ItemTemplate>
                        <DataTemplate>
                            <MenuItem IsCheckable="True" Header="{Binding DisplayName}" IsChecked="{Binding IsChecked}">
                            </MenuItem>
                        </DataTemplate>
                    </ContextMenu.ItemTemplate>
                </ContextMenu>
            </TextBox.ContextMenu>
        </TextBox>
    </Grid>
</Window>

可在任何地方使用的通用类:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Reflection;
    using System.Windows;

    namespace WpfContextMenuWithEnum
    {
        /// <summary>
        /// Note: Freezable is necessary otherwise binding will never occurs if EnumWrapperIteratorAndSelector is defined
        /// as resources. See article for more info: 
        /// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
        ///  </summary>
        public class EnumWrapperIteratorAndSelector : Freezable, IEnumerable<EnumWrapperIteratorAndSelectorChoice>, INotifyCollectionChanged
        {
            // ******************************************************************
            public static readonly DependencyProperty EnumProperty =
                DependencyProperty.Register("Enum", typeof(Enum), typeof(EnumWrapperIteratorAndSelector), new PropertyMetadata(null, PropertyChangedCallback));

            ObservableCollection<EnumWrapperIteratorAndSelectorChoice> _allEnumValue = new ObservableCollection<EnumWrapperIteratorAndSelectorChoice>();

            // ******************************************************************
            private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
            {
                if (!(dependencyPropertyChangedEventArgs.NewValue is Enum))
                {
                    throw new ArgumentException("Only enum are supported.");
                }

                var me = dependencyObject as EnumWrapperIteratorAndSelector;
                if (me != null)
                {
                    if (dependencyPropertyChangedEventArgs.OldValue == null)
                    {
                        me.ResetWithNewEnum(dependencyPropertyChangedEventArgs.NewValue);
                    }
                    else
                    {
                        foreach(EnumWrapperIteratorAndSelectorChoice enumWrapperIteratorAndSelectorChoice in me._allEnumValue)
                        {
                            enumWrapperIteratorAndSelectorChoice.RaiseChangeIfAppropriate(dependencyPropertyChangedEventArgs);
                        }
                    }
                }
            }

            // ******************************************************************
            private void ResetWithNewEnum(object enumValue)
            {
                _allEnumValue.Clear();

                var enumType = Enum.GetType();
                foreach (Enum enumValueIter in Enum.GetValues(enumValue.GetType()))
                {
                    MemberInfo[] memberInfos = enumType.GetMember(enumValueIter.ToString());
                    if (memberInfos.Length > 0)
                    {
                        var desc = memberInfos[0].GetCustomAttribute<DescriptionAttribute>();
                        if (desc != null)
                        {
                            _allEnumValue.Add(new EnumWrapperIteratorAndSelectorChoice(this, enumValueIter, desc.Description));
                        }
                        else
                        {
                            _allEnumValue.Add(new EnumWrapperIteratorAndSelectorChoice(this, enumValueIter));
                        }
                    }
                }
            }

            // ******************************************************************
            public Enum Enum
            {
                get { return (Enum)GetValue(EnumProperty); }
                set
                {
                    SetValue(EnumProperty, value);
                }
            }

            // ******************************************************************
            internal void SetCurrentValue(Enum enumValue)
            {
                SetCurrentValue(EnumProperty, enumValue);
            }

            // ******************************************************************
            public IEnumerator GetEnumerator()
            {
                return _allEnumValue.GetEnumerator();
            }

            // ******************************************************************
            IEnumerator<EnumWrapperIteratorAndSelectorChoice> IEnumerable<EnumWrapperIteratorAndSelectorChoice>.GetEnumerator()
            {
                return _allEnumValue.GetEnumerator();
            }

            // ******************************************************************
            public event NotifyCollectionChangedEventHandler CollectionChanged
            {
                add { _allEnumValue.CollectionChanged += value; }
                remove { _allEnumValue.CollectionChanged -= value; }
            }

            // ******************************************************************
            protected override Freezable CreateInstanceCore()
            {
                return new EnumWrapperIteratorAndSelector();
            }

            // ******************************************************************

        }
    }

    using System;
    using System.ComponentModel;
    using System.Windows;

    namespace WpfContextMenuWithEnum
    {
        public class EnumWrapperIteratorAndSelectorChoice : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            private EnumWrapperIteratorAndSelector _enumWrapperIteratorAndSelector;
            public Enum EnumValueRef { get; private set; }
            public string Name { get; set; }
            public string Description { get; set; }

            public bool IsChecked
            {
                get
                {
                    return _enumWrapperIteratorAndSelector.Enum.Equals(EnumValueRef);
                }

                set
                {
                    if (value) // Can only set value
                    {
                        _enumWrapperIteratorAndSelector.SetCurrentValue(EnumValueRef);
                    }
                }
            }

            internal void RaiseChangeIfAppropriate(DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
            {
                if (EnumValueRef.Equals(dependencyPropertyChangedEventArgs.OldValue) ||
                    EnumValueRef.Equals(dependencyPropertyChangedEventArgs.NewValue))
                {
                    var propertyChangeLocal = PropertyChanged;
                    if (propertyChangeLocal != null)
                    {
                        propertyChangeLocal(this, new PropertyChangedEventArgs("IsChecked"));
                    }
                }
            }

            public EnumWrapperIteratorAndSelectorChoice(EnumWrapperIteratorAndSelector enumWrapperIteratorAndSelector,
                Enum enumValueRef, string description = null)
            {
                _enumWrapperIteratorAndSelector = enumWrapperIteratorAndSelector;
                EnumValueRef = enumValueRef;
                Name = enumValueRef.ToString();
                Description = description;
            }

            public string DisplayName
            {
                get { return Description ?? Name; }
            }
        }
    }

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;

namespace WpfContextMenuWithEnum
{
    public class MainWindowModel : ViewModelBase
    {
        private EnumChoice _selectedEnumChoice;

        public EnumChoice SelectedEnumChoice
        {
            get { return _selectedEnumChoice; }
            set { _selectedEnumChoice = value; RaisePropertyChanged(); }
        }
    }
}

答案 2 :(得分:0)

补充其余的答案:您可以通过设置 ItemContainerStyle 而不是 ItemTemplate 来摆脱“菜单中的菜单”样式问题:

DataTemplate

<MenuItem.ItemTemplate>
    <DataTemplate>
        <MenuItem IsCheckable="True"
                  Header="{Binding DisplayName}"
                  IsChecked="{Binding IsChecked}">
        </MenuItem>
    </DataTemplate>
</MenuItem.ItemTemplate>

ItemContainerStyle

<MenuItem.ItemContainerStyle>
    <Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
        <Setter Property="IsCheckable" Value="True" />
        <Setter Property="Header" Value="{Binding DisplayName}" />
        <Setter Property="IsChecked" Value="{Binding IsChecked}" />
    </Style>
</MenuItem.ItemContainerStyle>