Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can PropertyGrid edit any old list of key-value pairs?

All PropertyGrid examples I have seen allow the user to edit a single object, which PropertyGrid scans by reflection. I would like the user to be able to edit, for example, an ini file or a plain-old Dictionary, with one line per key-value pair. Is this possible?

like image 871
Qwertie Avatar asked Sep 08 '10 16:09

Qwertie


3 Answers

Yes. Some years ago, I wrote some code to display an IDictionary in a PropertyGrid.

like image 137
Roger Lipscombe Avatar answered Nov 05 '22 18:11

Roger Lipscombe


Here is a complete example derived from the code that Roger linked to. I updated it so that

  • It uses Dictionary<GridProperty,object> instead of IDictionary
  • Each GridProperty specifies a name, category, description, etc.

(Note, this post has been changed from my original design.) Thanks Roger!

public partial class PropertyEditor : Form
{
    public PropertyEditor()
    {
        InitializeComponent();

        var dict = new Dictionary<GridProperty, object>();
        dict["Food"] = "Poutine";
        dict["Ball"] = "Football";
        dict[new GridProperty("1. Greeting", "Words", "The first word to say")] = "Hello";
        dict[new GridProperty("2. Subject", "Words", "The second word to say")] = "Dogs";
        dict[new GridProperty("3. Verb", "Words", "The third word to say")] = "Like";
        dict[new GridProperty("4. Object", "Words", "The fourth word to say")] = "Burritos";
        dict[new GridProperty("Integer", "Types", "")] = 42;
        dict[new GridProperty("Double", "Types", "")] = 42.5;
        dict[new GridProperty("Color", "Types", "")] = Color.ForestGreen;

        propertyGrid1.SelectedObject = new DictionaryPropertyGridAdapter(dict, "Stuff");
    }
}

/// <summary>
/// Holds information about a property in a Dictionary-based PropertyGrid
/// </summary>
public class GridProperty
{
    public GridProperty(string name)
        { Name = name; }
    public GridProperty(string name, string category)
        { Name = name; Category = category; }
    public GridProperty(string name, string category, string description)
        { Name = name; Category = category; Description = description; }

    public string Name { get; private set; }
    public string Category { get; private set; }
    public string Description { get; set; }
    public bool IsReadOnly { get; set; }
    public object DefaultValue { get; set; } // shown if value is null

    public static implicit operator GridProperty(string name) { return new GridProperty(name); }
}

/// <summary>An object that wraps a dictionary so that it can be used as the
/// SelectedObject property of a standard PropertyGrid control.</summary>
/// <example>
/// propertyGrid.SelectedObject = new DictionaryPropertyGridAdapter(dict, "");
/// </example>
public class DictionaryPropertyGridAdapter : ICustomTypeDescriptor
{
    internal IDictionary<GridProperty, object> _dictionary;
    internal string _defaultCategory;

    public DictionaryPropertyGridAdapter(Dictionary<GridProperty, object> dict, string defaultCategory)
    {
        _dictionary = dict;
        _defaultCategory = defaultCategory;
    }

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var props = new PropertyDescriptor[_dictionary.Count];
        int i = 0;
        foreach (var prop in _dictionary)
            props[i++] = new GridPropertyDescriptor(prop.Key, this);
        return new PropertyDescriptorCollection(props);
    }

    #region Boilerplate

    #region Never called
    public string GetComponentName()
    {
        return TypeDescriptor.GetComponentName(this, true);
    }
    public EventDescriptor GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(this, true);
    }
    EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
    {
        return TypeDescriptor.GetEvents(this, true);
    }
    PropertyDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetProperties()
    {
        return GetProperties(null);
    }
    #endregion

    public string GetClassName()
    {
        return TypeDescriptor.GetClassName(this, true);
    }
    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes, 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;
    }

    #endregion

    class GridPropertyDescriptor : PropertyDescriptor
    {
        GridProperty _prop;
        DictionaryPropertyGridAdapter _parent;

        internal GridPropertyDescriptor(GridProperty prop, DictionaryPropertyGridAdapter parent)
            : base(prop.Name, null)
        {
            _prop = prop;
            _parent = parent;
        }
        public override string Description
        {
            get { return _prop.Description; }
        }
        public override string Category
        {
            get { return _prop.Category ?? _parent._defaultCategory; }
        }
        public override Type PropertyType
        {
            get { return (_parent._dictionary[_prop] ?? _prop.DefaultValue ?? "").GetType(); }
        }
        public override void SetValue(object component, object value)
        {
            _parent._dictionary[_prop] = value;
        }
        public override object GetValue(object component)
        {
            return _parent._dictionary[_prop];
        }
        public override bool IsReadOnly
        {
            get { return _prop.IsReadOnly; }
        }
        public override Type ComponentType
        {
            get { return null; }
        }
        public override bool CanResetValue(object component)
        {
            return _prop.DefaultValue != null;
        }
        public override void ResetValue(object component)
        {
            SetValue(component, _prop.DefaultValue);
        }
        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }
    }
}
like image 44
Qwertie Avatar answered Nov 05 '22 18:11

Qwertie


The PropertyGrid will allow the editing of any property that has get and set accessors or an appropriate editor attached to it or the type it provides that describes how to convert or even edit that value.

Therefore, if you expose a property that is, for example, a Stream of an INI file and attach a custom TypeConverter that expands it to the name/value pairs within it, you can indeed edit an INI file with the PropertyGrid.

Useful links:

  • EditorAttribute
  • TypeConverter
  • TypeConverterAttribute
  • Other types within the System.ComponentModel namespace

Type converters

A type converter is attached to a type using a TypeConverterAttribute declaration. Type converters allow you to provide rules on how to convert your type to and from other types. A range of overrides are provided to tailor your conversions, all beginning with Convert.

Through various GetPropertiesxxxx calls, type converters also allow you to specify what properties your type has that can be edited and how they appear (their names, for example). This makes it possible to expand values (like editing the Point type) and show or hide properties based on the state of a value (for example, your INI file would use this to show or hide made-up properties based on the contents of the file).

Type converters also allow you to specify a list of values that your type can show as a drop-down list when being edited. This is provided by the GetStandardValuesXxx set of overrides and can be useful if you don't want to create a custom editor but you have a fixed list of allowable values.

Editors

Editors allow you to fine tune the design time experience of editing an instance of a type. They can be attached to a property or a type and indicate to the PropertyGrid what editor to use when editing a value. This allows you to show a dialog or your own dropdown with some custom user interface (for example, a slider).

like image 34
Jeff Yates Avatar answered Nov 05 '22 18:11

Jeff Yates