我正在实现 ICustomTypeDescriptor,以便可以在运行时创建具有动态属性的类型,但是,我不想将 ICustomTypeDescriptor 与大多数人似乎使用的 DataGrid 一起使用,而是希望将它与 ItemsControl 一起使用。
以下是应用程序的内容。(对不起,代码很多)
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>
问题是,通过上述实现,当我更改属性值时 ItemsControl 不会更新(请参阅 OnMouseDoubleClick),这是可以预料的,因为我在真正应该绑定时绑定到初始“CustomType.Properties”按名称分配给每个属性。我不能做后者,因为直到运行时我才知道属性名称。
因此,我假设我需要在后面的代码中动态地进行绑定,但我不太清楚如何。