Click here to Skip to main content
Click here to Skip to main content

Populating a PropertyGrid using Reflection.Emit

By , 30 Jan 2004
 

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:

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:

[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.

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 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

About the Author

Ben Ratzlaff
United States United States
Member
Graduate of the Computer Science department at the University of Arizona

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralSubpropertymemberviciouskinid3 Apr '10 - 15:36 
Is there a way I can hide the sub property S in the property grid at runtime? I am using ExpandableObjectConverter to show all the subproperties. Thanks for the help.
 
public class CustomClass
{
CustomClassSubProperty m_ccsp = new CustomClassSubProperty();
public CustomClassSubProperty M_ccsp
{
get { return m_ccsp ; }
set { m_ccsp = value; }
}
}
 
public class CustomClassSubProperty
{
string m_s = "Hide Me";
public string S
{
get { return m_s; }
set { m_s = value; }
}
 
string m_a = "Show Me";
public string A
{
get { return m_a; }
set { m_a = value; }
}
 
}
GeneralSubpropertiesmemberHeptagonal29 Feb '08 - 13:15 
Hello!
 
Thank you for the great work on this control Smile | :) I wonder though, how would I add sub properties in my control? Does it require a lot of "hacking"?
 
Regards,
Martin
QuestionNewbie - Confusedmembermcampster19 Sep '07 - 2:23 
If you can imagine a listbox on the left, listing all of the objects within my application. On the right I want a propertygrid displaying the fields and their values of the object that is highlighted.
 
I tried to use the PropertyGrid control, however I want it to display the fields within an object, not the properties. I differentiate the two as properties have a set get method, whereas my fields do not.
 
Is this custompropertygrid what I'm looking for or is there a much simpler way of doing this? And if it is, how do I go about implementing it? I've added the code to my app, created a settings object containing my fields names and values however then I get stuck. If I set this settings object as the selectedObject of my custompropertygrid, nothing appears.

AnswerRe: Newbie - Confusedmemberchris1755 Nov '07 - 2:56 
You could use this property grid to accomplish what you are trying to do.
 

mcampster wrote:
Is this custompropertygrid what I'm looking for or is there a much simpler way of doing this?

I really dont think there is any simpler way to do what you want unless you make your fields into properties and then place your selected object onto the properties grid. That would totally defeat the purpose of using 'this' property grid because you could easily use the standard properties grid to do that.
 

mcampster wrote:
And if it is, how do I go about implementing it?

There is a sample project which shows you how to accomplish this.
 
mcampster wrote:
If I set this settings object as the selectedObject of my custompropertygrid, nothing appears.

That is because there is a "settings" property on the property grid which your are to place your selected object on. Do not use the "selectedObject" property. At least, I think thats how to do it.
 

 
Chris
NewsUpdatememberchris17512 Sep '07 - 16:35 
I just wanted to let you know that anything is possible with you code...
 
1. Read-Only Properties
2. Editors, Converters, etc... Basically any attribute you place on a property.
3. Updating (With the ability to cancel) and Updated events when properties are changed within the property grid control.
4. When properties are changed programmically they are sent to the property grid for updating there as well.
5. Total globalization of tip text on property grid toolbar.
6. Ability to remove the "Property Pages" toolbar button.
7. Ability to have properties with sub properties.
8. The ability to have sub properties with the same name as base properties which was hard to figure out. Ex: Having Class1 with properties 'Size', 'Location', 'Font'. Then there is a Class2 which is Class1's 'Font' property. Then in Class2 there is a 'Size' property as well.
9. Having a Font class which mimics the windows font class but is also globalized.
9. Another upgrade which I think was very cool was getting rid of the Setting and Settings property on your property grid control. Then overriding the SelectedObject and SelectedObjects class and the doing something special if the object is a Setting class.
 
Chris
Questionreadonly propertymemberxupi13 Jul '07 - 2:46 
Hi I'm using your customproperty grid and it works fine for me, but now I want to make a 'ReadOnly' property.
 
How can I make it?
 
thanks
AnswerRe:resolved -> readonly propertymemberxupi13 Jul '07 - 3:05 
Hi,
finally I resolved my problem.
 
I added an attribute "Enabled" in the setting class and the get/set method
and after
in the emitProperty function I changed this:
 

pb.SetSetMethod(setMethod);
pb.SetGetMethod(getMethod);

 
for this
 

if (s.Enabled != false) {pb.SetSetMethod(setMethod);}
 
pb.SetGetMethod(getMethod);

 

Now you have your property in readonly mode
GeneralComment.....memberchris1754 Jun '07 - 8:00 
After taking a lot of time and modifing your work. I have come to the conclusion that almost anything is possible with your framework. I have been successful in using your code to do the following.....
 
1. Create a custom Font class that has the look and feel like placing the font object on the property grid except the GDICharSet and GdiVerticalFont properties have been removed. This includes having the font property look like an expandable tree node.
2. Create the ability to put custom TypeConverters and Editors for certain properties.
3. The ability to have update events sent back to the calling class. Ex: I have a property in the property grid called "MyFont". I update the Strikeout to be true under the property "MyFont". After doing so an event is fire and received in my "CustomClass" which I can then set the font for "CustomClass" to be strikeout.
4. Dynamically Loading Assemblies that contain classes that use your framework still works!!!!. Ex: Project2 references Project1. Project1.exe load up a class in Project2.dll and loads it onto the properties grid in Project1.exe.
 

The only thing my version of your framework that I am having trouble with is two or more properties with the same name. Other than that your frameworks works VERY VERY nicely.
 
Chris
GeneralRe: Comment.....memberBen Ratzlaff22 Jun '07 - 5:42 
Ahh, replies!
 
Sorry, I haven't been able to incorporate your updates just yet, but it's on my list of things to do!
 
Glad you find this useful =)
GeneralHide GdiVerticalFont and GdiCharSet from Font in Property GridmemberPiotr Jozwiak28 Jan '10 - 20:56 
Hi,
To hide any property grid item in for ex. Font you must to write yours own TypeConverter like this one:
 
using System.Drawing;
using System.ComponentModel;
 
public class FilteredFontConverter : FontConverter
{
      public override PropertyDescriptorCollection GetProperties(
                                             ITypeDescriptorContext context, 
                                             object value, 
                                             Attribute[] attributes)
      {
         //in this method you can collect all properties that should be 
         //visible in property grid. So we can also remove unwanted items

         PropertyDescriptorCollection props = base.GetProperties(context, value, attributes);
         PropertyDescriptor p = props.Find("GdiCharSet", true);
         if (p != null)
            props.Remove(p);
         p = props.Find("GdiVerticalFont", true);
         if (p != null)
            props.Remove(p);
         return props;
      }
 
      public override bool GetPropertiesSupported(ITypeDescriptorContext context)
      {
         //here you can return true for displaying subitems or false
         //if you want hide all subitems
         return true;
      }
}
 

And at the and we must add attribute to our Font property in class that is used for property grid:
 

     
     [TypeConverter(typeof(FilteredFontConverter ))]
     public Font Font
     {
         get { return m_Font; }
         set { m_Font = value;}
     }

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 31 Jan 2004
Article Copyright 2004 by Ben Ratzlaff
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid