Набор инструментов WPF: перечисление CheckComboBox и [Flags]

Я использую 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());
    }
}

Как видите, до сих пор я пытался связать свойства SelectedValue и SelectedItem. CreateValueConverter() определяется в базовом классе и возвращает null.

Это хорошо работает, если я выбираю некоторые значения в поле и нажимаю кнопку «Сохранить» - в моей модели я получаю правильное значение перечисления. Но это не работает в другом направлении — если я устанавливаю любое значение перечисления (с флагами или без них) для своего свойства, все значения не проверяются, а область содержимого пусто.

У вас есть идея решить эту проблему?


person Franz Wimmer    schedule 22.01.2016    source источник


Ответы (1)


Для Enum с FlagsAttribute в этом случае наиболее общим решением будет использование определенных полей в вашей виртуальной машине для всех элементов 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 января 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);
person Anton Danylov    schedule 22.01.2016
comment
Что я забыл упомянуть - извините, я обновлю свой вопрос - так это то, что я не могу полагаться на перечисление времени компиляции. Я бы динамически создал Enum, используя [EnumBuilder](https://msdn.microsoft.com/de-de/library/system.reflection.emit.enumbuilder%28v=vs.110%29.aspx). - person Franz Wimmer; 28.01.2016
comment
Обновлен код для обработки динамически генерируемых перечислений с атрибутом flags. - person Anton Danylov; 31.01.2016