Using ICustomTypeDescriptor with an ItemsControl

861 views Asked by At

I'm implementing ICustomTypeDescriptor so that I can create types with dynamic properties at runtime, however, instead of using the ICustomTypeDescriptor with a DataGrid which is how most people seem to use it, I'm wanting to use it with an ItemsControl.

Below is the guts of the application. (sorry, it's a lot of code)

public class BindableTypeDescriptor : INotifyPropertyChanged, ICustomTypeDescriptor
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void NotifyPropertyChanged(string _propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(_propertyName));
        }
    }

    class BindablePropertyDescriptor : PropertyDescriptor
    {
        public override bool IsReadOnly { get { return false; } }
        public override Type PropertyType { get { return m_type; } }
        public override Type ComponentType { get { return null; } }

        public BindablePropertyDescriptor(BindableTypeDescriptor _owner, Type _type, string _name)
            : base(_name, null)
        {
            m_owner = _owner;
            m_type = _type;
            m_name = _name;
        }

        public override void SetValue(object component, object _value)
        {
            m_owner[m_name] = _value;
        }

        public override object GetValue(object component)
        {
            return m_owner[m_name];
        }

        public override bool CanResetValue(object component)
        {
            return false;
        }

        public override void ResetValue(object component)
        {

        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }

        BindableTypeDescriptor m_owner;
        Type m_type;
        string m_name;
    }

    public IReadOnlyDictionary<string, object> Properties { get { return m_properties; } }

    public string GetComponentName() 
    { 
        return TypeDescriptor.GetComponentName(this, true); 
    }

    public EventDescriptor GetDefaultEvent() 
    { 
        return TypeDescriptor.GetDefaultEvent(this, true); 
    }

    public string GetClassName()
    {
        return TypeDescriptor.GetClassName(this, true);
    }

    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes, true);
    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return TypeDescriptor.GetEvents(this, true);
    }

    public TypeConverter GetConverter()
    {
        return TypeDescriptor.GetConverter(this, true);
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return m_properties;
    }

    public AttributeCollection GetAttributes()
    {
        return TypeDescriptor.GetAttributes(this, true);
    }

    public object GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(this, editorBaseType, true);
    }

    public PropertyDescriptor GetDefaultProperty()
    {
        return null;
    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(null);
    }

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        return new PropertyDescriptorCollection(
            m_properties.Select(e => new BindablePropertyDescriptor(this, e.Value != null ? e.Value.GetType() : typeof(object), e.Key))
            .ToArray());
    }

    public object this[string _name]
    {
        get { return m_properties[_name]; }
        set 
        {
            m_properties[_name] = value;
            NotifyPropertyChanged(_name);
        }
    }

    Dictionary<string, object> m_properties = new Dictionary<string, object>();
}

    public class Element 
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void NotifyPropertyChanged(string _propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(_propertyName));
        }
    }
}

public class StringElement : Element
{
    string m_value;
    public string Value
    {
        get { return m_value; }
        set
        {
            m_value = value;
            NotifyPropertyChanged("Value");
        }
    }
}

public class NumberElement : Element
{
    int m_value;
    public int Value
    {
        get { return m_value; }
        set
        {
            m_value = value;
            NotifyPropertyChanged("Value");
        }
    }
}

public partial class MainWindow : Window
{
    public BindableTypeDescriptor CustomType { get; private set; }

    public MainWindow()
    {
        CustomType = new BindableTypeDescriptor();
        CustomType["Name"] = new StringElement() { Value = "Dave" };
        CustomType["Age"] = new NumberElement() { Value = 5 };

        DataContext = this;
        InitializeComponent();
    }

    protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
    {
        base.OnMouseDoubleClick(e);
        CustomType["Name"] = new StringElement() { Value = "Fred" };
        CustomType["Age"] = new NumberElement() { Value = 6 };
    }
}

XAML:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:l="clr-namespace:WpfApplication1">
<ItemsControl ItemsSource="{Binding CustomType.Properties}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

                <Label Content="{Binding Key}"/>
                <ContentPresenter Content="{Binding Value}" Grid.Column="1">
                    <ContentPresenter.Resources>
                        <DataTemplate DataType="{x:Type l:StringElement}">
                            <TextBox Text="{Binding Value}"/>
                        </DataTemplate>
                        <DataTemplate DataType="{x:Type l:NumberElement}">
                            <Slider Value="{Binding Value}" Minimum="0" Maximum="50"/>
                        </DataTemplate>
                    </ContentPresenter.Resources>
                </ContentPresenter>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The problem is that with the above implementation, the ItemsControl doesn't update when I change a properties value (see OnMouseDoubleClick), which is to be expected as I'm binding to the initial "CustomType.Properties" when I should really be binding to each property by name. I can't do the latter though as I don't know the property names until runtime.

Therefore, I assume I need to be doing the binding dynamically in code behind, but I can't quite figure out how.

0

There are 0 answers