Click here to Skip to main content
Click here to Skip to main content
Go to top

Dynamic Properties for PropertyGrid

, 3 Nov 2011
Rate this:
Please Sign up or sign in to vote.
How to get dynamic behavior out of the PropertyGrid control.

Introduction

In this article, I will discuss and show how to get dynamic behavior out of the PropertyGrid control. I would emphasize the word "dynamic" here, and also "dynamic" here means the ability to do things at run-time. Some of the stuff here can also be done using code if you have enough knowledge of the extensibility mechanism of .NET. Dynamic or not, this solution will make your life simpler when it comes to controlling the PropertyGrid control.

This article assumes you have some knowledge of the following:

Don't be nervous if you are not familiar with the above classes, simply read the first two articles in the reference section. That will bring you up-to-speed. All-in-all, this solution encapsulates usage of these classes, so you will work with more plain field by using this solution. Using this solution, you can achieve the following functionalities at run-time:

  • Create properties at run-time and add it to your objects.
  • Get property change notifications to your code for properties created at run-time.
  • Show/hide any property.
  • Enable/disable any property.
  • Show/hide any member of an enumerated data type, including .NET's built-in enumerations.
  • Enable/disable any member of an enumerated data type, including .NET's built-in enumerations.
  • Add custom display name to any member of an enumerated data type, including .NET's built-in enumerations.
  • Add custom description to any member of an enumerated data type, including .NET's built-in enumerations.
  • Provide editor for enumeration data types that has System.FlagsAttribute.
  • Sort properties and/or categories in ascending or descending order using their names or by their IDs (discussed later).
  • Localize your property name, category name, property description, enumeration name, and enumeration description (including .NET's built-in enumerations).
  • A property with Boolean type can show Yes/No or any other display string instead of just True/False.
  • A property with Boolean type can be localized.
  • Show/Hide one or more state icons for properties along with tool tips. Icon size must by 8 pixels by 8 pixels. This is useful to visualize the state of the property (i.e., invalid data). You can show multiple state icons simultaneously. Icons are shown on the right of the property name on the same line of the property on the PropertyGrid.
  • Show value icon for a property value.
  • Show Collection Item as child properties. Any property of type of IEnumerable can be expanded as child properties.

All these can be done at runtime (as well as during coding). During my search, I found some articles which I have listed at the end of this article. They have few of these features in limited form. What I tried to do with my solution was to make a generic solution for the developer so she/he can tell the PropertyGrid what to display, when to display, and how to display from within their class.

Using the code

The sample source code provided in this article contains two C#, VS2008 projects:

Many features have been demonstrated on the sample application in the article. To cover them all in writing would be difficult task, rather one can get the idea by looking at the code. But some features may need some explanation. Those features will be discussed here in a questions and answers format.

Q1: How do I sort properties and categories?

Let's consider this simple class.

public class MyClass()
{
  private DynamicCustomTypeDescriptor m_dctd = null;

  public MyClass()
  {
    m_dctd = ProviderInstaller.Install(this);
    m_dctd.PropertySortOrder = CustomSortOrder.AscendingByName;
    m_dctd.CategorySortOrder = CustomSortOrder.DescendingById;
  }

  [Category("CatA")]
  [DisplayName("PropertyA")]
  [Description("Description of PropertyA")]
  [Id(3, 1)]  //here PropA gets an ID 3 and CatA gets in ID 1 as well.
  public int PropA{...}
  
  [Category("CatB")]
  [DisplayName("PropertyB")]  
  [Description("Description of PropertyB")]
  [Id(2, 2)]  // here PropB gets an ID 2 and CatA gets in ID 2 as well.
  public int PropB{...}
  
  [Category("CatC")]
  [DisplayName("PropertyC")]  
  [Description("Description of PropertyC")]
  [Id(1, 3)]  // here PropC gets an ID 1 and CatA gets in ID 3 as well.
  public bool PropC{...}
  
  [Category("CatC")]
  [DisplayName("PropertyD")]  
  [Description("Description of PropertyD")]
  [Id(4, 3)]  //here PropD gets an ID 4 and CatA gets in ID 3 as well.
  public int PropD{...}

}

In the constructor of the class, we are saying that we would like to sort the properties by name in ascending order and sort categories by ID in descending order.

CustomSortOrder is an enumeration:

public enum CustomSortOrder
{
    // no custom sorting
    None,
    // sort asscending using the property name or category name
    AscendingByName,
    // sort asscending using property id or categor id
    AscendingById,
    // sort descending using the property name or category name
    DescendingByName,
    // sort descending using property id or categor id
    DescendingById,
}

Note that a property without the IdAttribute is same as a property with [Id(0,0)], and IDs cannot be negative values. The PropertySort property of PropertyGrid effects the sorting. The sample application allows you combine all sorting features through GUI. You experiment with different combination.

Q2: How do I show property names, property description, and category names from satellite assemblies for localizing purpose?

Here we will consider the same class, MyClass, from above. To make our class, it needs information to be be able to construct ResourceManager. For that we use a class level attribute called - ClassResourceAttribute. This attribute has three properties:

  • BaseName (string) - Used in ResourceManager construction.
  • Assembly (string) - If specified, used in ResourceManager construction, otherwise Type.Assembly is used.
  • KeyPrefix (object) - If specified, all resource keys must be prefixed with this string. This allows to create unique keys.
[ClassResource(BaseName="TypeDescriptorApp.Properties.Resources", 
         KeyPrefix="MyClass_", Assembly="")]
public class MyClass()
{
....
}

Note that all keys are case-sensitive.

Let's consider the property PropA from our sample class. For display name, PropertyDescriptorManager will search for a string in the following order:

  • Look for a resource string in the target resource file with key MyClass_PropA_Name. The format is: <KeyPrefix>_<PropertyName>_Name.
  • Use the DisplayNameAttribute if it exists. In our case, it will be "PropertyA".
  • Use the property name itself. In our case, it is "PropA".

For the description string of the property PropA, PropertyDescriptorManager will search for a string in the following order:

  • Look for a resource string in the target resource file with key MyClass_PropA_Desc. The format is: <KeyPrefix>_<PropertyName>_Desc.
  • Use the DescriptionAttribute if it exists. In our case, it will be "Description of PropertyA".
  • Otherwise, the description will be blank.

For the category string of property PropA, PropertyDescriptorManager will search for a string in the following order:

  • Look for a resource string in the target resource file with key MyClass_Cat3. The format is: <KeyPrefix>_Cat<CategoryID>.
  • Use the CategoryAttribute if it exists. In our case, it will be "CatA".
  • Otherwise, use the default category, which is "Misc".

Q3: How do I change the property display name, category string, and description string at run-time and also how do I show/hide/enable/disable a property at run-time?

Here we will consider the same class, MyClass, from above. We will modify the constructor to modify some attributes of the property PropA.

public MyClass()
{
    m_dctd = ProviderInstaller.Install(this);
    m_dctd .PropertySortOrder = CustomSortOrder.AscendingByName;
    m_dctd .CategorySortOrder = CustomSortOrder.DescendingByName;
    
    // now lets modify some attribute of PropA
    CustomPropertyDescriptor cpd = m_pdm.GetProperty("PropA");
    cpd.SetDisplayName("New display name of PropA");
    cpd.SetDescription("New description of PropA");
    cpd.SetCategory("New Category of PropA");
    cpd.SetIsReadOnly(true); // disables the property
    cpd.SetIsBrowsable(true);  // hides the property
    cpd.CategoryID = 4;
}

Q4: I have a property of type Int32. This property represents the customer ID. In my database, I have a list of customer names and IDs. I would like for the PropertyGrid to show a drop-down box with customer names and when users pick one from the list, I would like the PropertyGrid to assign the ID to the property instead of the name. How do I do this?

Here we will consider the same class again, MyClass, from above. We will modify the constructor to provide a lookup drop-down box for PropA.

public MyClass()
{
    m_dctd = ProviderInstaller.Install(this);
    m_dctd.PropertySortOrder = CustomSortOrder.AscendingByName;
    m_dctd.CategorySortOrder = CustomSortOrder.DescendingByName;

    CustomPropertyDescriptor cpd = m_pdm.GetProperty("PropA");
    PopululateDropDownListFromDatabaseSource(cpd);
}

private void PopululateDropDownListFromDatabaseSource( CustomPropertyDescriptor cpd )
{      

    cpd.StatandardValues.Clear( );
    string[] arrNames = {"Adam", "Brian", 
        "Russel", "Jones", "Jakob"};
    for (int i = 101; i < 106; i++)
    {
        StandardValueAttribute sva = 
           new StandardValueAttribute(arrNames[i - 101] + 
           " ("+ i.ToString() + ")", i);
        // you can also add an description for any StandardValueAttribute
        sva.Description = "Description of " + sva.DisplayName + ".";
        cpd.StatandardValues.Add(sva);
    }
}

Notice that we have included a description as well. But when you run the code, you will not see the description. Because to show description, you will need a special UITypeEditor. So to make the property PropA to display the description, we will have to add the following attribute to PropA:

[Editor(typeof(StandardValueEditor), typeof(UITypeEditor))]

StandardValueAttribute has these properties:

  • DisplayName (string) - What gets displayed on the screen.
  • Value (object) - The actual value. The actual type of this value must match the type of the property.
  • Description (string) - Description of the value. This requires the StandardValueEditor to have an effect.
  • Visible (bool) - Indicates whether or not to show the value.
  • Enabled (bool) - Indicates whether or not to enable the value. This requires the StandardValueEditor to have an effect.

Q5: In the beginning of this article, you said we can manipulate any member of an enumeration data type, how do I this?

Let's consider this following enumeration type and class:

[EnumResource("TypeDescriptorApp.Properties.Resources")]
[Editor(typeof(StandardValueEditor), typeof(UITypeEditor))]
public enum Position
{
  [StandardValue("First", Description = "Excellen.")]
  One
  [StandardValue("Second", Description = "Good.")]
  Two    

}

[ClassResource("TypeDescriptorApp.Properties.Resources", 
         KeyPrefix="MyEnumClass1_")]
public class MyEnumClass1()
{
  private DynamicCustomTypeDescriptor m_dctd = null;
  public MyEnumClass()
  {
    m_dctd = ProviderInstaller.Install(this);
  }

  public Position PropE{...}
  
  public Position PropF{...}  
}

public class MyEnumClass2()
{
  private DynamicCustomTypeDescriptor m_dctd = null;
  public MyEnumClass()
  {
     m_dctd = ProviderInstaller.Install(this);
  }
  public Position PropG{...}  
}

Note that we are applying StandardValueAttribute on each field of the enumeration. So each field becomes an standard-value, thus you can use the properties of the StandardValueAttribute to show/hide/enable/disable any member of the enumeration. On top of all that, enumeration members can be localized. As always, to localize your class or enum, you have to add the attribute ResourceBaseNameAttribute on the enumeration itself and/or on the class that uses the enumeration. If you apply on both, the class has higher priority than the enumeration. This allows you to override resource information of this enumeration type that is defined in another library that you do not have access to in the source code. You can also override the enumeration resource information at the property level as well which has the highest priority.

Let's create a string table in a resource file that looks like this:

Key Value Comment
Position_One_Name MyString1 enumeration level (priority = 3). Format: <KeyPrefix><Enum FieldName>_Name
Position_One_Desc MyString2 enumeration level (priority = 3). Format: <KeyPrefix><Enum FieldName>_Desc
Position_Two_Name MyString3 enumeration level (priority = 3)
Position_Two_Desc MyString4 enumeration level (priority = 3)
MyEnumClass1_Position_One_Name MyString5 class level override (priority = 2). Format: <KeyPrefix><Enum Name>_<Enum FieldName>_Name
MyEnumClass1_Position_One_Desc MyString6 class level override (priority = 2). Format: <KeyPrefix><Enum Name>_<Enum FieldName>_Desc
MyEnumClass1_Position_Two_Name MyString7 class level override (priority = 2)
MyEnumClass1_Position_Two_Desc MyString8 class level override (priority = 2)
MyEnumClass1_PropE_One_Name MyString9 property level override (priority = 1). Format: <KeyPrefix><Property Name>_<Enum FieldName>_Name
MyEnumClass1_PropE_One_Desc MyString10 property level override (priority = 1). Format: <KeyPrefix><Property Name>_<Enum FieldName>_Desc
MyEnumClass1_PropE_Two_Name MyString11 property level override (priority = 1)
MyEnumClass1_PropE_Two_Desc MyString12 property level override (priority = 1)

Note: 1 is the highest priority and 3 is the lowest priority.

PropE will use the following keys:

  • MyEnumClass1_PropE_One_Name
  • MyEnumClass1_PropE_One_Desc
  • MyEnumClass1_PropE_Two_Name
  • MyEnumClass1_PropE_Two_Desc

Because the enumeration has been overridden at property level.

PropF will use the following keys:

  • MyEnumClass1_Position_One_Name
  • MyEnumClass1_Position_One_Desc
  • MyEnumClass1_Position_Two_Name
  • MyEnumClass1_Position_Two_Desc

Because the enumeration has not been overridden at property for this property, only at class level.

PropG will use the following keys:

  • Position_One_Name
  • Position_One_Desc
  • Position_Two_Name
  • Position_Two_Desc

The enumeration has not been overridden for this class, so it uses what is defined for the enumeration itself.

So as you can see, there is a fallback strategy in place for the enumeration type. That mans you can define all your enumeration in a library, including the resource information, and then another library can use that enumeration as it is or override the resource information as necessary. Just know how to override the keys in the resource.

Q6: I have a Boolean property in my class, I would like to show Yes\No on the PropertyGrid instead of True\False. How do I do this?

Here we will consider the same class, MyClass, from above. PropC is a Boolean type property, so we can do this in the constructor:

public MyClass()
{
  m_dctd = ProviderInstaller.Install(this);
  m_dctd.PropertySortOrder = CustomSortOrder.AscendingByName;
  m_dctd.CategorySortOrder = CustomSortOrder.DescendingByName;
 
  // now lets display Yes/No instead of True/False
  CustomPropertyDescriptor cpd = m_pdm.GetProperty("PropC");
  
  foreach (StandardValueAttribute sva in cpd.StatandardValues)
  {
    if (sva.Value == true) // means it is True part
    {
      sva.DisplayName = "Yes";
    }
    else
    {
      sva.DisplayName = "No";
    }
  }
}

Q7: Can a Boolean property be localized? If so, how?

Yes, a Boolean property can be localized. So you can show "True"/"False" in different languages.

Think of Boolean properties as enumeration properties, like this:

public enum Boolean
{
  True = true;
  False = false;    
}

Note: This above enumeration does not exist in this solution or in .NET.

-- End of Questions and Answers Format--

So, now to localize this, you will have to override the enumeration at class level or property level, or both. Please see Q5 for details.

I hope by now you know how to use this solution. There are other features that are demonstrated in the sample application. If you have questions, feel free to post it here. If you like it, don't forget to vote for it generously, this is the only motivation to get it going.

Points of interest

When you get into the System.ComponentModel.TypeDescriptor class, it is interesting to look at the number of classes that play a role in providing a description for a type.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mizan Rahman

Denmark Denmark
No Biography provided

Comments and Discussions

 
QuestionUsing this for multiple objects PinmemberScruffyDuck27-May-14 2:46 
QuestionAdding Enum On The Fly PinmemberMrGadget19-Nov-12 21:18 
AnswerRe: Adding Enum On The Fly PinmemberMizan Rahman19-Nov-12 23:30 
GeneralRe: Adding Enum On The Fly PinmemberMrGadget20-Nov-12 5:21 
GeneralMy vote of 5 PinmemberMember 18436-Sep-12 11:48 
GeneralMy vote of 5 PinmemberMember 18436-Sep-12 5:16 
SuggestionDelayed the property value display Pinmembergloutonsoft7-Feb-12 21:11 
GeneralRe: Delayed the property value display PinmemberMizan Rahman9-Feb-12 22:44 
AnswerRe: Delayed the property value display Pinmembergloutonsoft10-Feb-12 2:24 
QuestionObject properties (deeper level of localization) Pinmemberjpmoraes2-Jan-12 7:09 
AnswerRe: Object properties (deeper level of localization) PinmemberMizan Rahman26-Jan-12 4:58 
GeneralRe: Object properties (deeper level of localization) Pinmemberjpmoraes26-Jan-12 17:09 
QuestionConverting the project to .NET Framework 4 Pinmemberoriginalbudul31-Oct-11 7:10 
AnswerRe: Converting the project to .NET Framework 4 Pinmemberoriginalbudul1-Nov-11 2:36 
GeneralRe: Converting the project to .NET Framework 4 PinmemberMizan Rahman2-Nov-11 0:47 
QuestionCheckbox instead of Yes / No combobox Pinmemberoriginalbudul26-Oct-11 23:43 
AnswerRe: Checkbox instead of Yes / No combobox PinmemberMizan Rahman28-Oct-11 8:10 
GeneralRe: Checkbox instead of Yes / No combobox Pinmemberoriginalbudul29-Oct-11 3:45 
GeneralSecurity level to edit a property PinmemberMember 404642930-May-11 23:57 
GeneralNice work!! Pinmembertransparency25-May-11 22:32 
GeneralMy vote of 5 Pinmembergclpixel23-May-11 7:42 
GeneralAdd new Property Pinmemberdjdjoko22-May-11 12:36 
GeneralRe: Add new Property PinmemberMizan Rahman22-May-11 21:53 
GeneralRe: Add new Property Pinmemberdjdjoko23-May-11 5:09 
GeneralRe: Add new Property Pinmemberdjdjoko23-May-11 6:35 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 3 Nov 2011
Article Copyright 2011 by Mizan Rahman
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid