views:

555

answers:

1

I need to have the ability to select multiple values as is the nature of a Flag enumeration from a WPF view (all be it, in a PropertyGrid).

The properties in question are dynamic and no pre-defined DataTemplates can be used as the type of the properties will be discovered at runtime. (A DataTemplate which can detect if an enumeration is a Flag may prove helpful, but from my understanding I would need to know the Flag Enum types ahead of time to achieve this and that will not be the case).

I have tried out a number of proprietary and open source property grids for WPF and none seem to support 'Flags' attributed enum types out of the box.

A solution to this issue would be anything that would allow me to databind to + select multiple values for said Flags Enum for any commercial or open source WPF PropertyGrid.

Code:

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

Example Flag Enumeration Type:

[Flags]
public enum FlagEnumDataType : byte
{
    None = 0,
    Trade = 1,
    Quote = 2,
    MarketDepth = 4,
    All = 255
}

Note:

If the solution makes use of the Open Source WPF PropertyGrid (http://www.codeplex.com/wpg) I will implement the changes /additions back into the control.

Thanks.

A: 

I haven't found a truly elegant way of doing this, but from talking with the Developers at Mindscape and here's something crude but functional thats works with the Mindscape PropertyGrid.

First, we create a template for the flag-enum editor itself. This is an ItemsControl populated using the EnumValuesConverter from the WPF Property Grid library:

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

Now we need to display the check boxes as checked according to whether the flag is on or off. This requires two things: first, an IMultiValueConverter so it can consider both the flag at hand and the context value, and second, a way for individual check boxes to read the context value. (By context value I mean the actual property value. E.g. the context value might be Flag1 | Flag4 | Flag32.) Here's the converter:

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

For propagating the context value, I'm going to take a shortcut and use Tag. You might prefer to create an attached property with a more meaningful name.

Now the control will display checks for the flags that are set, but won't yet update the value when you click a checkbox on or off. Unfortunately, the only way I've found to make this work is by handling the Checked and Unchecked events and setting the context value by hand. In order to do this, we need place the context value in a place where it can be updated from the check box event handlers. This means two-way binding a property of the check box to the context value. Again I'll use Tag though you may want something a bit cleaner; also, I'm going to use direct event handling, though depending on your design you may want to wrap this up into an attached behaviour (this would work particularly well if you were creating attached properties to carry around the context 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>

Note the two-way binding of the Tag: this is so that when we set Tag from our event handling code, it propagates back to ic.Tag, and from there to the property's Value.

The event handlers are mostly obvious but with one wrinkle:

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

Event Handlers:

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

Note the cast when setting cb.Tag. Without this, WPF internally fails to convert the value to the enum type when trying to propagate it back to the source. Here Curses is my enum type. If you want a fully flexible, type-agnostic editor, you'll want to provide this externally, for example as an attached property on the check box. You could either infer this using a converter or propagate it from an editor EditContext.

Finally we need to hook this up to the grid. You can do this either on a property-by-property basis:

<ms:PropertyGrid>
  <ms:PropertyGrid.Editors>
    <ms:PropertyEditor PropertyName="Curses" EditorTemplate="{StaticResource FlagEditorTemplate}" />
  </ms:PropertyGrid.Editors>
</ms:PropertyGrid>

or by using a smart editor declaration to hook up all properties whose types have a FlagsAttribute. For info about creating and using smart editors, see http://www.mindscape.co.nz/blog/index.php/2008/04/30/smart-editor-declarations-in-the-wpf-property-grid/.

If you want to save space you can change the ItemsControl to a ComboBox though you'll need to do some additional work to handle the collapsed display; I haven't explored this in detail.

StevenH