Reputation: 1487
I'm using Xceed Extended WPF Toolkit to display a enum with [Flags]
attribute in a PropertyGrid
.
[Flags]
public enum TestEnum
{
Test1 = 1,
Test2 = 2,
Test3 = 4,
Test4 = 8,
Test5 = 16,
Test6 = 32,
Test7 = 64,
}
Because I can't know the enum definition at compile time, I would dynamically create an Enum using EnumBuilder.
I created an editor to display the enum as 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());
}
}
As you can see, so far I've tried to bind each the SelectedValue
and the SelectedItem
property. CreateValueConverter()
is defined in the base class and returns null
.
It works well if I select some Values in the box and hit my save Button - in my model, I receive the correct enum value. But it doesn't work in the other direction - if i set any enum value (with or without flags) to my property, all values are unchecked and the content area is empty.
Do you have any idea to solve this problem?
Upvotes: 3
Views: 1522
Reputation: 1491
For Enum with FlagsAttribute in that case the most general solution would be to use specific fields in your VM for all items of the Enum and for selected items. Something like that:
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)));
}
}
}
UPDATE 31/01/2016 To make the code work for dynamically generated Enum, I've made following changes:
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();
}
}
Now for ItemViewModel Value could be set like this:
ItemViewModel vm = new ItemViewModel();
vm.Value = Enum.ToObject(ItemViewModel.EnumType, 33);
Upvotes: 6