Introduction
PropertyGrid is a very nice control that allows you displaying and changing object information through its properties. Various attributes can be applied to properties of the implemented class in order to define how they would appear in the PropertyGrid. However you might find some tasks being difficult to accomplish at runtime, i.e.:
- Filtering properties in the property grid.
- Arranging properties under new categories.
- Changing display name and description information for properties of the standard objects (i.e. standard WinForms controls).
- Assigning custom type editors and type converters to properties.
This article describes a possible way of how such tasks could be solved using wrapper classes generated at runtime.
Alternative Solutions
A topic of filtering properties in a PropertyGrid is already covered in several articles.
- "Filtering properties in a PropertyGrid" presents a custom PropertyGrid control that allows filtering properties to be displayed in the grid. While it covers property filtering problem, it does not allow changing other property features, i.e. type converter and editor information.
- "Dynamic properties in the PropertyGrid" also discusses the problem of property filtering in a PropertyGrid. The biggest limitation of this method is the requirement to implement a custom interface for the objects that would be displayed in a PropertyGrid.
Why Object Wrappers?
In order to change the way objects appear in the PropertyGrid it is necessary to modify the type information of the object. Usually it is done when designing a particular class by applying various attributes to class properties. However it cannot be done for already built object types as you cannot change type information at runtime. On the other hand, it is possible to write an inherited class but this is not always a suitable solution.
The idea presented in this article is based on dynamically creating wrapper classes that wrap original types. Wrapper classes contain the same properties as the type being wrapped. All property setters and getters are just forward declarations to properties of the original instance.
public class ObjectWrapper
{
private Button wrappedInstance;
public ObjectWrapper(Button objectInstance)
{
wrappedInstance = objectInstance;
}
[Description("The background color of the component. "), <BR> Category("Appearance")]
public Color BackColor
{
get
{
return this.wrappedInstance.BackColor;
}
set
{
this.wrappedInstance.BackColor = value;
}
}
...
}
System.Reflection.Emit API is used to dynamically generate class types similar to the one presented above. Using System.Reflection.Emit is faster than using CodeDOM API, but it requires all constructor and method body content to be described in IL instructions. So generally having 3 IL instruction sets - 1 for object wrapper constructor and 2 for property getter and setter methods - is enough to generate wrapper class for any type in order to present it in the PropertyGrid.
Also when generating a wrapper type, it is possible to add, change or remove any custom attributes that affect the way object is presented in the PropertyGrid. The following example displays how DisplayNameAttribute
, DescriptionAttribute
, CategoryAttribute
, BrowsableAttribute
and TypeConverterAttribute
are set to the generated wrapper type at runtime:
private static void CreateCustomAttributes(PropertyInfo propertyInfo, <BR> PropertyBuilder propertyBuilder)
{
Array customAttributes = propertyInfo.GetCustomAttributes(true);
ConstructorInfo ctor = null;
object[] ctorArgs = null;
if (customAttributes != null)
foreach (Attribute attribute in customAttributes)
{
ctor = null;
ctorArgs = null;
if (attribute is BrowsableAttribute)
{
BrowsableAttribute a = attribute as BrowsableAttribute;
ctor = typeof(BrowsableAttribute).GetConstructor(new Type[]<BR> { typeof(bool) });
ctorArgs = new object[] { a.Browsable };
}
if (attribute is DescriptionAttribute)
{
DescriptionAttribute a = attribute as DescriptionAttribute;
ctor = typeof(DescriptionAttribute).GetConstructor(new Type[]<BR> { typeof(string) });
ctorArgs = new object[] { a.Description };
}
if (attribute is CategoryAttribute)
{
CategoryAttribute a = attribute as CategoryAttribute;
ctor = typeof(CategoryAttribute).GetConstructor(new Type[]<BR> { typeof(string) });
ctorArgs = new object[] { a.Category };
}
if (attribute is DisplayNameAttribute)
{
DisplayNameAttribute a = attribute as DisplayNameAttribute;
ctor = typeof(DisplayNameAttribute).GetConstructor(new Type[]<BR> { typeof(string) });
ctorArgs = new object[] { a.DisplayName };
}
if (ctor != null && ctorArgs != null)
propertyBuilder.SetCustomAttribute(<BR> new CustomAttributeBuilder(ctor, ctorArgs));
}
if (propertyInfo.PropertyType.Equals(typeof(object)))
{
TypeConverterAttribute a = new TypeConverterAttribute();
ctor = typeof(TypeConverterAttribute).GetConstructor(new Type[] <BR> { typeof(System.Type) });
ctorArgs = new object[] { typeof(StringConverter) };
propertyBuilder.SetCustomAttribute(new CustomAttributeBuilder(ctor, <BR> ctorArgs));
}
}
Using the code
Classes ObjectWrapperFactory
and ObjectWrapper
demonstrate how wrapper type can be generated and used for filtering properties in a PropertyGrid.
string[] propertyNames = new string[] { "Size", "Text", "Enabled" };
pgOriginal.SelectedObject = button1;
pgWrapper.SelectedObject<BR> = ObjectWrapperFactory.CreateWrapper(button1).Wrapper;
pgVisible.SelectedObject<BR> = ObjectWrapperFactory.CreateWrapperWithVisibleProperties(button1, <BR> propertyNames).Wrapper;
pgHidden.SelectedObject<BR> = ObjectWrapperFactory.CreateWrapperWithHiddenProperties(button1, <BR> propertyNames).Wrapper;
Points of Interest
The method presented in this article does not require any modifications in the PropertyGrid control or the objects to be presented in the PropertyGrid. All necessary type modifications can be performed on the wrapper type generated at runtime.
Major Problems
- Wrapper types do not include attached properties.
- Demo application does not set category, display name, description information for properties that are overridden in base classes, i.e. Text property of the
System.Windows.Forms.Button
.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.