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.