The PropertyGrid control that is provided with Visual Studio, while not the most powerful of controls, provides some pretty awesome functionality. In a nutshell it permits you to bind an object to it via a .SelectedObject property. The PropertyGrid will then, via reflection, discover properties on your object and display them for your viewing/editing pleasure. In doing so, you can provide users of your applications with a UX (user experience) similar to what you would receive within Visual Studio's design time.
Doing this binding is trivial and quite enjoyable actually.
The PropertyGrid also allows you to further extend the design experience with UITypeEditor-derived types. These types can provide custom dropdowns or popup dialogs that give you the ability to offer the user a rich experience in setting property values. Often times these are used when a property value is more complex than a simple value (e.g. collection management, graphical color selections, etc). These, too, are a lot of fun and quite easy to create.
If a property on your bound object is another non-simple type (such as a Customer), the PropertyGrid won't know how to represent the data and will simply call .ToString() on the property, thereby returning the class name. This is usually not the desired behavior. Perhaps you want to display some more palatable text or a particular property of the object. This can easily be accomplished through a mechanism known as a TypeConverter.
A TypeConverter-based class provides a consistent manner of converting an object of one type to another. Via the TypeConverterAttribute class, you can designate the TypeConverter-type to use to represent your property as in the following example. Suppose that a Customer object is a property on some other object (such as an Order) that is bound to the PropertyGrid.
namespace Devstone.Samples.Objects {
public sealed class Order {
private Customer _customer;
public Customer Customer {
get { return _customer; }
set { _customer = value; }
}
}
[TypeConverter(typeof(CustomerConverter)]
public sealed class Customer {
//... additional code here
}
internal sealed class CustomerConverter : TypeConverter {
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
if ( destinationType == typeof(string) && value is Customer ) {
// NOTE: we could simply cast value to Customer and return a string consisting
// of property values if we chose to do so.
// for simplicity sake, however, we'll return just some basic text.
return "(Customer)";
}
else {
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
}
Now, rather than 'Devstone.Samples.Objects.Customer' showing up as the value for the Customer property, we have the nicer '(Customer)' value.
This particular example is very straightforward and simple. Tomorrow, I'll follow up with a more complex and robust example that takes the PropertyGrid and extends it further by dynamically creating properties on the fly that represent actual properties on the constituent objects. This gives us the ability to view and edit hierarchical information directly within the PropertyGrid. :) It's cool stuff.