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