I'm experimenting with the ICustomTypeDescriptor
interface and the PropertyDescriptor
class in-order to create dynamic properties on objects. I am having a lot of success with simple objects, but I cannot get nested objects to create their dynamic properties?
For example in the data-binding dialog below, I'm adding my Person class as a StaticResource
, then trying to data-bind the Person.Child.Name
to a testbox:
For the Person.Child
I am expecting to see my dynamically created properties (Name
and Age
), but as you can see it's not working as expected?
It's almost as if the databinding dialog is not interrogating the ICustomTypeDescriptor
interface on the Person.Child
?
Any guidance on how to make these nested properties 'visible'?
Outer class
public class Person : ICustomTypeDescriptor, INotifyPropertyChanged
{
private readonly List<CustomPropertyDescriptor> propertyDescriptors = new List<CustomPropertyDescriptor>();
private readonly Dictionary<string, object> properties = new Dictionary<string, object>();
public Person()
{
// 'Dynamic' Property
string name = "Name";
object value = "Person's Name";
this.properties.Add(name, value);
var propertyDescriptor = new CustomPropertyDescriptor(
typeof(Person),
name,
value,
value.GetType().GetCustomAttributes(true).Cast<Attribute>().ToArray());
this.propertyDescriptors.Add(propertyDescriptor);
// 'Dynamic' Property
name = "Child";
value = new Child();
this.properties.Add(name, value);
propertyDescriptor = new CustomPropertyDescriptor(
typeof(Child),
name,
value,
value.GetType().GetCustomAttributes(true).Cast<Attribute>().ToArray());
this.propertyDescriptors.Add(propertyDescriptor);
propertyDescriptor.PropertyChanged += this.PropertyDescriptorPropertyChanged;
}
public event PropertyChangedEventHandler PropertyChanged;
// Test Property (shouldn't be visible)
public string NotDynamic { get; set; }
public override string ToString()
{
return string.Format("{0} ({1})", this.properties["Name"], this.properties["Age"]);
}
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()
{
try
{
return this.propertyDescriptors.First();
}
catch (InvalidOperationException)
{
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)
{
return new PropertyDescriptorCollection(this.propertyDescriptors.ToArray());
}
public PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(null);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
protected void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private void PropertyDescriptorPropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.OnPropertyChanged(e.PropertyName);
}
}
Inner class
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Child : ICustomTypeDescriptor, INotifyPropertyChanged
{
private readonly List<CustomPropertyDescriptor> propertyDescriptors = new List<CustomPropertyDescriptor>();
private readonly Dictionary<string, object> properties = new Dictionary<string, object>();
public Child()
{
// 'Dynamic' Property
string name = "Name";
object value = "Person's Child";
this.properties.Add(name, value);
var propertyDescriptor = new CustomPropertyDescriptor(
typeof(Person),
name,
value,
value.GetType().GetCustomAttributes(true).Cast<Attribute>().ToArray());
propertyDescriptor.PropertyChanged += this.PropertyDescriptorPropertyChanged;
this.propertyDescriptors.Add(propertyDescriptor);
}
public event PropertyChangedEventHandler PropertyChanged;
// Test Property (shouldn't be visible)
public string NotDynamic { get; set; }
public override string ToString()
{
return string.Format("{0} ({1})", this.properties["Name"], this.properties["Age"]);
}
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()
{
try
{
return this.propertyDescriptors.First();
}
catch (InvalidOperationException)
{
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)
{
return new PropertyDescriptorCollection(this.propertyDescriptors.ToArray());
}
public PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(null);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
protected void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private void PropertyDescriptorPropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.OnPropertyChanged(e.PropertyName);
}
}
Property Descriptor
public class CustomPropertyDescriptor : PropertyDescriptor, INotifyPropertyChanged
{
private readonly Type componentType;
private string name;
private object value;
public CustomPropertyDescriptor(Type componentType, string name, object value, Attribute[] attributes)
: base(name, attributes)
{
this.componentType = componentType;
this.name = name;
this.value = value;
}
public event PropertyChangedEventHandler PropertyChanged;
public override bool IsBrowsable
{
get
{
return true;
}
}
public override Type ComponentType
{
get { return this.componentType; }
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return this.value.GetType(); }
}
public override object GetValue(object component)
{
return this.value;
}
public override bool CanResetValue(object component)
{
return false;
}
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
this.value = value;
this.OnPropertyChanged(this.Name);
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
private void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
I think you incorrectly set the ComponentType property.
ComponentType is used to define property owner type.
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