In Depth Banner
Skip Navigation Links

Select your preferred language

Customizing properties for the PropertyGrid

If you ever create applications in which the user has to edit items they have selected, such as graphical elements in a drawing package or objects in a list view, you'll probably have used the PropertyGrid control as a UI element to speed development of what would otherwise be a very complex task.

Sometimes however, the property grid makes the user interface look a little too much like a programmers tool for the likes of some non code-aware users. For example, you cannot create a property called "Background Colour" the best you'd be able to do was "BackgroundColour" relying on camel casing or perhaps underscores to break the words up.

Another requirement might be to show only a limited number of properties or even change the visibility of properties from moment to moment depending on user selections. For example, if a graphical object has a solid fill you might not want the property grid to show you the same colour options as you would if the object had a gradient fill.

The PropertyGrid uses reflection to get at all the inner workings of a class and enable the user to edit the properties. It also uses the attributes system to help control that process for example only displaying properties that have the Browsable(true) attribute. This is where reflection, attributes and the type system really come into their own because it enables you to enhance the PropertyGrid even more.

When an object is selected, the PropertyGrid uses the type system to interrogate the object and get a list of all it's browsable properties. If we intervene at this point we can change the characteristics of the properties and even which ones are seen.

For the first part of this article, the properties returned will be given a friendly name that can be any string, then in the second half we'll look at how to filter properties dynamically so that the content of an object can change on-the-fly.

Friendly names for properties.

The TypeDescriptor base class can reflect all the members of a class and more specifically, retrieve its properties as a PropertyDescriptorCollection containing one or more PropertyDescriptor objects. If the class needs to return customized reflection information the class must implement the ICustomTypeDescriptor interface. This interface enables you to modify the reflection information returned whenever a class is examined.

A PropertyDescriptor contains information about the type of property, it's name , it's display name which can be different from it's real name, the type converters used for the property, what attributes the property has and some other information that is not important to this discussion. To change the way a property is displayed a mechanism for marking a property in some way is needed.

The framework provides attributes for this purpose. Attributes are a way of tagging classes, properties, methods and events with information, known as metadata, before the class is compiled. Once it is compiled to code, those attributes can be read but not modified by the runtime system. Attributes that enable new metadata, such as a FreindlyNameAttribute can be created and used to tag all the properties we want to display differently.

Listing 1 shows the FriendlyNameAttribute.

Now that the properties may be tagged with friendly names, the property descriptors passed to the PropertyGrid need to be able to use that information. The PropertyDescriptor returned from the standard TypeDescriptor.GetProperties method knows nothing of the new attribute and had read-only properties so it's not possible to intercept or override the information. Instead, a wrapper class, based on PropertyDescriptor and containing the original PropertyDescriptor is used. Whenever information is read from the derived property descriptor, it passes on the information in the inner descriptor except where the DisplayName property is concerned. That returns the contents of the attribute created for the purpose.

Listing 2 shows the FriendlyNamePropertyDescriptor.

The classes that are to be enhanced in the PropertyGrid must know how to return the correct information, more specifically, the newly enhanced FriendlyNamePropertyDescriptor. To do this, the class must implement ICustomTypeDescriptor. This interface provides methods for returning custom metadata whenever a class is interrogated. Like the FriendlyNamePropertyDescriptor, the implementation used in this example will return most of it's information simply by passing on that of the standard TypeDescriptor. When it comes to properties however, a collection of FriendlyNamePropertyDescriptors is assembled and returned instead. The PropertyGrid will then show the contents of the DisplayName property which is tagged with the FriendlyNameAttribute.

Listing 3 shows a base class implementing ICustomTypeDescriptor and another which returns the custom property descriptor collection.

Note how in listing 3, the parameters of the TypeDescriptor methods include a boolean that prevents the TypeDescriptor from searching for custom type descriptor implementations. This enables the TypeDescriptor to return standard information and, perhaps more importantly, avoid a stack overflow when the method repeatedly calls itself.

The DemoClass shown in listing 3 can now be displayed in the PropertyGrid. Figure1 shows how the friendly name is a vast improvement over camel-cased names.

Figure 1: A class with modified properties in the property grid.

Dynamic properties.

Many people ask whether it's possible to add or remove properties at runtime dependant on the selection in another property. For example, if the enumeration property A contains the value 0 then display properties X and Y. If property A contains 1 then display properties P and Q. This is possible using dynamic property filtering. This technique relies on custom attributes again.

In this example, an attribute is created that enables you to select a property name and a value, The property name is used to specify a property that contains the trigger condition for showing or filtering the attributed property and the value is the one used to enable the attributed property to be seen.  

Listing 4 shows the DynamicPropertyFilterAttribute.

Once again, an ICustomTypeDescriptor is employed to enable the DynamicPropertyFilterAttribute to enable the functionality. In this instance, the type descriptor implementation does the following;

  • Gets a list of standard properties from the standard TypeDescriptor

  • Examines the attributes of the property to see if it contains the DynamicPropertyFilterAttribute. If it does not, the property is added to the final list of properties. If it does the property nominated in the attribute is examined to get it's value. If that value corresponds to one of the values in the ShowOn property of the attribute then the current property is added to the list of final poperties, otherwise, it's excluded.

  • The list of final properties is returned to the PropertyGrid.

Listing 5 shows the ICustomTypeDescriptor implementation used in the demonstration. Note how once again, the GetProperties method calls a specialized routine that does the filtering as explained.

Finally the class which is being filtered is created. It has a non-dynamic property which is used as the trigger to filter the other properties according to the values in the attribute.

Listing 6 shows the filter demo class.

Figure 2 shows the property filtering system in action.

Figure 2: Dynamic property filtering in action.

Summary

This article has shown you how to change the way that properties are viewed to present a less confusing interface for a non-programmer user. This makes the powerful PropertyGrid a more appealing user interface component. Remember that the properties hidden do not go away so they are still accessible by code no matter what your filter settings, The Friendly Names do not change the property names either so you're code will not be affected. The techniques presented here can be extended to events too with a little modification.

This article sprang from a real world scenario. One customer for whom I did some work a while ago liked the property grid but wanted what he dubbed as a "Fisher-Price" interface. This is the result.

Acknowledgements

I'd like to thank Jacob Grass for the inspiration for the friendly name implementation. I adapted this from one that he created.


Return to the main index

Copyright © Bob Powell 2003-2009. All rights reserved