Nancy Street

PropertyGrid Control

Click to see the Site Map
HomeInfoMusicGalleryPetsGeoHobbiesGeo
Site MapWhat's NewRecent ChangesContactsServer StatisticsSite Information Home « Computers « DevBlog

Back to: Development Blog Contents

If you have an application with lots of configurable "options", then consider using the PropertyGrid control. This often overlooked control in the .NET Framework can be a treasure if used wisely.


Sample Options dialog from my Onyx MP3 tag editor

Preamble

In late March 2004, two .NET Forms applications I was creating started to have so many options that the tabbed dialogs containing the options were getting quite cluttered and difficult to maintain. Each time I added a new option I had to create a new control in the appropriate tab, populate it if necessary, rearrange the controls to keep the appearance "nice", add additional code to move the data to-and-from the source, and then perform validation. Programmers from the C++/MFC days will be familiar with this task where the DDX macros supposedly helped, but in reality options dialogs could become quite messy.

My .NET dialogs were becoming just as messy, so I considered writing a parameter-driven generalised options dialog based upon a tree on the left that groups options into categories by nodes, and a matching set of controls on the right that would flip their appearance according to what was selected. This used to be a popular technique in many large programs such as Mathematica and Netscape. However, there is still a lot of potential coding involved, so I stumbled across a better idea...

PropertyGrid Control

Never wanting to rewrite the wheel in my old age, I wondered if I could reuse the "property grid" control that is familiar to most VB and Visual Studio developers. I had never seen this control used in an application until NDoc came along, and I figured that if they use the property control then perhaps I could too. To be honest, I had seen a sample application in the MSJ magazine years ago that used the same control, but after taking 40 issues of the magazine to bed with me one night I failed locate the old article with sample code.

This control is so obscure that it took me almost an hour to determine exactly which class it was: PropertyGrid. The delay was mostly caused by lack of documentation and the huge number of similarly named classes.

After web searching for almost an hour and finding lots of jumbled and confusing articles I stumbled across this one in the Microsoft MSDN site:

Getting the Most Out of the .NET Framework PropertyGrid Control

Start there - This approx 20 printed page article is a bit simplistic for serious developers, but it will quickly get you into the feel of the control and how it's supposed to work.

Basic Use

In a nutshell: Create a class full of "options", give a Property to each option, apply special [Attributes] to the properties, then attach the options class to the property grid via the SelectedObject property. Using the magic of reflection, the PropertyGrid control will inspect each property in the SelectedObject assigned class and populate the control accordingly. Basic types such as bool, int, enum, Color, Font, Rectangle, etc will automatically be interpreted and appear in a familiar way in the property grid.

AppOptions options = mainApp.GetOptions();
propGrid.SelectedObject = options;

The [Attributes] on each property control the category groups, the default values and descriptions of the properties. By simply coding a few attributes over some public class properties you can create a stunning effect with lists of options in category groups with drop-down lists and basic validation rules. These are most useful attributes:

[DefaultProperty]
[Description]
[Category]
[Browsable]
[DefaultValue] 

Extended Use

Serious PropertyGrid users will quickly find they need to extend the basic behaviour by setting properties for fancier classes. You will need to become familiar with the TypeConverter class and derived XXXConverter classes in the System.ComponentModel namespace. The MSDN article previously mentioned contains a reasonably good introduction.

The default behaviour of some of the derived converters is not very useful. For example, when I presented a byte array as a property I received a string of hex digits with a pop-up to a Byte Array Property Editor dialog which doesn't seem to have any editing abilities at all, it just displays the bytes. You can expand the property and edit the numeric value of each byte but you can't change the length or get any special validation. I had to create my own converter class called ByteArrayConverter (click to see the source). You can see that it simply converts to-and-from byte[] to string and throws an Exception if there are any errors.

The MSDN article also describes how to create an expandable list of sub-options and how to create a dropdown list of selectable standard values for an option.

Custom UI for Properties

NOTE: This section used to describe a custom class derived from UITypeEditor that displayed a modal file open dialog for a property. That old class is redundant, as Microsoft supply property editors for both files and folders. To use them with your properties, reference the System.Design namepsace then add an attribute like the following to your property:

[EditorAttribute(typeof(System.Windows.Forms.Design.FileNameEditor),
typeof(System.Drawing.Design.UITypeEditor))]
[EditorAttribute(typeof(System.Windows.Forms.Design.FolderNameEditor),
typeof(System.Drawing.Design.UITypeEditor))]

These attributes add a small ellipses '...' button to a selected property which display a file and folder open modal dialog respectively.

A Default Color Trap

By setting the [DefaultValue] attribute you can tell the grid to make non-default values bold. I found that if I set a Color default as [DefaultValue(Color.Pink)] then I'd naturally get:

An attribute argument must be a constant expression,
typeof expression or array creation expression

I eventually noticed a 2 parameter constructor for DefaultValueAttribute that took (type,string) arguments. I stumbled around and tried [DefaultValue(GetType(Color),"Pink")] but I got the same error. Then I tried [DefaultValue(typeof(Color),"Pink")] and it worked.

My troubles weren't over, as one of my default colours was SystemColors.Window, not a named Color. I didn't expect [DefaultValue(typeof(Color),"Window")] to work, but does, even through "Window" is not a named colour of the Color class.

A similar trap exists for Font objects. For my default I had to specify this attribute:

[DefaultValue(typeof(Font),"Arial, 10pt")]

A Reflection trick for defaults

I found a very snazzy way of setting properties to their default values via reflection. The following code finds all properties that have a DefaultValueAttribute assigned to them and sets the property to the default.

PropertyInfo[] props = this.GetType().GetProperties();
for (int i=0; i<props.Length; i++)
{
  object[] attrs = props[i].GetCustomAttributes(typeof(DefaultValueAttribute),false);
  if (attrs.Length > 0)
  {
    DefaultValueAttribute attr = (DefaultValueAttribute)attrs[0];
    props[i].SetValue(this,attr.Value,null);
  }
}

Summary and a Warning

I am overall very impressed by the PropertyGrid and TypeConverter classes. The most robust and professional looking options edit facility can be created with little coding.

However, the appearance of a PropertyGrid will probably frighten or confuse unskilled users. I will only consider using the PropertyGrid in applications where I know the users will be appropriately computer literate or will be trained if necessary.

The optlib Project

In September 2004 I created a general purpose library called optlib that is designed for creating classes of options that can be fed into a PropertyGrid control. The library also contains simple methods for loading and saving the options to persistent storage.

Back to: Development Blog Contents


Contact Information | PGP Keys | Site Map | What's New | Visitor Book
Last Updated: 06-Aug-2007 21:10
Copyright © 1999-2007 Orthogonal Programming