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

Populating a PropertyGrid using Reflection.Emit

Rate me:
Please Sign up or sign in to vote.
4.62/5 (25 votes)
30 Jan 2004Public Domain3 min read 179.7K   1.2K   71   40
How to create a class at runtime so you can use its properties in a PropertyGrid.

Introduction

I like using the PropertyGrid as a way for the user to specify various options when using my programs, but I got tired of constantly having to change the class that stored those options. I wanted an easy way to change what properties the grid would show without me having to change anything in a class structure.

Since the PropertyGrid automatically shows the properties of an object and the property's value, the idea came to me that I could dynamically create a type at runtime using the classes contained in Reflection.Emit, so that's what I did.

Implementation

I needed a good way to store all the information needed to create each property. I did this with the Setting object. Setting objects store the property name, the initial value of the property, a description and category to be shown in the PropertyGrid, and an EventHandler so you can catch when the value of the property changes.

Secondly, I wanted to create an easy storage device for my Setting objects. This is what the Settings object is for. It is essentially a wrapper around a HashTable that does the casting for you. The Settings object is what makes the CustomPropertyGrid come to life.

By setting the Settings property of the CustomPropertyGrid object to your own Settings object, an internal type will be created having the properties that were specified as Setting objects. The type will have an internal HashTable to make the emitted code easy to write. For instance, if you have the following code:

C#
Settings settings = new Settings();

settings["GridColor"]=new Setting(
    myGrid.Color,
    "The color of the lines used to draw the grid",
    "Grid");

the type that is created will have a property written like so:

C#
[Description("The color of the lines used to draw the grid")]
[Category("Grid")]
public Color GridColor
{
  get{return (Color)myHash["GridColor"];} 
  set{myHash["GridColor"]=value;}
}

Emitting the bytecode

Doing this was fairly tricky, as this was a learning experience for me. The procedure consisted of writing a small class having the property signature I desired and using ildasm and peverify to examine the bytecodes. From that, I gathered enough information to emit code that works. Working with objects is easy, as there is a bytecode to load and store objects no problem, but value types are another matter. To store a value type, you must box it, and then you can treat it like an object. To retrieve a value type, you must unbox it (naturally) and then you have to load it based on what type it is. If it is a numeric type, there are different bytecodes to load ints, chars, doubles, floats, etc. If you have a value type that is not a numeric type (like a Color, or an enum) then it's just a matter of loading the type of the object. Figuring out how to load the value types was the hardest part of this project for me to figure out.

C#
private void emitProperty(
    TypeBuilder tb,
    FieldBuilder hash,
    Setting s,
    string name) 
{ 
    //to figure out what opcodes to emit, i would compile a small class 
    //having the functionality i wanted, and viewed it with ildasm. 
    //peverify is also kinda nice to use to see what errors there are. 
  
    //define the property first 
    PropertyBuilder pb = tb.DefineProperty(
        name,
        PropertyAttributes.None,
        s.Value.GetType(),
        new Type[] {}); 
    Type objType = s.Value.GetType(); 

    //define the get method for the property 
    MethodBuilder getMethod = tb.DefineMethod(
        "get_"+name,
        MethodAttributes.Public,
        objType,
        new Type[]{}); 

    ILGenerator ilg = getMethod.GetILGenerator(); 
    ilg.DeclareLocal(objType); 
    ilg.Emit(OpCodes.Ldarg_0); 
    ilg.Emit(OpCodes.Ldfld,hash); 
    ilg.Emit(OpCodes.Ldstr,name); 
    ilg.EmitCall(OpCodes.Callvirt,
        typeof(Hashtable).GetMethod("get_Item"),
        null); 

    if(objType.IsValueType) 
    { 
        ilg.Emit(OpCodes.Unbox,objType); 
        if(typeHash[objType]!=null) 
            ilg.Emit((OpCode)typeHash[objType]); 
        else ilg.Emit(OpCodes.Ldobj,objType); 
    } 
    else 
        ilg.Emit(OpCodes.Castclass,objType);
 
    ilg.Emit(OpCodes.Stloc_0); 
    ilg.Emit(OpCodes.Br_S,(byte)0); 
    ilg.Emit(OpCodes.Ldloc_0); 
    ilg.Emit(OpCodes.Ret); 

    //now we generate the set method for the property 
    MethodBuilder setMethod = tb.DefineMethod(
        "set_"+name,
        MethodAttributes.Public,
        null,
        new Type[]{objType}); 

    ilg = setMethod.GetILGenerator(); 
    ilg.Emit(OpCodes.Ldarg_0); 
    ilg.Emit(OpCodes.Ldfld,hash); 
    ilg.Emit(OpCodes.Ldstr,name); 
    ilg.Emit(OpCodes.Ldarg_1); 

    if(objType.IsValueType) 
        ilg.Emit(OpCodes.Box,objType); 
    ilg.EmitCall(OpCodes.Callvirt,
        typeof(Hashtable).GetMethod("set_Item"),
        null); 

    ilg.Emit(OpCodes.Ret); 

    //put the get/set methods in with the property 
    pb.SetGetMethod(getMethod); 
    pb.SetSetMethod(setMethod); 
}

Once all the properties are emitted, I use reflection to create an object of that type and set the SelectedObject property to this new object. When the form is shown, all the properties and their values are in a categorized fashion.

There is another custom property in the CustomPropertyGrid called InstantUpdate. By default, InstantUpdate is on and what it does is, when a value is changed in the PropertyGrid, an event is fired, the CustomPropertyGrid catches the event and passes it along to the event in the specific Setting object. The demo program included shows three different ways of handling these events. If you do not wish these events to be fired, just set the InstantUpdate property to false. Also, if you do not construct a Setting with an EventHandler, the event will not be fired regardless of the value of InstantUpdate.

History

Update: Minor text changes/grammar.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Software Developer (Senior)
United States United States
Graduate of the Computer Science department at the University of Arizona

Comments and Discussions

 
GeneralSubproperty Pin
viciouskinid3-Apr-10 15:36
viciouskinid3-Apr-10 15:36 
GeneralSubproperties Pin
Heptagonal29-Feb-08 13:15
Heptagonal29-Feb-08 13:15 
QuestionNewbie - Confused Pin
mcampster19-Sep-07 2:23
mcampster19-Sep-07 2:23 
AnswerRe: Newbie - Confused Pin
Christopher Stratmann5-Nov-07 2:56
Christopher Stratmann5-Nov-07 2:56 
NewsUpdate Pin
Christopher Stratmann12-Sep-07 16:35
Christopher Stratmann12-Sep-07 16:35 
Questionreadonly property Pin
xupi13-Jul-07 2:46
xupi13-Jul-07 2:46 
AnswerRe:resolved -> readonly property Pin
xupi13-Jul-07 3:05
xupi13-Jul-07 3:05 
GeneralComment..... Pin
Christopher Stratmann4-Jun-07 8:00
Christopher Stratmann4-Jun-07 8:00 
GeneralRe: Comment..... Pin
Ben Ratzlaff22-Jun-07 5:42
professionalBen Ratzlaff22-Jun-07 5:42 
GeneralHide GdiVerticalFont and GdiCharSet from Font in Property Grid Pin
Piotr Jozwiak28-Jan-10 20:56
Piotr Jozwiak28-Jan-10 20:56 
NewsAnother Update needed. Pin
Christopher Stratmann25-Apr-07 6:06
Christopher Stratmann25-Apr-07 6:06 
NewsSuggestions Pin
Christopher Stratmann13-Apr-07 7:39
Christopher Stratmann13-Apr-07 7:39 
GeneralICustomTypeDescriptor Pin
Heath Stewart7-Nov-05 20:11
protectorHeath Stewart7-Nov-05 20:11 
GeneralRe: ICustomTypeDescriptor Pin
Ben Ratzlaff9-Nov-05 9:53
professionalBen Ratzlaff9-Nov-05 9:53 
GeneralRe: ICustomTypeDescriptor Pin
Christopher Stratmann25-Apr-07 4:21
Christopher Stratmann25-Apr-07 4:21 
GeneralUsing dynamic Enums Pin
Sampath Narayanan6-Nov-05 20:20
Sampath Narayanan6-Nov-05 20:20 
GeneralRe: Using dynamic Enums Pin
Christopher Stratmann25-Apr-07 3:51
Christopher Stratmann25-Apr-07 3:51 
GeneralAdjusting read access at run time for diffrence permission Pin
IanJamesWood[]7-Sep-05 5:08
IanJamesWood[]7-Sep-05 5:08 
GeneralHiding Properties in PropertyGrid Pin
Varun Gulati2-Jan-05 23:34
Varun Gulati2-Jan-05 23:34 
GeneralRe: Hiding Properties in PropertyGrid Pin
Christopher Stratmann25-Apr-07 3:50
Christopher Stratmann25-Apr-07 3:50 
GeneralWhy am i getting Key as the Property name and ICollection as the value Pin
jen0s22-Nov-04 5:43
jen0s22-Nov-04 5:43 
GeneralAdding attributes for PropertyGrid's use Pin
Anonymous17-Sep-04 16:40
Anonymous17-Sep-04 16:40 
GeneralRe: Adding attributes for PropertyGrid's use Pin
Ben Ratzlaff17-Sep-04 18:06
professionalBen Ratzlaff17-Sep-04 18:06 
GeneralRe: Adding attributes for PropertyGrid's use Pin
Anonymous21-Sep-04 7:20
Anonymous21-Sep-04 7:20 
GeneralRe: Adding attributes for PropertyGrid's use Pin
Ben Ratzlaff23-Sep-04 6:54
professionalBen Ratzlaff23-Sep-04 6:54 

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.