Click here to Skip to main content
15,860,844 members
Articles / Programming Languages / C#
Article

Type Wrapper Classes and a PropertyGrid

Rate me:
Please Sign up or sign in to vote.
4.68/5 (9 votes)
1 Aug 20063 min read 73.8K   1.5K   56   17
An article discusses a way of dynamically generating wrapper classes for objects displayed in a Property Grid.

Button wrapper objects assigned to PropertyGrid

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.

C#
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:

C#
private static void CreateCustomAttributes(PropertyInfo propertyInfo, <BR>                                         PropertyBuilder propertyBuilder)
{
    Array customAttributes = propertyInfo.GetCustomAttributes(true);
    ConstructorInfo ctor = null;
    object[] ctorArgs = null;

    // In order for properties to be displayed properly inside the property <BR>    // grid it is necessary to set appropriate BrowsableAttribute, <BR>    // DescriptionAttribute, CategoryAttribute, DisplayNameAttribute <BR>    // attribute values (could be more attributes applicable). 
    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));
        }

    // Property grid by default does not allow editing System.Object <BR>    // properties, but sometimes it is necessary to fill in value as a <BR>    // string (ie. when filling System.Windows.Control.ContentControl.Content<BR>    // value for Windows Presentation Foundation ContentControl). For that <BR>    // purpose we add TypeConverter(typeof(StringConverter)) attribute to <BR>    // such properties. 
    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.

C#
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Lithuania Lithuania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionBug with Objects collection Pin
Wrangly30-Jan-12 23:41
Wrangly30-Jan-12 23:41 
QuestionGreat ! Pin
Wrangly23-Jan-12 22:06
Wrangly23-Jan-12 22:06 
QuestionIs it possible to add value change notifications on existing type? Pin
VallarasuS21-Dec-11 1:14
VallarasuS21-Dec-11 1:14 
GeneralMy vote of 5 Pin
VovaM212-Sep-11 18:59
VovaM212-Sep-11 18:59 
GeneralChnaging 'EditorAttribute at runtime' for a property Pin
ManasAddy15-Dec-09 4:36
ManasAddy15-Dec-09 4:36 
GeneralEvents Pin
Aos3k28-Sep-09 23:18
Aos3k28-Sep-09 23:18 
GeneralBrilliant! Pin
tcp2000a7-Oct-08 23:50
tcp2000a7-Oct-08 23:50 
GeneralProperty Grid Freedom! Pin
softplanner17-Oct-07 9:56
softplanner17-Oct-07 9:56 
GeneralRe: Property Grid Freedom! Pin
melnac4-Feb-08 0:40
melnac4-Feb-08 0:40 
GeneralMore attributes... Pin
softplanner10-Oct-07 8:59
softplanner10-Oct-07 8:59 
GeneralInheritance of CustomAttributes Pin
softplanner10-Oct-07 7:44
softplanner10-Oct-07 7:44 
QuestionWhat's wrong with TypeConverter when it comes to Reflection.Emit used in AddIn program Pin
sinbao21-Jun-07 23:09
sinbao21-Jun-07 23:09 
GeneralOustanding Pin
BlaiseBraye11-Jan-07 2:52
BlaiseBraye11-Jan-07 2:52 
GeneralRe: Oustanding Pin
BlaiseBraye11-Jan-07 3:20
BlaiseBraye11-Jan-07 3:20 
GeneralConsider ICustomTypeDescriptor instead Pin
dsk303715-Aug-06 17:18
dsk303715-Aug-06 17:18 
QuestionRe: Consider ICustomTypeDescriptor instead Pin
Member 79931615-Feb-09 5:43
Member 79931615-Feb-09 5:43 
AnswerRe: Consider ICustomTypeDescriptor instead Pin
dsk303715-Feb-09 19:52
dsk303715-Feb-09 19:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.