Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'Binding Builder' not interrogating nested ICustomTypeDescriptor (path empty)?

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));
        }
    }
}
like image 874
Jonny P Avatar asked Sep 01 '12 15:09

Jonny P


Video Answer


1 Answers

I think you incorrectly set the ComponentType property.

  1. Property: Person.Child should have the ComponentType set to typeof(Person) not typeof(Child). Just like property: Person.Name.
  2. Property: Child.Name should have the ComponentType set to typeof(Child).

ComponentType is used to define property owner type.

like image 196
Tomasz Malik Avatar answered Sep 17 '22 11:09

Tomasz Malik