Using PropertyGrid with a dictionary object

roger's picture

If you try using IDictionary with the PropertyGrid control, the results aren't spectacular:

Here's how to do it properly.

The first thing that you need to figure out is that when you associate an object with the PropertyGrid, it asks for that object's type descriptor, and then asks that about which properties it supports.

To fake out the type descriptor, we'll need some kind of adapter object. It'll need to implement ICustomTypeDescriptor:

class DictionaryPropertyGridAdapter : ICustomTypeDescriptor
{
    IDictionary _dictionary;

    public DictionaryPropertyGridAdapter(IDictionary d)
    {
        _dictionary = d;
    }

Three of the ICustomTypeDescriptor methods are never called by the property grid, but we'll stub them out properly anyway:

    public string GetComponentName()
    {
        return TypeDescriptor.GetComponentName(this, true);
    }

    public EventDescriptor GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(this, true);
    }

    public string GetClassName()
    {
        return TypeDescriptor.GetClassName(this, true);
    }

Then there's a whole slew of methods that are called by PropertyGrid, but we don't need to do anything interesting in them:

    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes, true);
    }

    EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
    {
        return TypeDescriptor.GetEvents(this, true);
    }

    public TypeConverter GetConverter()
    {
        return TypeDescriptor.GetConverter(this, true);
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return _dictionary;
    }

    public AttributeCollection GetAttributes()
    {
        return TypeDescriptor.GetAttributes(this, true);
    }

    public object GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(this, editorBaseType, true);
    }

    public PropertyDescriptor GetDefaultProperty()
    {
        return null;
    }

    PropertyDescriptorCollection
        System.ComponentModel.ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
    }

Then the interesting bit. We simply iterate over the IDictionary, creating a property descriptor for each entry:

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        ArrayList properties = new ArrayList();
        foreach (DictionaryEntry e in _dictionary)
        {
            properties.Add(new DictionaryPropertyDescriptor(_dictionary, e.Key));
        }

        PropertyDescriptor[] props =
            (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor));

        return new PropertyDescriptorCollection(props);
    }

Of course, now we need to implement the DictionaryPropertyDescriptor class:

class DictionaryPropertyDescriptor : PropertyDescriptor
{

PropertyDescriptor provides 3 constructors. We want the one that takes a string and an array of attributes:

    IDictionary _dictionary;
    object _key;

    internal DictionaryPropertyDescriptor(IDictionary d, object key)
        : base(key.ToString(), null)
    {
        _dictionary = d;
        _key = key;
    }

The attributes are used by PropertyGrid to organise the properties into categories, to display help text and so on. We don't bother with any of that at the moment, so we simply pass null.

The first interesting member is the PropertyType property. We just get the object out of the dictionary and ask it:

    public override Type PropertyType
    {
        get { return _dictionary[_key].GetType(); }
    }

If you knew that all of your values were strings, for example, you could just return typeof(string).

Then we implement SetValue and GetValue:

    public override void SetValue(object component, object value)
    {
        _dictionary[_key] = value;
    }

    public override object GetValue(object component)
    {
        return _dictionary[_key];
    }

The component parameter passed to these two methods is whatever value was returned from ICustomTypeDescriptor.GetPropertyOwner. If it weren't for the fact that we need the dictionary object in PropertyType, we could avoid using the _dictionary member, and just grab it using this mechanism.

And that's it for interesting things. The rest of the class looks like this:

    public override bool IsReadOnly
    {
        get { return false; }
    }

    public override Type ComponentType
    {
        get { return null; }
    }

    public override bool CanResetValue(object component)
    {
        return false;
    }

    public override void ResetValue(object component)
    {
    }

    public override bool ShouldSerializeValue(object component)
    {
        return false;
    }
}

Then you can just use it like this:

private void Form1_Load(object sender, System.EventArgs e)
{
    IDictionary d = new Hashtable();
    d["Hello"] = "World";
    d["Meaning"] = 42;
    d["Shade"] = Color.ForestGreen;

    propertyGrid1.SelectedObject = new DictionaryPropertyGridAdapter(d);
}

And it comes out looking like this:

Comments

Great code thanks. Question,

Great code thanks. Question, how to add and delete?

RE: Question, how to add and delete?

Just add/remove the key/value pair from the underlying dictionary.

Small error in Code

Hi there, first of all, thanks for the great example and the code. I just wanted to point a minor error that causes an exception to be thrown if there is a key that has a null as value assigned to it. you have to correct the PropertyType property to read:
        public override Type PropertyType
        {
            get
            {
                if (_dictionary[_key] != null)
                {
                    return _dictionary[_key].GetType();
                }
                else
                {
                    return typeof(object);
                }
            }
        }
Cheers, Tobi

RE: Small error in Code

It's not really so much a bug in the code as it is a gap in the functionality. I've got to ask why you're putting nulls into a dictionary that you then intend to put into a PropertyGrid. Even if this was your desired functionality, using typeof(object) might not be the right answer either. Consider the following (using System.Net.Mail):

dict["To"] = new MailAddress("somebody@example.com");

versus the following:

dict["To"] = null;

It could be argued that the correct behaviour in the second instance is to present the user with a MailAddress that can be edited, rather than a System.Object that makes no sense.

So, it's up to you how to solve it. I decided to opt for disallowing it. In another project, I invented an ExtendedDictionary class that also held categories and descriptions for display in the PropertyGrid. It would be possible to hold the type as well.

Cheers,
Roger.

Can use a TypeConverter

Hi, Me I want to show a class with a dictionary members, I see we can use the TypeConverter to display it in a PropertyGrid, How I can use your code to put it in a class derive from TypeConverter. Thank 4 help

TypeConverter

I'm not sure I understand what you're asking. Could you explain more?

Nesting?

This works great for single layer hashtables, but what if I have nested hashtables inside hashtables? As it is, my nested hashtable is presented to me with an ellypsis and I can only see that it's a further dictionary entry inside.

Same thing?

Hi I haven't tried this code yet but it seems logical to assume that if you have a nested hashtable you would have to wrap the nested one the same way as described here before adding it to the parent. I would guess that the property grid would be able to resolve that the same way, too.

Override GetEditor

I think I'd probably override the GetEditor implementation (my original has it do the default). If the object implements IDictionary, you can have GetEditor return an object that puts another property grid on a form, but drilled down a further level.

Actually, I lied

You'd need to add an EditorAttribute to the property descriptor. Something like this:

  1. In the constructor for DictionaryPropertyDescriptor, add a parameter Attribute[] attributes. Pass it to the base constructor.
  2. In the call to the constructor you just changed, add some more attributes. You need at least an EditorAttribute.

Yep, that seems to work...

In Main...

            Hashtable stuff = new Hashtable();
            stuff["Other"] = "Thanks for the fish";
            stuff["Thing"] = SystemIcons.Asterisk;

            dictionary["Stuff"] = stuff;

DictionaryPropertyGridAdapter.GetProperties should look like this:

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            List properties = new List();
            foreach (DictionaryEntry entry in _dictionary)
            {
                Attribute[] propertyAttributes = null;
                if (typeof(IDictionary).IsAssignableFrom(entry.Value.GetType()))
                {
                    propertyAttributes = new Attribute[] { new EditorAttribute(typeof(DictionaryEditor), typeof(UITypeEditor)) };
                }

                properties.Add(new DictionaryPropertyDescriptor(_dictionary, entry.Key, propertyAttributes));
            }

            return new PropertyDescriptorCollection(properties.ToArray());
        }

DictionaryEditor looks like this:

    public class DictionaryEditor : UITypeEditor
    {
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            // Gives us an ellipsis, rather than a drop-down arrow. Otherwise, it's pretty much cosmetic.
            return UITypeEditorEditStyle.Modal;
        }

        public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
        {
            if (value is IDictionary)
            {
                DictionaryPropertyGridForm form = new DictionaryPropertyGridForm((IDictionary)value);
                form.ShowDialog();
            }

            return value;
        }
    }

Databinding

Any suggestions on databinding here. Say you have a bindinglist and you loop through the list to only show the keys & values wanted in the propertygrid. Now you want to edit the values and have them update the bindinglist when the value is changed.

Combobox??

How would I add support for a drop down menu or a Boolean switch?

Re: Combobox??

If the item in the dictionary is an enum, you'll get a drop down menu; if it's a bool, you'll get a drop down with true and false in it.

Beyond that, you're kinda stuck. PropertyGrid will allow you to display a dialog box when editing a value (and you'll need to extend this example to do that), but the only real control you get is whether to display an ellipsis or a drop-down arrow in the property grid.

excellent way to solve this

excellent way to solve this common task! Thank you for a lot of time for me and all shame on microsoft which always have no idea how developers will be using its controls.

Works like a charm!

There is other information around on how to do this, but they are more complicated and solve different problems. I scoured for this for a few hours and am very glad I did. Still works in runtime 4.5, too!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.