将标志枚举绑定到包含复选框的列表框

本文关键字:复选框 列表 包含 标志 枚举 绑定 | 更新日期: 2023-09-27 18:30:36

我有标志枚举说这个 -

[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 上有这么多复选框,因为我的枚举包含 20 个值(所以这意味着 UI 上有 20 个复选框)。

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

将标志枚举绑定到包含复选框的列表框

我认为没有办法在不使用一些代码隐藏的情况下做到这一点。

我采用了上面链接到的示例解决方案,从 MainWindow.xaml 中删除了所有复选框,将以下方法添加到 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中,并将"Raw Value"显示移动到这个外部StackPanel中。 最后,我将整个 MainWindow 的 DataContext 而不是 DepartmentsPanel 的 DataContext 设置为创建的DataObject。 此步骤是使"原始值"显示正常工作所必需的。

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

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

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

转换器实例应获取枚举的类型以及它是否是 Flags 枚举。

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