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?
Yes. Some years ago, I wrote some code to display an IDictionary
in a PropertyGrid
.
Here is a complete example derived from the code that Roger linked to. I updated it so that
Dictionary<GridProperty,object>
instead of IDictionaryGridProperty
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;
}
}
}
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
System.ComponentModel
namespaceA 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 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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With