PropertyGrid can influence through TypeConverter , ICustomTypeDescriptor and / or TypeDescriptionProvider . Of these, TypeConverter is the simplest, overriding GetProperties (and marking it as supported).
In any case, you will also need to write an implementation of PropertyDescriptor , which knows how to take the field and object, and get / set the value, i.e.
public override void SetValue(object component, object value) { ((YourType)component)[fieldNameSetInConstructor] = value; }
Here is a basic property package that expands everything as a string ; obviously, as you extend this (different types of properties, change notifications, etc.), it becomes more complex very quickly. Also note that this TypeConverter approach TypeConverter works for PropertyGrid ; for DataGridView etc. you will need ICustomTypeDescriptor or TypeDescriptionProvider . For collections you will need ITypedList . In addition, for specific scenarios, there are about 20 other interfaces around the edges. But you understand: p The main thing is that our PropertyDescriptor acts as a translation between your actual model (the dictionary in my case) and the model that you display on the TypeDescriptor (fake properties for each key).
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows.Forms; static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var bag = new BasicPropertyBag { Properties = { new MetaProp("Name", typeof(string)), new MetaProp("Description", typeof(string)), new MetaProp("DateOfBirth", typeof(DateTime) , new CategoryAttribute("Personal"), new DisplayNameAttribute("Date Of Birth")) } }; bag["Name"] = "foo"; bag["DateOfBirth"] = DateTime.Today; Application.Run(new Form { Controls = { new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = bag } } }); } } public class MetaProp { public MetaProp(string name, Type type, params Attribute[] attributes) { this.Name = name; this.Type = type; if (attributes != null) { Attributes = new Attribute[attributes.Length]; attributes.CopyTo(Attributes, 0); } } public string Name { get; private set; } public Type Type { get; private set; } public Attribute[] Attributes { get; private set; } } [TypeConverter(typeof(BasicPropertyBagConverter))] class BasicPropertyBag { private readonly List<MetaProp> properties = new List<MetaProp>(); public List<MetaProp> Properties { get { return properties; } } private readonly Dictionary<string, object> values = new Dictionary<string, object>(); public object this[string key] { get { object value; return values.TryGetValue(key, out value) ? value : null; } set { if (value == null) values.Remove(key); else values[key] = value; } } class BasicPropertyBagConverter : ExpandableObjectConverter { public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { PropertyDescriptor[] metaProps = (from prop in ((BasicPropertyBag)value).Properties select new PropertyBagDescriptor(prop.Name, prop.Type, prop.Attributes)).ToArray(); return new PropertyDescriptorCollection(metaProps); } } class PropertyBagDescriptor : PropertyDescriptor { private readonly Type type; public PropertyBagDescriptor(string name, Type type, Attribute[] attributes) : base(name, attributes) { this.type = type; } public override Type PropertyType { get { return type; } } public override object GetValue(object component) { return ((BasicPropertyBag)component)[Name]; } public override void SetValue(object component, object value) { ((BasicPropertyBag)component)[Name] = (string)value; } public override bool ShouldSerializeValue(object component) { return GetValue(component) != null; } public override bool CanResetValue(object component) { return true; } public override void ResetValue(object component) { SetValue(component, null); } public override bool IsReadOnly { get { return false; } } public override Type ComponentType { get { return typeof(BasicPropertyBag); } } } }