WPF 工具包:CheckComboBox 和 [Flags] 枚举

WPF Toolkit: CheckComboBox and [Flags] enum

我正在使用 Xceed Extended WPF Toolkit 在 PropertyGrid.

中显示具有 [Flags] 属性的枚举
[Flags]             
public enum TestEnum
{
    Test1 = 1,
    Test2 = 2,
    Test3 = 4,
    Test4 = 8,
    Test5 = 16,
    Test6 = 32,
    Test7 = 64,
}

因为我无法在编译时知道枚举定义,所以我会使用EnumBuilder动态创建一个枚举。

我创建了一个编辑器来将枚举显示为 CheckComboBox:

public class CheckComboBoxEditor : TypeEditor<CheckComboBox>, ITypeEditor
{
    protected override void SetValueDependencyProperty()
    {
        ValueProperty = CheckComboBox.SelectedValueProperty;
    }

    protected override CheckComboBox CreateEditor()
    {
        return new CheckComboBox();
    }

    protected override void ResolveValueBinding(PropertyItem propertyItem)
    {
        var _binding = new Binding("Value");
        _binding.Source = propertyItem;
        _binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        _binding.Mode = BindingMode.TwoWay;
        _binding.Converter = CreateValueConverter();
        BindingOperations.SetBinding(Editor, CheckComboBox.SelectedValueProperty, _binding);

        var _binding2 = new Binding("Value");
        _binding2.Source = propertyItem;
        _binding2.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        _binding2.Mode = BindingMode.TwoWay;
        _binding2.Converter = CreateValueConverter();
        BindingOperations.SetBinding(Editor, CheckComboBox.SelectedItemProperty, _binding2);

        Editor.ItemsSource = Enum.GetValues(propertyItem.Value.GetType());
    }
}

如您所见,到目前为止,我已尝试分别绑定 SelectedValueSelectedItem 属性。 CreateValueConverter() 定义在基础 class 和 returns null.

如果我 select 框中的一些值并点击我的保存按钮,它会很好地工作 - 在我的模型中,我收到正确的枚举值。但它 在另一个方向上不起作用 - 如果我将 any 枚举值(有或没有标志)设置为我的 属性 , 所有值都未选中并且内容区域为 empty.

你有解决这个问题的办法吗?

在这种情况下,对于带有 FlagsAttribute 的枚举,最通用的解决方案是将 VM 中的特定字段用于枚举的所有项目和选定项目。类似的东西:

XAML

<Window.DataContext>
    <local:MainWindowViewModel />
</Window.DataContext>
<Grid>

    <xctkpg:PropertyGrid Grid.Row="1" SelectedObject="{Binding CurrentObject}" AutoGenerateProperties="False">
        <xctkpg:PropertyGrid.EditorDefinitions>
            <xctkpg:EditorTemplateDefinition TargetProperties="{x:Type local:TestEnum}">

                <xctkpg:EditorTemplateDefinition.EditingTemplate>
                    <DataTemplate>
                            <xctk:CheckComboBox ItemsSource="{Binding Instance.Items}" SelectedValue="{Binding Instance.SelValue}" />
                    </DataTemplate>
                </xctkpg:EditorTemplateDefinition.EditingTemplate>

            </xctkpg:EditorTemplateDefinition>
        </xctkpg:PropertyGrid.EditorDefinitions>
        <xctkpg:PropertyGrid.PropertyDefinitions>
            <xctkpg:PropertyDefinition TargetProperties="Value" />
        </xctkpg:PropertyGrid.PropertyDefinitions>
    </xctkpg:PropertyGrid>

</Grid>

C#

class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private ItemViewModel _currentObject = new ItemViewModel() { Value = TestEnum.Test3 | TestEnum.Test7 };
    public ItemViewModel CurrentObject
    {
        get { return _currentObject; }
        set
        {
            _currentObject = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentObject)));
        }
    }
}


public class ItemViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private TestEnum _value;
    public TestEnum Value
    {
        get { return _value; }
        set
        {
            _value = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelValue)));
        }
    }

    public string SelValue
    {
        get
        {
            return String.Join(",", Enum.GetValues(typeof(TestEnum)).OfType<TestEnum>().Where(v => (_value & v) != 0).Select(v => v.ToString()));
        }
        set
        {
            _value = value.Split(new[] { ','}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()). 
                Aggregate((TestEnum)0, (acc, val) => acc | (TestEnum)Enum.Parse(typeof(TestEnum), val));

            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelValue)));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
        }
    }

    public ObservableCollection<string> Items
    {
        get
        {
            return new ObservableCollection<string>(Enum.GetNames(typeof(TestEnum)));
        }
    }
}

2016 年 1 月 31 日更新 为了使代码适用于动态生成的枚举,我进行了以下更改:

XAML

 <xctkpg:EditorTemplateDefinition TargetProperties="Value">

C#

public class ItemViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public static readonly Type EnumType = GenerateEnumType();

    private object _value;
    public object Value
    {
        get { return _value; }
        set
        {
            _value = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelValue)));
        }
    }

    public string SelValue
    {
        get
        {
            return String.Join(",", 
                Enum.GetValues(EnumType).OfType<object>().Where(v => ((int)_value & (int)v) != 0).Select(v => v.ToString()));
        }
        set
        {
            var strings = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());        
            _value = Enum.ToObject(EnumType, strings.Aggregate(0, (acc, val) => acc | (int)Enum.Parse(EnumType, val)));


            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelValue)));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
        }
    }

    public ObservableCollection<string> Items
    {
        get
        {
            return new ObservableCollection<string>(Enum.GetNames(EnumType));
        }
    }

    public static Type GenerateEnumType()
    {
        string asmNameString = "flags_enum";

        //    Create Base Assembly Objects
        AppDomain appDomain = AppDomain.CurrentDomain;
        AssemblyName asmName = new AssemblyName(asmNameString);
        AssemblyBuilder asmBuilder = appDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);

        //    Create Module and Enumeration Builder Objects
        ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule(asmNameString + "_module");
        EnumBuilder enumBuilder = modBuilder.DefineEnum(asmNameString, TypeAttributes.Public, typeof(int));

        Type fa = typeof(FlagsAttribute);


        CustomAttributeBuilder attributeBuilder =
                 new CustomAttributeBuilder(fa.GetConstructor(new Type[0]), new object[0]);

        enumBuilder.SetCustomAttribute(attributeBuilder);

        for (int i = 0; i < 7; i++)
        {
            enumBuilder.DefineLiteral($"Test{i + 1}", 1 << i);
        }

        return enumBuilder.CreateType();
    }
}

现在 ItemViewModel 的值可以这样设置:

ItemViewModel vm = new ItemViewModel();
vm.Value = Enum.ToObject(ItemViewModel.EnumType, 33);