In a project I had to create a little test application to create and edit objects of a referenced assembly. This was very early in the development stage, so the classes could possibly change a lot during the course of the project.
Instead of designing a UI for each class and getting into the process of changing it over and over again, I decided to use Reflection and the PropertyGrid control in winForms.
This worked well: getting all types via reflection, showing them in a treeview, creating an instance when the user selected a type in the treeview and showing it in the PropertyGrid. No problem there.
Even editing a property of an enumeration type was no problem.
But then I ran into a problem. Some of the enumerations were actually Flags. So the user must be able to select multiple values from the list. This is not supported by the PropertyGrid.
Fortunately there is a solution for this at CodeProject. A Flag enum editor. This can be used by the PropertyGrid control by adding an attribute to the properties that use a flag enumeration:
[Editor(typeof(FlagEnumUIEditor), typeof(System.Drawing.Design.UITypeEditor))]
Problem solved!
No wait! The items we wanted to test were defined in some business logic layer. We don’t want to add this attribute and a reference to Windows.Forms and so, to our business logic layer.
So, why can’t we just apply or set the attribute to the flag-properties at runtime?
Via reflection, using the PropertyDescriptor, you can check the attributes and get the list (or array) of attributes, but unfortunately this attribute list is readonly!
Unless you use reflection on the PropertyDescriptor as well. So, Reflection on Reflection objects. 🙂
private static readonly Attribute s_EditorAttribute = new EditorAttribute(typeof(FlagEnumUIEditor), typeof(System.Drawing.Design.UITypeEditor)); /// <summary> /// Processes the flag enum properties of the given type /// to set the UI-editor for property grid /// (Add the Editor-attribute to these properties) /// </summary> /// <param name="type">The type to analyse.</param> private static void ProcessFlagProperties(Type type) { var props = TypeDescriptor .GetProperties(type, new[] { new FlagsAttribute() }); foreach (var prop in props) { // AttributeArray-property is not accessible // => use reflection to get and set it. var attributeArrayPropInfo = prop .GetType() .GetProperty("AttributeArray", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); var attributeArray = attributeArrayPropInfo .GetValue(prop, null) as Attribute[]; AddEditorAttribute(ref attributeArray); attributeArrayPropInfo .SetValue(prop, attributeArray, null); } } /// <summary> /// Adds the editor attribute to the given attribute array. /// </summary> /// <param name="att">The attribute array.</param> private static void AddEditorAttribute(ref Attribute[] att) { var length = att.Length; if (length == 0) { att = new[] { s_EditorAttribute }; } else { var newArray = new Attribute[length + 1]; att.CopyTo(newArray, 0); newArray[length] = s_EditorAttribute; att = newArray; } }
When I then created an instance (newObject = Activator.CreateInstance(type)
) of these types, the attribute was taken into account and the checked listbox was shown in the PropertyGrid. Which was nice. 🙂