PropertyGrid is one of the most useful controls provided in
System.Windows.Forms namespace. However, one important feature missing in it is the ability to display the selected object in a read only format. This article proposes a method to achieve this by the means of a control that inherits
PropertyGrid and wraps the selected object in a custom wrapper to bring about the read only effect.
Understanding the Terms Used in this Article
Throughout this article, you will come across terms like
PropertyDescriptor, etc. If you feel comfortable with these terms and understand their roles, it would be easier to understand the article. If not, no worries. You may directly skip to the how to use section. The knowledge of these terms or their working is not necessary to be able to use the solution. The attached binaries include the XML documentation to make it easier to use the properties and methods of the proposed control.
Nearly two years ago, while working on a designer I had faced a situation where the
PropertyGrid was expected to display the selected object as read only in certain situations. But the control provided by the framework has no provision for the same. So, while searching for the possible solutions, I came across a lot of forums where others needed a similar behavior but no elegant solution was proposed. The most widely used workarounds were:
- Simply disabling the control when the read only behavior was needed
Problems: Disabling the control is not only a lazy substitute, but also a visually unpleasant one. When the control is disabled, it loses its ability to scroll, thereby rendering it pretty useless when it comes to browsing through the properties. It also makes the grids un-expandable, so viewing the child properties is impossible.
ICustomTypeDescriptor for the classes whose objects need to be displayed as selected object
ICustomTypeDescriptor does empower the type to tailor its properties and manipulate their attributes. However this ability is restricted to the implementing class and has no power over the child properties. And it is not always possible to implement
ICustomTypeDescriptor for all the classes that may make up the properties of the root object, because they may be a part of the framework's classes. Thus this does not provide a fool proof and complete solution. Some also propose to disable the ability to expand the child properties. But this, again, makes browsing the properties a big pain.
So, after playing around with
TypeDescriptors, etc. I had discovered the following solution then. And recently I realized that this, or a similar solution, was not yet available to everyone (at least my search for it proved futile) I decided to publish it here.
The solution proposed in this article involves the use of a new control called
RPropertyGrid that inherits from
PropertyGrid and has a property called
ReadOnly which can be used to make the selected object read only.
Internally, when the
ReadOnly property is set to
true the control wraps the selected objects
by instances of a custom class called the
ReadOnlyWrapper, which makes the object behave as if they were read only.
Behind the Scene
When the selected object is wrapped by the
ReadOnlyWrapper, the following components come into play.
ReadOnlyWrapper: This is the class where the process of making the object seem read only happens. It implements
ICustomTypeDescriptor, but most of the implemented methods return the values obtained by calling their counterparts provided by the
TypeDescriptor class on the wrapped object. The only method that does not do this is the
GetProperties method which in turn wraps the original properties (instances of
PropertyDescriptor) by instances of
ReadOnlyPropertyDescriptor: This is the class that serves as a proxy property descriptor
ReadOnly property always returns
true. This makes them appear as read only in the
PropertyGrid. The other two aberrations in this class are in the form of the property
Converter and the method
GetEditor which always return instances of
ReadOnlyConverter: This is the class that derives from
TypeConverter and acts on behalf of the original converter of the wrapped object. In most cases, it simply returns the actual values obtained by using the counterpart methods of the original converter. However, the
GetProperties method, again, is the only point where the behavior changes. In the custom implementation of this method, instead of returning the original
PropertyDescriptor of the properties, they are wrapped in instances of
ReadOnlyPropertyDescriptor, thus making the child properties read only too.
ReadOnlyConverter and vice versa, all the properties of the root object and the subsequent properties of its child properties are made read only. This "symbiotic" mechanism forms the core of the idea. Also, since all the wrapping and unwrapping happens at the
TypeConverter levels, it makes no difference if the wrapped object is a custom object or one that instantiates some built-in class.
How to Use RPropertyGrid
There is no need to make any change in your existing components to be able to use the given solution. Your classes need not implement any special interface or derive from any special class.
RPropertyGrid derives from
PropertyGrid, so it can simply replace your existing control without demanding any change in the way selected objects are set. Here is an example (with your original code shown in comments).
RPropertyGrid propertyGrid = new RPropertyGrid();
propertyGrid.SelectedObject = value;
Moreover, it provides three new properties which have the following use:
ReadOnly: Property that lets you
set(and, of course,
get) the selected objects as read only. Everything that needs to be done is internally handled.
OriginalSelectedObject: Property that lets you get the original selected object, in case you need to retrieve the actual object that was used as the selected object (and not the custom wrapper's instance).
OriginalSelectedObjects: Property that lets you get the original selected objects, in case you need to retrieve the actual objects that were used as the selected objects (and not the custom wrapper's instances).
ReadOnly property is set to
UITypeEditors of the properties are stopped from being shown. Generally this is not a problem because properties with a
UITypeEditor typically have a
TypeConverter associated with them that show their state well enough. However, in rare cases where an object can only be described by the means of an editor (e.g. a pop-up editor that could show an image for a property of type
Image), this limitation would apply.
Just a Side Note
I intend to add some more controls to this assembly in the future, and hence the audacious name. Please excuse me for it!
- June 28, 2010: Article submitted
- July 7, 2010: Made correction to stop the extra firing of '