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

时间:2016-01-22 07:35:39

标签: c# wpf enums wpftoolkit xceed

我正在使用Xceed Extended WPF Toolkit在[Flags]中显示包含PropertyGrid属性的枚举。

[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()在基类中定义并返回null

如果我在框中选择一些值并点击我的保存按钮,那么效果很好 - 在我的模型中,我收到了正确的枚举值。但是在另一个方向上无效 - 如果我将任何枚举值(带或不带标记)设置为我的属性,则取消选中所有值并且内容区域为

你有什么想法解决这个问题吗?

1 个答案:

答案 0 :(得分:5)

对于具有FlagsAttribute的Enum,在这种情况下,最常见的解决方案是在VM中为Enum的所有项目和所选项目使用特定字段。这样的事情:

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

更新31/01/2016 为了使代码适用于动态生成的Enum,我做了以下更改:

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,Value可以这样设置:

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