Tagged: dynamic properties
Dynamic custom type information
Have you ever wondered how could you define custom properties at runtime? I didn’t… but recently I came across this issue.
The interface ICustomTypeDescriptor
came to save the day. This interface is used to add custom type description outside of what the standard TypeDescriptor
provides. If ICustomTypeDescriptor
is not used, the default behaviour is given by a static TypeDescriptor
at runtime, which provides type information based on the meta data obtained via reflection.
Let’s get to the code (here are the sources):github
The PropertyGrid
is widely used to display an object’s properties and values. I used this control in a WPF application to prove the utility of using this interface.
<Window x:Class="TestCustomProperty.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:swf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" Title="MainWindow" Height="350" Width="525"> <Grid> <WindowsFormsHost Name="windowsFormsHost1" > <swf:PropertyGrid x:Name="propertyGrid"/> </WindowsFormsHost> </Grid> </Window>
In code-behind:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.Loaded += this.Window_Loaded; } private void Window_Loaded(object sender, RoutedEventArgs e) { MyClass c = new MyClass(); propertyGrid.SelectedObject = c; } }
Initially MyClass has only two properties:
public class MyClass { public string Name { get; set; } public int Nr { get; set; } public MyClass() { Name = "sss"; Nr = 1; } }
With these two properties, the result looks something like this:
Here comes the interface ICustomTypeDescriptor in handy.
The interface implementation consist of a lot of methods that I didn’t need. The heart of the whole thing is the GetProperties method. The purpose of this method is to return a PropertyDescriptorCollection that describes all of the properties of the object.
public class DictionaryPropertyGridAdapter : ICustomTypeDescriptor { IDictionary _dictionary; public DictionaryPropertyGridAdapter(IDictionary d) { _dictionary = d; } public DictionaryPropertyGridAdapter() { _dictionary = new Hashtable(); } public AttributeCollection GetAttributes() { return TypeDescriptor.GetAttributes(this, true); } public string GetClassName() { return TypeDescriptor.GetClassName(this, true); } public string GetComponentName() { return TypeDescriptor.GetComponentName(this, true); } public TypeConverter GetConverter() { return TypeDescriptor.GetConverter(this, true); } public EventDescriptor GetDefaultEvent() { return TypeDescriptor.GetDefaultEvent(this, true); } public PropertyDescriptor GetDefaultProperty() { return null; } public object GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, true); } public EventDescriptorCollection GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, true); } public EventDescriptorCollection GetEvents() { return TypeDescriptor.GetEvents(this, true); } 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); } public PropertyDescriptorCollection GetProperties() { return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]); } public object GetPropertyOwner(PropertyDescriptor pd) { return _dictionary; } public void AddProperty(object key, object value) { _dictionary.Add(key, value); } }
You will also notice in the GetProperties method that the Property Descriptors added for the dictionary entries are of type DictionaryPropertyDescriptor. This is a custom Property Descriptor class that manages how properties are set and retrieved. The main methods to look at on this class are GetValue and SetValue. Here you can see the component being casted as a dictionary and the value of the key inside it being set or retrieved. Take a look at the implementation below:
public class DictionaryPropertyDescriptor : PropertyDescriptor { IDictionary _dictionary; object _key; internal DictionaryPropertyDescriptor(IDictionary d, object key) : base(key.ToString(), null) { _dictionary = d; _key = key; } public override Type PropertyType { get { return _dictionary[_key].GetType(); } } public override void SetValue(object component, object value) { _dictionary[_key] = value; } public override object GetValue(object component) { return _dictionary[_key]; } 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; } }
I modified MyClass so that it derives from the DictionaryPropertyGridAdapter and added the initial properties of the class to the dictionary in the constructors. I guess there is a better way to do this. Please fell free to comment with another solution.
public class MyClass : DictionaryPropertyGridAdapter { public string Name { get; set; } public int Nr { get; set; } public MyClass(IDictionary d) : base(d) { Name = "sss"; Nr = 1; foreach (PropertyInfo pi in typeof(MyClass).GetProperties()) { d.Add(pi.Name, pi.GetValue(this, null)); } } public MyClass():base() { Name = "sss"; foreach (PropertyInfo pi in typeof(MyClass).GetProperties()) { this.AddProperty(pi.Name, pi.GetValue(this, null)); } Nr = 1; } }
I added the other properties in code-behind:
private void Window_Loaded(object sender, RoutedEventArgs e) { MyClass c = new MyClass(); c.AddProperty("hello","world"); c.AddProperty("testint", 2); propertyGrid.SelectedObject = c; }
Here is the end result:
Sources: MSDN, source1, source2, source3