Click here to Skip to main content
15,861,168 members
Articles / Programming Languages / C#

Dynamic Properties for PropertyGrid

Rate me:
Please Sign up or sign in to vote.
4.94/5 (35 votes)
3 Nov 2011CPOL9 min read 161.9K   13K   100   34
How to get dynamic behavior out of the PropertyGrid control.

Image 1

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.

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

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

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

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

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

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

KeyValueComment
Position_One_NameMyString1enumeration level (priority = 3). Format: <KeyPrefix><Enum FieldName>_Name
Position_One_DescMyString2enumeration level (priority = 3). Format: <KeyPrefix><Enum FieldName>_Desc
Position_Two_NameMyString3enumeration level (priority = 3)
Position_Two_DescMyString4enumeration level (priority = 3)
MyEnumClass1_Position_One_NameMyString5class level override (priority = 2). Format: <KeyPrefix><Enum Name>_<Enum FieldName>_Name
MyEnumClass1_Position_One_DescMyString6class level override (priority = 2). Format: <KeyPrefix><Enum Name>_<Enum FieldName>_Desc
MyEnumClass1_Position_Two_NameMyString7class level override (priority = 2)
MyEnumClass1_Position_Two_DescMyString8class level override (priority = 2)
MyEnumClass1_PropE_One_NameMyString9property level override (priority = 1). Format: <KeyPrefix><Property Name>_<Enum FieldName>_Name
MyEnumClass1_PropE_One_DescMyString10property level override (priority = 1). Format: <KeyPrefix><Property Name>_<Enum FieldName>_Desc
MyEnumClass1_PropE_Two_NameMyString11property level override (priority = 1)
MyEnumClass1_PropE_Two_DescMyString12property 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:

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

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


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

Comments and Discussions

 
QuestionAn Error occured on my self_definition Enum Data Type Pin
Jacberg30-May-16 4:06
Jacberg30-May-16 4:06 
QuestionCreate subcategories on the fly? Pin
Member 97573517-Sep-15 22:33
Member 97573517-Sep-15 22:33 
QuestionIMPORTANT NOTICE: This article is obsolete and no longer supported. Use the newer version. Pin
Mizan Rahman25-Oct-14 19:45
Mizan Rahman25-Oct-14 19:45 
QuestionHow to hide entire category? Pin
bltdavid24-Oct-14 14:42
professionalbltdavid24-Oct-14 14:42 
AnswerRe: How to hide entire category? Pin
Mizan Rahman25-Oct-14 19:43
Mizan Rahman25-Oct-14 19:43 
QuestionUsing this for multiple objects Pin
ScruffyDuck27-May-14 2:46
ScruffyDuck27-May-14 2:46 
AnswerRe: Using this for multiple objects Pin
Mizan Rahman21-Oct-14 6:47
Mizan Rahman21-Oct-14 6:47 
QuestionAdding Enum On The Fly Pin
MrGadget19-Nov-12 21:18
MrGadget19-Nov-12 21:18 
AnswerRe: Adding Enum On The Fly Pin
Mizan Rahman19-Nov-12 23:30
Mizan Rahman19-Nov-12 23:30 
GeneralRe: Adding Enum On The Fly Pin
MrGadget20-Nov-12 5:21
MrGadget20-Nov-12 5:21 
GeneralMy vote of 5 Pin
Member 18436-Sep-12 11:48
Member 18436-Sep-12 11:48 
GeneralMy vote of 5 Pin
Member 18436-Sep-12 5:16
Member 18436-Sep-12 5:16 
SuggestionDelayed the property value display Pin
gloutonsoft7-Feb-12 21:11
gloutonsoft7-Feb-12 21:11 
GeneralRe: Delayed the property value display Pin
Mizan Rahman9-Feb-12 22:44
Mizan Rahman9-Feb-12 22:44 
AnswerRe: Delayed the property value display Pin
gloutonsoft10-Feb-12 2:24
gloutonsoft10-Feb-12 2:24 
QuestionObject properties (deeper level of localization) Pin
jpmoraes2-Jan-12 7:09
jpmoraes2-Jan-12 7:09 
AnswerRe: Object properties (deeper level of localization) Pin
Mizan Rahman26-Jan-12 4:58
Mizan Rahman26-Jan-12 4:58 
GeneralRe: Object properties (deeper level of localization) Pin
jpmoraes26-Jan-12 17:09
jpmoraes26-Jan-12 17:09 
QuestionConverting the project to .NET Framework 4 Pin
originalbudul31-Oct-11 7:10
originalbudul31-Oct-11 7:10 
AnswerRe: Converting the project to .NET Framework 4 Pin
originalbudul1-Nov-11 2:36
originalbudul1-Nov-11 2:36 
GeneralRe: Converting the project to .NET Framework 4 Pin
Mizan Rahman2-Nov-11 0:47
Mizan Rahman2-Nov-11 0:47 
QuestionCheckbox instead of Yes / No combobox Pin
originalbudul26-Oct-11 23:43
originalbudul26-Oct-11 23:43 
AnswerRe: Checkbox instead of Yes / No combobox Pin
Mizan Rahman28-Oct-11 8:10
Mizan Rahman28-Oct-11 8:10 
GeneralRe: Checkbox instead of Yes / No combobox Pin
originalbudul29-Oct-11 3:45
originalbudul29-Oct-11 3:45 
GeneralSecurity level to edit a property Pin
Troopers30-May-11 23:57
Troopers30-May-11 23:57 

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.