Wednesday, 2 February 2011

Enabling/Disabling properties at runtime in the PropertyGrid

Many people knows about the benefits of using PropertyGrids in user interfaces.
You probably know about the System.ComponentModel namespace, and how it can help you customizing the behavior of classes and their properties when edited through controls like the PropertyGrid. One of the attributes you can find in that namespace is the ReadOnly, which will make a property to be read only and appear in gray (even if it has the Set accessor).
The problem with this attribute is that it must be defined at build-time. Sometimes, it is very useful (and improves the User Experience quite a bit), to enable or disable properties at runtime.
Imagine the following example, where we have a “UserData” class:
public class UserData
{
...
 
        public string Country
        {
            get { return mCountry; }
            set { mCountry = value; }
        }
        public string State
        {
            get { return mState; }
            set { mState = value; }
        }
}
Now, imagine you want the user to fill the “State” field only in the case he selected U.S. as his Country. In such a case, you need to make this change at Runtime.
There are several solutions for this, but I find the following one to be the most elegant, as it´s coded in the “UserData” class itself. The final code looks like:
[RefreshProperties(System.ComponentModel.RefreshProperties.All)]
[ReadOnly(false)]
public string Country
{
  get { return mCountry; }
  set
      {
      mCountry = value;
      PropertyDescriptor descriptor = TypeDescriptor.GetProperties(this.GetType())["State"];
      ReadOnlyAttribute attribute = (ReadOnlyAttribute)
                                    descriptor.Attributes[typeof(ReadOnlyAttribute)];
      FieldInfo fieldToChange = attribute.GetType().GetField("isReadOnly",
                                       System.Reflection.BindingFlags.NonPublic |
                                       System.Reflection.BindingFlags.Instance);                   fieldToChange.SetValue(attribute, mCountry != "U.S.");
      }
}
[ReadOnly(true)]
public string State
{
   get { return mState; }
   set { mState = value; }
}

Some Tips

1.- We obviously change the “ReadOnly” attribute of the “State” property in the setter accessor of the property “Country”. That´s the exact point where we know if State should be enabled or disabled.
2.- It is important to add the “RefreshProperties” attribute to the “Country” property. That will force the PropertyGrid control to refresh all it’s properties every time the value of “Country” changes, reflecting the changes we made to the attributes of “State”
3.- In order to work properly all of this, it is important to statically define the “ReadOnly” attribute of every property of the class, to whatever value you want. If not, changing the attribute at runtime that way, will wrongly modify the attributes of every property of the class.
Hope it helps!

More Info

At The Code Project
At C-Sharp Corner
Cheers!

1 comment:

Anonymous said...

This seems to work well for the ReadOnly attribute, but throws an exception if I try the same technique on the Browsable attribute. Is there a way to get it to work on the Browsable attribute?