【问题标题】:WPF ComboBox/ListBox with MultiSelect based on Enum with FlagsWPF ComboBox/ListBox 与 MultiSelect 基于带有标志的枚举
【发布时间】:2010-12-05 13:12:20
【问题描述】:

所以我可能会稍微突破界限......

基本上我有以下枚举,用 C# 代码声明:

[Flags]
public enum FlaggedEnum : int
{
    Option1 = 1,
    Option2 = 2,
    Option3 = 4,
    Option4 = 8,
    ...
    Option16 = 32768,
    None = 0
}

此枚举是我已成功绑定到 DataGrid 对象的对象的成员。成功意味着我已成功绑定所有其他字段。 :)

我想要在这里实现的是一个控件,其中检查了上述所有适当的选项,它的行为和行为就像一个组合框/列表框。因此,您单击该字段,会弹出一个下拉菜单,可以“检查”所需的任何选项。

控件还必须能够读取枚举并写入枚举。

我是 WPF 新手,所以除了创建 ComboBox 并绑定到列之外,我不知道去哪里...任何帮助将不胜感激!

【问题讨论】:

    标签: c# datagrid enums wpftoolkit flags


    【解决方案1】:

    我创建了一个 IValueConverter,它支持直接绑定到枚举,无需代码隐藏或辅助类。它只有两个限制:

    • 必须为每个源属性使用一个转换器实例。如果模型包含更多相同枚举类型的属性,则每个属性都需要使用单独的转换器实例。这可以通过在 XAML 中实例化转换器来完成。
    • 当枚举类型是标志枚举时,它必须是整数。

    该解决方案基于经验事实,即在 ConvertBack 之前始终先有一个 Convert。如果 ConvertBack 更改了值,则始终存在 Convert。这仅在模型上正确实施 INotifyPropertyChanged 时才有效。因此,在两次调用之间,最后一个已知值可以存储在转换器中并在 ConvertBack 方法中使用。

    转换器实例应该获取枚举的类型以及它是否是一个标志枚举。

     <EnumToCheckedConverter x:Key="InstanceName" Type="{x:Type MyEnum}" Flags="True" />
    

    然后可以使用此转换器绑定复选框。

     <CheckBox Content="ValueText" IsChecked="{Binding Source, Converter={StaticResource InstanceName}, ConverterParameter=Value}"/>
    

    单选按钮可以通过相同的机制使用 Flags="False" 的实例进行绑定

    转换器的源代码

    public class EnumToCheckedConverter : IValueConverter
    {
        public Type Type { get; set; }
        public int? LastValue { get; private set; }
        public bool Flags { get; set; }
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value != null && value.GetType() == Type)
            {
                try
                {
                    var parameterValue = Enum.Parse(Type, parameter as string);
    
                    if (Flags == true)
                    {
                        var intParameter = (int)parameterValue;
                        var intValue = (int)value;
                        LastValue = intValue;
    
                        return (intValue & intParameter) == intParameter;
                    }
                    else
                    {
                        return Equals(parameterValue, value);
                    }
                }
                catch (ArgumentNullException)
                {
                    return false;
                }
                catch (ArgumentException)
                {
                    throw new NotSupportedException();
                }
            }
            else if (value == null)
            {
                return false;
            }
    
            throw new NotSupportedException();
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value != null && value is bool check)
            {
                if (check)
                {
                    try
                    {
                        if (Flags == true && LastValue.HasValue)
                        {
                            var parameterValue = Enum.Parse(Type, parameter as string);
                            var intParameter = (int)parameterValue;
    
                            return Enum.ToObject(Type, LastValue | intParameter);
                        }
                        else
                        {
                            return Enum.Parse(Type, parameter as string);
                        }
                    }
                    catch (ArgumentNullException)
                    {
                        return Binding.DoNothing;
                    }
                    catch (ArgumentException)
                    {
                        return Binding.DoNothing;
                    }
                }
                else
                {
                    try
                    {
                        if (Flags == true && LastValue.HasValue)
                        {
                            var parameterValue = Enum.Parse(Type, parameter as string);
                            var intParameter = (int)parameterValue;
    
                            return Enum.ToObject(Type, LastValue ^ intParameter);
                        }
                        else
                        {
                            return Binding.DoNothing;
                        }
                    }
                    catch (ArgumentNullException)
                    {
                        return Binding.DoNothing;
                    }
                    catch (ArgumentException)
                    {
                        return Binding.DoNothing;
                    }
                }
            }
    
            throw new NotSupportedException();
        }
    }
    

    【讨论】:

      【解决方案2】:

      我有一个可行的方法。我对此不以为然-我在网上找到了这种方法,但忘记了保存地址。

      在我的项目中,我需要将一些复选框绑定到标志枚举。为了提供帮助,我找到了一个简单的值转换器的实现,以促进两种方式的绑定。它不是通用的,转换器的单个实例一次只能与一个目标(即一个值的一个实例及其一组复选框)一起使用。转换器使用对该值的存储引用作为转换回来的一种方式,因此如果您尝试在单独的对象实例之间重用它,它将不起作用。也就是说,这是我对这种东西的唯一用途,它就像一个魅力。

      转换器:

      /// <summary>
      /// Provides for two way binding between a TestErrors Flag Enum property and a boolean value.
      /// TODO: make this more generic and add it to the converter dictionary if possible
      /// </summary>
      public class TestActionFlagValueConverter : IValueConverter {
          private TestErrors target;
      
          public TestActionFlagValueConverter() {
      
          }
      
          public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
              TestErrors mask = (TestErrors)parameter;
              this.target = (TestErrors)value;
              return ((mask & this.target) != 0);
          }
      
          public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
              this.target ^= (TestErrors)parameter;
              return this.target;
          }
      }
      

      在 xaml 中是这样使用的:

      <StackPanel.Resources>
          <local:TestActionFlagValueConverter x:Key="TestActionFlagValueConverter"/>
      </StackPanel.Resources>
      
      <CheckBox IsChecked="{Binding Errors, Converter={StaticResource TestActionFlagValueConverter}, ConverterParameter={x:Static local:TestErrors.PowerFailure}...
      <CheckBox IsChecked="{Binding Errors, Converter={StaticResource TestActionFlagValueConverter}, ConverterParameter={x:Static local:TestErrors.OpenCondition}...
      

      在您的情况下,您可以将其放入数据单元模板中(尽管显然您可能更喜欢使用组合框而不是简单的堆栈面板。确保在您的复选框组容器附近实例化转换器以确保它们有自己的实例转换器。

      编辑:

      在这里,我做了一个小测试项目来演示在带有数据网格的组合框中使用它,它基于默认的 WPF 应用程序 - 只需确保引用 WPF 工具包。

      这是 Window1.xaml 文件:

      <Window 
          x:Class="FlagEnumTest.Window1"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
          xmlns:FlagEnumTest="clr-namespace:FlagEnumTest"
          Title="Window1" Height="300" Width="300">
      
          <Window.Resources>
              <x:Array Type="{x:Type FlagEnumTest:TestObject}" x:Key="TestArray">
                  <FlagEnumTest:TestObject Errors="OpenCondition" />
                  <FlagEnumTest:TestObject />
              </x:Array>
          </Window.Resources>
      
          <StackPanel>
      
              <Controls:DataGrid ItemsSource="{Binding Source={StaticResource TestArray}}">
                  <Controls:DataGrid.Columns>
                      <Controls:DataGridTemplateColumn Header="Errors">
                          <Controls:DataGridTemplateColumn.CellTemplate>
                              <DataTemplate>
                                  <ComboBox>
                                      <ComboBox.Resources>
                                          <FlagEnumTest:TestErrorConverter x:Key="ErrorConverter" />
                                      </ComboBox.Resources>
                                      <CheckBox Content="PowerFailure" IsChecked="{Binding Path=Errors, Converter={StaticResource ErrorConverter}, ConverterParameter={x:Static FlagEnumTest:TestErrors.PowerFailure}}" />
                                      <CheckBox Content="OpenCondition" IsChecked="{Binding Path=Errors, Converter={StaticResource ErrorConverter}, ConverterParameter={x:Static FlagEnumTest:TestErrors.OpenCondition}}" />
                                  </ComboBox>
                              </DataTemplate>
                          </Controls:DataGridTemplateColumn.CellTemplate>
                      </Controls:DataGridTemplateColumn>
                  </Controls:DataGrid.Columns>
              </Controls:DataGrid>
      
          </StackPanel>
      </Window>
      

      这里是 Window1.xaml.cs 文件代码隐藏。

      using System;
      using System.Globalization;
      using System.Windows;
      using System.Windows.Data;
      
      namespace FlagEnumTest {
          /// <summary>
          /// Interaction logic for Window1.xaml
          /// </summary>
          public partial class Window1 : Window {
              public Window1() {
                  InitializeComponent();
              }
          }
      
          [Flags]
          public enum TestErrors {
              NoError = 0x0,
              PowerFailure = 0x1,
              OpenCondition = 0x2,
          }
      
          public class TestObject {
              public TestErrors Errors { get; set; }
          } 
      
          /// <summary>
          /// Provides for two way binding between a TestErrors Flag Enum property and a boolean value.
          /// TODO: make this more generic and add it to the converter dictionary if possible
          /// </summary>
          public class TestErrorConverter : IValueConverter {
              private TestErrors target;
      
              public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
                  TestErrors mask = (TestErrors)parameter;
                  this.target = (TestErrors)value;
                  return ((mask & this.target) != 0);
              }
      
              public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
                  this.target ^= (TestErrors)parameter;
                  return this.target;
              }
          }
      
      }
      

      默认情况下,数据网格将创建自己的列表示以及我的强制模板表示,因此您可以看到文本表示以及复选框之一。标志枚举混淆了默认文本表示,但您仍然可以看到绑定正常工作(检查两者,然后取消选中您最后检查的那个 - 字符串值更改为另一个,而不是 0)。

      【讨论】:

      • 干杯,看起来它会完美运行!我会继续尝试在我的应用程序中实现它。
      • Egor,我能够让它完美运行。我想知道当复选框状态已更改时,是否可以向 DataGrid 发出已修改行的信号?现在我依靠绑定数据上的 IEditableInterface 实现将更新写入后端数据库。但是,编辑复选框不会触发此行为。
      • 另外,是否可以更改组合框中显示的值?我注意到属性 SelectionBoxItem 但它似乎是只读的。
      • 我相信组合框中显示的值是当前选中的项目(在这种情况下,是复选框的集合)。带有标题和扩展器的模板可能会更好地为您服务(您可以将其模板化为看起来像一个组合框) - 这样您就不必担心所选项目的语义,并且可以为要显示为“当前”值的标志属性。
      • 至于您的其他问题 - 您必须确保以正确的顺序触发正确的事件。如果您有权访问源,请在相关的事件调用方法中放置一些断点,并查看在您编辑其他属性之一时会发生什么。该界面中只有三个事件,您必须确保以某种方式单击复选框会触发它们。我对 IEditable 几乎一无所知,抱歉。
      猜你喜欢
      • 2013-08-25
      • 1970-01-01
      • 1970-01-01
      • 2011-07-03
      • 1970-01-01
      • 2012-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多