Introduction
Normal C# PropertyGrid
is good enough to display an object’s properties when you select it into the PropertyGrid
object and show it. PropertyGrid
is smart enough to know the property data type and display the property value either in text box, or combobox. Here, I want to show you an example of how to use it to show the properties added at runtime.
Using the Code
This is MSDN's suggested way to use PropertyGrid
class:
- Define your object and use property descriptions:
class BOOK
{
[BrowsableAttribute(false)]
public int book_id { get;set}
[BrowsableAttribute(true)]
[Description("Book’s title")]
[DisplayName("Title")]
public string title {get;set}
[Description("Book’s author")]
[DisplayName("Author")]
public string author {get;set}
[Description("Book’s price in USD")]
[DisplayName("Price($)")]
public float price {get;set}
public BOOK() {
}
}
- Select your object into
PropertyGrid
instance and show it:
public Form1() {
PropertyGrid propertyGrid1 = new PropertyGrid();
propertyGrid1.CommandsVisibleIfAvailable = true;
propertyGrid1.Location = new Point(10, 20);
propertyGrid1.Size = new System.Drawing.Size(400, 300);
propertyGrid1.TabIndex = 1;
propertyGrid1.Text = "Property Grid";
this.Controls.Add(propertyGrid1);
BOOK book1 = new BOOK();
propertyGrid1.SelectedObject = book1;
}
The project requires to use the C# propertyGrid
to display/modify the object’s properties, but the object properties are not predefined in the design time, for example, the object’s properties are in the directory container which is dynamically generated based on different needs, like below BOOK
class:
public class UserProperty; public class LocalProperty
{
public string Name{get;set;}
public string Group{get;set;} public string DisplayName{get;set;} public Boolean Display {get;set;} public Boolean Readonly {get;set;} public string Description {get;set;}
public object Value {get;set}
public List<object> Options = new List<object>();
public LocalProperty(string name, string displayname,
string description, object value)
{
Display = true;
Readonly = false;
Name = name;
Group = “general”;
DisplayName = displayname;
Description = description;
Value = value;
}
public object ValueEditor()
{
if(_value.GetType().BaseType == typeof(UserProperty)) {
return (_value as UserProperty).Editor;
}
return null;
}
}
public class LocalProperties : Dictionary<string, LocalProperty>
{
}
class BOOK
{
LocalProperties properties = new LocalProperties();
public BOOK() {
}
}
public Form1() {
PropertyGrid propertyGrid1 = new PropertyGrid();
propertyGrid1.CommandsVisibleIfAvailable = true;
propertyGrid1.Location = new Point(10, 20);
propertyGrid1.Size = new System.Drawing.Size(400, 300);
propertyGrid1.TabIndex = 1;
propertyGrid1.Text = "Property Grid";
this.Controls.Add(propertyGrid1);
BOOK book1 = new BOOK();
book1.poperties[“title”] =
new LocalProperty(“title”, “Title”,
“Book’s title”, true, “C# for beginner”);
book1.properties[“price”] =
new LocalProperty(“price”,”Price($)”,
“Book’s price in USD”, true,“$19”);
propertyGrid1.SelectedObject = book1;
}
To implement this kind of function, we need to implement our owner PropertyGrid
Object based on some PropertyGrid
interface classes. The most important part is to re-implement the override-able functions provided in ICustomTypeDescriptor
, PropertyDescriptor
and UITypeEditor
. UITypeEditor
is used to define the user specific property editor, like a dialog box or a form.
private class DynamicPropertyDescriptor : PropertyDescriptor
{
private readonly LocalPropertyGridObject _propertyGridObject;
private readonly LocalProperty _propertyObject;
private readonly Type _propertyType;
public DynamicPropertyDescriptor(LocalPropertyGridObject propertyGridObject,
string propertyName, Type propertyType, LocalProperty propertyObject)
: base(propertyName, null)
{
this._propertyGridObject = propertyGridObject;
this._propertyObject = propertyObject;
this._propertyType = propertyType;
var attributes = new List<Attribute>();
if (propertyObject.GetOptions().Count > 0
&& !(_propertyObject.ValueType() == typeof(DateTime)
||_propertyObject.ValueType() == typeof(System.Drawing.Color)))
{
attributes.Add(new typeConverterAttribute
(typeof(OptionTypeConverter))); }
attributes.Add(new DisplayNameAttribute(_propertyObject.DisplayName==null?
_propertyObject.Name: _propertyObject.DisplayName));
attributes.Add(new CategoryAttribute(_propertyObject.Group));
attributes.Add(new BrowsableAttribute(_propertyObject.Display));
attributes.Add(new DescriptionAttribute(_propertyObject.Description));
if (_propertyObject.ValueType().BaseType == typeof(UserProperty))
{
attributes.Add(new EditorAttribute(typeof(UserPropertyEditor),
typeof(UITypeEditor))); }
this.AttributeArray = attributes.ToArray();
}
}
public class LocalPropertyGridObject : DynamicObject, ICustomTypeDescriptor, INotifyPropertyChanged
{
private LocalPropertyGrid _owner;
private LocalProperties _properties;
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var memberName = binder.Name;
LocalProperty _p;
if (_properties.TryGetValue(memberName, out _p))
{
result = _p.Value;
return true;
}
result = null;
return false;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
var memberName = binder.Name;
_properties[memberName].Value = value.ToString();
NotifyToRefreshAllProperties();
return true;
}
public PropertyDescriptorCollection GetProperties()
{
var properties = _properties
.Select(pair => new DynamicPropertyDescriptor(this,
pair.Key, pair.Value.Value.GetType(), pair.Value));
return new PropertyDescriptorCollection(properties.ToArray());
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return this.GetProperties();
}
}
private class UserPropertyEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue
(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
IWindowsFormsEditorService svc = provider.GetService
(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
DynamicPropertyDescriptor d = context.PropertyDescriptor as DynamicPropertyDescriptor;
if (d != null)
{
LocalPropertyEditor user_editor = System.Activator.CreateInstance(
d.GetPropertyObject().ValueEditor().GetType(),
new object[] { value }
) as LocalPropertyEditor;
if (svc != null && user_editor != null)
{
LocalPropertyGridObject localpropertyobject
= context.Instance as LocalPropertyGridObject;
if (localpropertyobject != null)
{
user_editor.StartPosition = FormStartPosition.Manual;
using (Form form = user_editor as Form)
{
if (svc.ShowDialog(form) == DialogResult.OK)
{
value = user_editor.Value;
}
}
}
}
return value; }
User defined property editor should be inherited from the class "LocalPropertyEditor
" with a constructor to accept property value and a pre-closing callback to set the DialogResult = OK
.
class YourPropertyEditor: LocalPropertyEditor
{
YourPropertyEditor(string value);
public void OnOK(EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
}
Check the example attached to see the full source code.