Friday, August 17, 2012

WPF PropertyGrid - display partial enum values in ComboBox

In the previous post, I explained a way of getting a subset of enum values. This post shows an example of displaying a subset of enum onto WPF PropertyGrid. In WPF 4.0, there is no standard built-in PropertyGrid control but one can find a good PropertyGrid from Extended WPF ToolKit at codeplex website. As of now, its latest version is 1.6 but due to some compatibility issue, I happen to use v1.5. Since this is open source, you can also download source code from the website.

In WPF PropertyGrid 1.5, there is no way of doing enum filtering as we expect (of course). In order to facilitate enum filtering, I added an editor EnumFilterComboBoxEditor derived from ComboBoxEditor.

public class EnumFilterComboBoxEditor<T> : ComboBoxEditor where T : struct
{
    public EnumFilterComboBoxEditor()
    {
    }

    protected override void ResolveValueBinding(PropertyItem propertyItem)
    {
        EnumFilter<T> enumFilter = (EnumFilter<T>)propertyItem.Value;
        var _binding = new Binding("Value");
        _binding.Source = enumFilter;
        _binding.ValidatesOnExceptions = true;
        _binding.ValidatesOnDataErrors = true;
        _binding.Mode = propertyItem.IsWriteable ? BindingMode.TwoWay : BindingMode.OneWay;
        _binding.Converter = CreateValueConverter();
        BindingOperations.SetBinding(Editor, ValueProperty, _binding);
    }

    protected override IList<object> CreateItemsSource(PropertyItem propertyItem)
    {
        return GetValues(propertyItem.Value);
    }

    private static object[] GetValues(object enumObject)
    {
        List<object> values = new List<object>();

        EnumFilter<T> enumFilter = (EnumFilter<T>)enumObject;            
        var enums = enumFilter.GetEnums();
        foreach (var e in enums)
        {
            values.Add(e);
        }

        return values.ToArray();
    }
}

ResolveValueBinding() sets binding between source property EnumFilter<T>.Value and target Editor.ValueProperty. CreateItemsSource() sets all possible values of ComboBox items where we call GetEnums() method to get filtered enum values. And now to invoke this new type of editor, the following code added to PropertyGrid class (line 478 in propertygrid.cs).

else if (propertyItem.PropertyType.BaseType == typeof(Common.EnumFilter))
{
    Type tenum = propertyItem.Value.GetType().GetGenericArguments()[0];
    Type filterEditor = typeof(EnumFilterComboBoxEditor<>).MakeGenericType(tenum);
    editor = (ITypeEditor)Activator.CreateInstance(filterEditor, true);                        
}
else if (propertyItem.PropertyType.IsEnum) // all Enums
    editor = new EnumComboBoxEditor();

Basically what it does is check the PropertyType and sets different editor based on the type. For enum filtering case, propertyItem.Value is EnumFilter<T> object. And calling GetGenericArguments()[0] returns the first generic argument type (that is, T). For example, for EnumFilter<MartType>, GetGenericArguments()[0] will return MartType. Next line MakeGenericType() call returns concrete type from generics by using passed argument (tenum). So filterEditor will be EnumFilterComboBoxEditor<MartType> type. Please note C# generics cannot be a type unless generic argument T is specified. Once we got the concrete type, we can create an instance from it by using Activator.CreateInstance() method as seen in the next line. I think the sample code snippet is not optimized due to lack of time. Just wanted to summarize my thought...



1 comment: