1

我需要能够从 WPF 视图中选择多个值,就像从 WPF 视图中选择多个值一样(全部是它,在 PropertyGrid 中)。

有问题的属性是动态的,不能使用预定义的 DataTemplates ,因为属性的类型将在运行时被发现。(可以检测枚举是否为标志的 DataTemplate 可能会有所帮助,但据我了解,我需要提前知道标志枚举类型才能实现这一点,但事实并非如此)。

我已经为 WPF 尝试了许多专有和开源属性网格,但似乎没有一个支持开箱即用的“标志”属性枚举类型。

这个问题的解决方案是任何允许我为任何商业或开源 WPF PropertyGrid 的标志枚举 + 选择多个值的数据绑定。

代码:

示例属性类型:

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 ),我会将更改/添加重新实施到控件中。

谢谢。

4

2 回答 2

3

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

于 2009-10-19T12:49:05.850 回答
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;
    }
} 
于 2010-12-01T18:06:20.777 回答