我需要能够选择多个值,因为WPF视图中的Flag枚举的性质(都是它,在PropertyGrid中)。
有问题的属性是动态,并且不能使用预定义的DataTemplates,因为将在运行时发现类型的属性。 (可以检测枚举是否为Flag的DataTemplate可能会有所帮助,但根据我的理解,我需要提前知道Flag Enum类型以实现此目的,并且情况并非如此)。
我已经为WPF尝试了许多专有和开源属性网格,似乎没有人支持“Flags”归因于开箱即用的枚举类型。
这个问题的解决方案是任何允许我数据绑定为+为任何商业或开源WPF PropertyGrid为所述Flags Enum选择多个值的解决方案。
代码:
示例PropertyType:
public class PropertyTypeOne
{
public PropertyTypeOne()
{
IntProp = 1;
InProp2 = 2;
BoolProp = true;
Boolprop2 = false;
StringProp = "string1";
DoubleProp = 2.3;
EnumProp = FlagEnumDataTYpe.MarketDepth;
}
public int IntProp { get; set; }
public int InProp2 { get; set; }
public bool BoolProp { get; set; }
public bool BoolProp2 { get; set; }
public string StringProp { get; set; }
public double DoubleProp { get; set; }
//This is the property in question
public FlagEnumDataType EnumProp { get; set; }
}
示例标记枚举类型:
[Flags]
public enum FlagEnumDataType : byte
{
None = 0,
Trade = 1,
Quote = 2,
MarketDepth = 4,
All = 255
}
注意:
如果解决方案使用了开源WPF PropertyGrid(http://www.codeplex.com/wpg),我将把更改/添加实现回控件。
感谢。
答案 0 :(得分:2)
我没有找到一种真正优雅的方式来做这件事,但是通过与Mindscape的开发人员交谈,这里有一些粗糙但功能强大的东西适用于Mindscape PropertyGrid。
首先,我们为flag-enum编辑器本身创建一个模板。这是使用WPF属性网格库中的EnumValuesConverter填充的ItemsControl:
<ms:EnumValuesConverter x:Key="evc" />
<local:FlaggyConverter x:Key="fc" />
<DataTemplate x:Key="FlagEditorTemplate">
<ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}">
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
现在我们需要根据标志是打开还是关闭来显示复选框。这需要两件事:第一,IMultiValueConverter,因此它可以考虑手头的标志和上下文值,其次,单个复选框读取上下文值的方法。 (根据上下文值,我的意思是实际的属性值。例如,上下文值可能是Flag1 | Flag4 | Flag32。)这是转换器:
public class FlaggyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int flagValue = (int)values[0];
int propertyValue = (int)values[1];
return (flagValue & propertyValue) == flagValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
为了传播上下文值,我将使用快捷方式并使用Tag。您可能更喜欢使用更有意义的名称创建附加属性。
现在,控件将显示已设置标志的检查,但是当您单击复选框时,它将不会更新该值。不幸的是,我发现这项工作的唯一方法是处理Checked和Unchecked事件并手动设置上下文值。为此,我们需要将上下文值放在可以从复选框事件处理程序更新的位置。这意味着将复选框的属性双向绑定到上下文值。我再次使用Tag虽然你可能想要一些更清洁的东西;另外,我将使用直接事件处理,但根据您的设计,您可能希望将其包装为附加行为(如果您创建附加属性以携带上下文值,这将特别有效)。
<DataTemplate x:Key="FlagEditorTemplate">
<ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}" Tag="{Binding Value, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" Tag="{Binding Tag, ElementName=ic, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource fc}" Mode="OneWay">
<Binding />
<Binding Path="Tag" ElementName="ic" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
注意Tag的双向绑定:这样当我们从事件处理代码设置Tag时,它会传播回ic.Tag,并从那里传播到属性的Value。
事件处理程序大多是显而易见的,只有一个皱纹:
<DataTemplate x:Key="FlagEditorTemplate">
<ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}" Tag="{Binding Value, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" Tag="{Binding Tag, ElementName=ic, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource fc}" Mode="OneWay">
<Binding />
<Binding Path="Tag" ElementName="ic" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
事件处理程序:
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBox cb = (CheckBox)sender;
int val = (int)(cb.Tag);
int flag = (int)(cb.Content);
val = val | flag;
cb.Tag = (Curses)val;
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
CheckBox cb = (CheckBox)sender;
int val = (int)(cb.Tag);
int flag = (int)(cb.Content);
val = val & ~flag;
cb.Tag = (Curses)val;
}
注意设置cb.Tag时的强制转换。如果没有这个,WPF内部无法在尝试将值传播回源时将值转换为枚举类型。这里的Curses是我的枚举类型。如果您想要一个完全灵活的,与类型无关的编辑器,您需要在外部提供它,例如作为复选框上的附加属性。您可以使用转换器推断它,也可以从编辑器EditContext传播它。
最后,我们需要将其连接到网格。您可以逐个属性地执行此操作:
<ms:PropertyGrid>
<ms:PropertyGrid.Editors>
<ms:PropertyEditor PropertyName="Curses" EditorTemplate="{StaticResource FlagEditorTemplate}" />
</ms:PropertyGrid.Editors>
</ms:PropertyGrid>
或使用智能编辑器声明来连接其类型具有FlagsAttribute的所有属性。有关创建和使用智能编辑器的信息,请参阅http://www.mindscape.co.nz/blog/index.php/2008/04/30/smart-editor-declarations-in-the-wpf-property-grid/。
如果你想节省空间,可以将ItemsControl更改为ComboBox,虽然你需要做一些额外的工作来处理折叠的显示;我没有详细探讨过这个问题。
答案 1 :(得分:0)
在互联网上发现,略有改进,但还没来得及测试它。
/// <summary>
/// Two-way conversion from flags to bool and back using parameter as mask
/// Warning: The trick is in storing value locally between calls to Convert and ConvertBack
/// You must have a single instance of this converter per flags property per object
/// Do not share this converter between different objects or properties
/// Typical usage:
/// [Flags] enum FlagType { None = 0, Trade = 1, Quote = 2, Report = 4, All = 255 }
/// <local:EditableFlagsToBooleanConverter x:Key="FlagsToBooleanConverter" />
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay,
/// ConverterParameter={x:Static local:FlagType.Trade}}" >Trade</CheckBox>
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay,
/// ConverterParameter={x:Static local:FlagType.Quote}}" >Quote</CheckBox>
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay,
/// ConverterParameter={x:Static local:FlagType.Report}}" >Report</CheckBox>
/// </summary>
public class EditableFlagsToBooleanConverter : IValueConverter
{
private ulong _target;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is Enum && value is Enum)
{
var mask = (ulong) parameter;
_target = (ulong) value;
return ((mask & _target) != 0);
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool && parameter is Enum)
{
var mask = (ulong)parameter;
if ((bool)value)
{
_target |= mask;
}
else
{
_target &= ~mask;
}
return _target;
}
return Binding.DoNothing;
}
}