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.