WPF 组合框/列表框,具有基于带有标志的枚举的多重选择

发布于 2024-08-06 20:01:12 字数 475 浏览 2 评论 0原文

所以我可能有点突破了界限...

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

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

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

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

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

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

So I may be pushing the boundaries just a bit...

Basically I have the following enum, declared in C# code:

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

This enum is a member of an object which I have successfully bound to a DataGrid object. Successfully meaning that I have bound all the other fields successfully. :)

What I want to achieve here is a control where all the appropriate options above are checked, that behaves and acts like a ComboBox/ListBox. So you click on the field and a drop-down menu pops up with the ability to "check" whichever options are required.

The control will also have to be able to read from the enum and write an enum.

I'm a WPF novice so I have no idea where to go apart from creating a ComboBox and binding to the column... Any help would be appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

也只是曾经 2024-08-13 20:01:12

我有一个方法可能有效。我对此没有任何功劳 - 我在网上找到了这个方法,但忘记保存地址。

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

转换器:

/// <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)。

I have a way that might work. I take no credit for this - I found this method on the web, and forgot to save the address.

In my project I needed to bind a few checkboxes to a flag enum. To help, I found an implementation of a simple value converter to facilitate two way binding. It's not generic, and a single instance of a converter can only work with one target (meaning one instance of a value and its group of checkboxes) at a time. The converter uses a stored reference to the value as a way to convert back, so if you try to reuse it between separate object instances it won't work. That said, this is the only use i had for something like this and it worked like a charm.

The converter:

/// <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;
    }
}

In xaml it is used thusly:

<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}...

In your case you might place this into your datacell template (though obviously you probably prefer to use a combobox ragther than a simple stackpanel. Make sure to instantiate the converter close to your checkbox group container to make sure they have their own instance of the converter.

Edit:

Here, I made a little test project to demonstrate using this in a combobox with a datagrid, it's based off the default WPF application - just make sure to reference the WPF toolkit.

Here is the Window1.xaml file:

<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>

And here is the Window1.xaml.cs file codebehind.

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

}

By default the datagrid will create its own representation of the column as well as my mandated templated one, so you can see the text representation as well as the checkbox one. The flag enum confuses the default text representation, but you can still see that the binding is working correctly (check both, then uncheck the one you checked last - string value changes to the other one, not 0).

星軌x 2024-08-13 20:01:12

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

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

该解决方案基于这样的经验事实:在 ConvertBack 之前总是先有 Convert。如果 ConvertBack 更改了值,则始终存在 Convert。仅当 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();
    }
}

I have created an IValueConverter that supports binding to the enum directly without codebehind or helper classes. It has only two restrictions:

  • One converter instance has to be used for each source property. If the model contains more properties of the same enum type, each of them needs to use a separate converter instance. This can be done by instantiating the converters in the XAML.
  • When the enum type is a flags enum it has to be an Integer.

The solution is based on the empirical fact that there is always a Convert first before a ConvertBack. And there is always a Convert if a ConvertBack has changed the value. This one only works when the INotifyPropertyChanged is properly implemented on the model. So between the two calls the last known value can be stored in the converter and used in the ConvertBack method.

The converter instance should get the Type of the enum and whether it is a Flags enum or not.

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

Then the checkboxes can be bound using this converter.

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

Radio buttons can be bound by the same mechanism using an instance with Flags="False"

The source code of the converter

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();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文