【问题标题】:Bind Flag enums to listbox containing checkboxes将标志枚举绑定到包含复选框的列表框
【发布时间】:2012-04-21 03:47:06
【问题描述】:

我有标志枚举这样说 -

[Flags]
public enum Department
{
    None = 0,
    A = 1,
    B = 2,
    C = 4,
    D = 8
}

我想在视图中显示此枚举的值。我想创建一个列表框并将其源绑定到此枚举List<Department> Departments 的集合。 一切都很好,直到我想到有一个绑定到我的 Viewmodel 上的属性的复选框 -

public Department SelectedDepartments { get; set; }

http://compilewith.net/2008/12/wpf-flagsenumvalueconverter.html 的解决方案为将枚举值绑定到复选框提供了优雅的解决方案,但它有一个限制,即创建的复选框等于列表中枚举值的数量。 但是,在我的情况下,我无法在 UI 上放置这么多复选框,因为我的 Enum 包含 20 个值(这意味着 UI 上有 20 个复选框)。

我尝试使用MultiBindingConverter,但在ConvertBack 方法中失败。 我想将复选框的状态与属性 SelectedDepartments 绑定。假设属性值为“A | B”,则应选中 A 和 B 复选框,而 C 和 D 应保持未选中状态。

【问题讨论】:

    标签: c# wpf listbox wpf-controls itemscontrol


    【解决方案1】:

    我认为没有使用一些代码隐藏的方法可以做到这一点。

    我采用了您在上面链接的示例解决方案,从 MainWindow.xaml 中删除了所有 CheckBox,将以下方法添加到 MainWindow.xaml.cs 并从 MainWindow 构造函数中调用它:

        private void AddCheckBoxes()
        {
            var converter = new FlagsEnumValueConverter();
            foreach (Department dept in Enum.GetValues(typeof(Department)))
            {
                if (dept != Department.None)
                {
                    var binding = new Binding()
                    {
                        Path = new PropertyPath("Department"),
                        Converter = converter,
                        ConverterParameter = dept
                    };
    
                    var checkBox = new CheckBox() { Content = dept.ToString() };
                    checkBox.SetBinding(CheckBox.IsCheckedProperty, binding);
                    DepartmentsPanel.Children.Add(checkBox);
                }
            }
        }
    

    此方法创建所有复选框,除了None 之外,每个已命名的枚举常量对应一个复选框。然后我可以将更多部门添加到 Department 枚举,重新运行解决方案并查看新添加部门的其他复选框。

    我必须对此解决方案进行一些进一步的小改动才能使其完全正常工作。您可能需要也可能不需要对代码进行这些更改。首先,我让DataObject 类实现INotifyPropertyChanged。其次,我在 MainWindow.xaml 中重写了 XAML,如下所示:

    <StackPanel>
        <StackPanel x:Name="DepartmentsPanel" />
        <TextBlock Margin="5,20,0,0">
            <TextBlock Text="Raw Value:" FontWeight="Bold" />
            <TextBlock Text="{Binding Department}" />
        </TextBlock>
    </StackPanel>
    

    (基本上,我将现有的 DepartmentsPanel 包装在另一个 StackPanel 中,并将“原始值”显示移动到这个外部 StackPanel 中。)最后,我将整个 MainWindow 的 DataContext 设置为,而不是 DepartmentsPanel 的 DataContext DataObject 创建。此步骤是使“原始值”显示正常工作所必需的。

    【讨论】:

      【解决方案2】:

      我创建了一个 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();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2010-09-24
        • 2012-05-05
        • 1970-01-01
        • 2015-09-09
        • 2011-04-25
        • 2023-03-06
        • 2011-12-06
        • 1970-01-01
        • 2012-03-05
        相关资源
        最近更新 更多