我还没有找到一种真正优雅的方式来做到这一点,但是通过与 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,尽管您需要做一些额外的工作来处理折叠显示;我没有详细研究过这个。