代码之家  ›  专栏  ›  技术社区  ›  holsee

WPF数据绑定到标志枚举(在PropertyGrid中)

  •  1
  • holsee  · 技术社区  · 15 年前

    我需要有能力选择多个值,因为这是WPF视图中标志枚举的性质(所有都是,在PropertyGrid中)。

    所讨论的属性是 动态 并且没有预先定义的数据模板可以用作 将在运行时发现属性的类型 . (可以检测枚举是否为标志的数据模板可能会有所帮助,但据我所知,我需要提前了解标志枚举类型才能实现这一点,而事实并非如此)。

    我已经为WPF尝试了许多专有的和开放源代码的属性网格,但似乎没有一个可以直接支持“flags”属性枚举类型。

    这个问题的解决方案是任何允许我为任何商业或开放源代码WPF属性网格的上述标志枚举的+选择多个值进行数据绑定的内容。

    代码:

    示例属性类型:

    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 )我将在控件中实现更改/添加。

    谢谢。

    2 回复  |  直到 13 年前
        1
  •  2
  •   holsee    15 年前

    我还没有找到一种真正优雅的方法来实现这一点,但是通过与Mindscape的开发人员交谈,这里有一些粗糙但功能性很强的东西可以与Mindscape PropertyGrid一起使用。

    首先,我们为标志枚举编辑器本身创建一个模板。这是使用WPF属性网格库中的EnumValueConverter填充的项控件:

    <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();
      }
    }
    

    为了传播上下文值,我将使用一个快捷方式并使用标记。您可能更喜欢使用更有意义的名称创建附加属性。

    现在,控件将显示对已设置标志的检查,但当您单击打开或关闭复选框时,它还不会更新值。不幸的是,我找到的唯一方法是处理选中和未选中的事件,并手动设置上下文值。为了做到这一点,我们需要将上下文值放在可以从复选框事件处理程序更新它的位置。这意味着双向将复选框的属性绑定到上下文值。再次,我将使用标记,尽管您可能需要一些更干净的东西;另外,我将使用直接事件处理,但是根据您的设计,您可能希望将其包装成附加的行为(如果您创建附加属性以携带上下文值,这将特别有效)。

    <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>
    

    注意标记的双向绑定:这样,当我们从事件处理代码设置标记时,它会传播回ic.tag,并从那里传播到属性的值。

    事件处理程序通常很明显,但有一个褶皱:

    <datatemplate x:key=“flageditoremplate”>
    <itemscontrol name=“ic”itemssource=“绑定值,converter=staticresource evc”tag=“绑定值,模式=twoway”>
    <itemsControl.itemTemplate>
    <数据模板>
    <checkbox content=“绑定”tag=“绑定标记,elementname=ic,mode=twoway”checked=“checkbox_checked”unchecked=“checkbox_unchecked”>
    <checkbox.ischecked>
    <multibinding converter=“staticresource fc”mode=“单向”>
    <绑定/>
    <binding path=“tag”elementname=“ic”/>
    </multibinding>
    </checkbox.ischecked>
    </checkbox>
    </datatemplate>
    </itemscontrol.itemstemplate>
    </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更改为组合框,尽管您需要做一些额外的工作来处理折叠的显示;我没有详细探讨过这个问题。

        2
  •  0
  •   agroskin    14 年前

    在互联网上发现,略有改善,但还没有时间测试。

    /// <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;
        }
    }