Click here to Skip to main content
15,888,286 members
Articles / Programming Languages / C#

Making a PropertyGrid ReadOnly

Rate me:
Please Sign up or sign in to vote.
4.50/5 (9 votes)
6 Jul 2010CPOL6 min read 70.5K   1.7K   10   28
The System.Windows.Forms.PropertyGrid does not support displaying the selected objects as read only. This article describes the way to do it.

Introduction

The 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 PropertyGrid, TypeDescriptor, ICustomTypeDescriptor, 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.

Motivation

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:

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

  2. Implementing ICustomTypeDescriptor for the classes whose objects need to be displayed as selected object

    Problems: Implementing 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 PropertyGrid, TypeConverters, 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.

Proposed Solution

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.

ReadOnlyPropertyDescriptor: This is the class that serves as a proxy property descriptor and its 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 and UITypeEditor respectively.

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.

Since ReadOnlyPropertyDescriptor uses 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 PropertyDescriptor and 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).

C#
//PropertyGrid propertyGrid = new PropertyGrid(); 
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).

Limitations

When the ReadOnly property is set to true, the 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!

History

  • June 28, 2010: Article submitted
  • July 7, 2010: Made correction to stop the extra firing of 'SelectedObjectsChanged' event

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralAnother way Pin
tonyt28-Jun-10 2:17
tonyt28-Jun-10 2:17 
GeneralRe: Another way Pin
rajeev51128-Jun-10 6:26
rajeev51128-Jun-10 6:26 
GeneralRe: Another way Pin
tonyt28-Jun-10 23:38
tonyt28-Jun-10 23:38 
GeneralRe: Another way Pin
rajeev51129-Jun-10 14:31
rajeev51129-Jun-10 14:31 
GeneralRe: Another way [modified] Pin
tonyt3-Jul-10 12:42
tonyt3-Jul-10 12:42 
GeneralRe: Another way Pin
rajeev5114-Jul-10 10:40
rajeev5114-Jul-10 10:40 
GeneralRe: Another way Pin
tonyt5-Jul-10 20:33
tonyt5-Jul-10 20:33 
GeneralRe: Another way Pin
rajeev5115-Jul-10 23:04
rajeev5115-Jul-10 23:04 
OK. Looks like you did not like the 'lets solve this using code' approach.

"I've done it both ways depending on the specific requirements, and both of them work equally well. When there is no PropertyGrid involved (and hence, no expandable objects), the AddProvider() method works and is really nothing more than a different way of doing what your code does (put an ICustomTypeDescriptor wrapper around an object)."


If that is the case I don't understand why you called the title of your thread 'Another Way', which in this context should mean a different way of doing what this article is about.


"If you want properties of expandable objects to also be read-only, the customized ICustomTypeDescriptor can call AddProvider() for each object for which it's parent ICustomTypeDescriptor's GetPropertiesSupported() method returns true, and that takes care of that. Of course, that requires a globally-visible list of all objects for which AddProvider() was called, so that RemoveProvider() can be called on each."


So now a new design - adding AddProvider calls in custom type descriptor. Alright. Whatever said and done, if you need to maintain a list of objects to achieve what you are saying I simply don't get how does it even compare to the absolutely universal method I have provided.


"Where did you get the impression that I've never done this??? My own work with System.ComponentModel has involved much more than merely making objects 'read-only', and yes, it has included that, but also making those objects read-only in all UI's rather than just a PropertyGrid."


I got this impression from your change of approach in each reply and refusal to accept that the kind of design you are talking about does not work in real life. Also, please stop assuming that just because the control I have used in this article is a PropertyGrid this design works only for it. And, as you say, if you really have the experience of working with ComponentModel I don't think I need to say this to you specifically.


"The work I've done with System.ComponentModel has included making most or all objects expandable in the PropertyGrid (where they would otherwise not be); adding additional 'dynamic' properties to classes (e.g. properties that are not declared on a type), and providing values for them; dynamically providing custom TypeConverters and UITypeEditors for more-specialized formatting and editing of various types that I do not own; and various other things like, for example, adding support for AutoCompletion to the PropertyGrid, on both a per-property and per-type basis."


If your intention of writing the details of what you have worked on was to scare or impress me, then just to set the record straight - I have worked with all that you have mentioned (and much more) for more than 5 years. So, although I don't wish to boast, the things you have mentioned was a pretty small part of my actual work. However, that does not stop me from accepting something if it really is correct.


"If I can ever find the time to clean up some of that code and remove dependencies on things I can't make public, I'd love to make some of it available on Code Project, but unfortunately, my time right now is very limited because of my workload."


Its a matter of not more than 15 minutes to code what you propose. You don't need to find time out of your 'busy workload' to implement it.


"So actually, the 'read-only thing' is relatively simple and straightforward compared to some of the other stuff I've had to do with System.ComponentModel to achieve a broad variety of design objectives in my work."


I would appreciate if you said this was simple only after demonstrating that you have a way of doing this which is as universal as my approach. And again, if you are saying that the things you have mentioned like making properties expandable (for heaven's sake it is as simple as using the ExpandableObjectConverter statically or dynamically), adding dynamic properties, dynamic TypeConverters, or making custom UITypeEditors are less straightforward and more difficult, then I really don't think there is any point arguing further.


"I would appreciate you're not being too presumptious, If you don't mind."


I was not being presumptuous; I was simply making it easier for both of us. The only right way of settling this is to see a code that works. As I mentioned in my previous reply, I have no issues in accepting your approach provided it works. And if it does, I'll be more than happy to have learnt an alternative way.

Honestly, now I am too tired to take this discussion any further. By changing your approach in each reply, getting infuriated by my replies, and assuming that by enumerating simple tasks involving System.ComponentModel you could make me believe that you know a lot about all this, you are simply questioning my intelligence. And I don't really appreciate that.

Thank you very much !
GeneralRe: Another way Pin
tonyt13-Jul-10 8:50
tonyt13-Jul-10 8:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.