Xceed WPF propertyGrid显示扩展集合的项目
如何在Xceed WPF PropertyGrid中显示自定义对象的ObservableCollection
,其中每个List Item都可以展开以显示自定义对象属性。 (即:
—- —– PropertyGrid的
CoreClass
-
(+/-)ObservableCollection
-
(+/-)CustomClass.Object1
-
Property1:价值
-
Property2:价值
-
…
-
PropertyN:价值
-
-
(+/-)CustomClass.Object2
-
Property1:价值
-
Property2:价值
-
…
-
PropertyN:价值
-
-
如果我在ObservableCollection
上使用[ExpandableObject]
,它只显示Counts属性。
编辑:(添加代码)
MainWindow.xaml:
MainWindow.xaml.cs
public partial class MainWindow : Window { public MainWindow() { MainWindowViewModel mwvm = new MainWindowViewModel(); this.DataContext = mwvm; InitializeComponent(); } }
MainWindowViewModel.cs
public class MainWindowViewModel { public Item BindingItem { get; set; } public MainWindowViewModel() { BindingItem = new Item(); } public class Item { public int ID { get; set; } [ExpandableObject()] public ObservableCollection Classes { get; set; } public Item() { ID = 1; Classes = new ObservableCollection(); Classes.Add(new CustomClass() { Name = "CustomFoo" }); } } public class CustomClass { public string Name { get; set; } [ExpandableObject()] public ObservableCollection Types { get; set; } public CustomClass() { Types = new ObservableCollection(); Types.Add(new type() { name = "foo", value = "bar" }); Types.Add(new type() { name = "bar", value = "foo" }); } } public class type { public string name { get; set; } public string value { get; set; } } }
请注意,这个想法的大部分来自您链接到的CodeProject项目 。 本文将为您提供大部分内容,但正如您所注意到的,它不会扩展WPF PropertyGrid集合中的每个项目。 为此,每个“项”都需要具有ExpandableObjectAttribute
。
为了让将来的StackOverflow读者能够理解,我将从头开始。
从最开始
所以,从这个例子开始:
public class MainWindowViewModel { /// This the object we want to be able to edit in the data grid. public ComplexObject BindingComplexObject { get; set; } public MainWindowViewModel() { BindingComplexObject = new ComplexObject(); } } public class ComplexObject { public int ID { get; set; } public ObservableCollection Classes { get; set; } public ComplexObject() { ID = 1; Classes = new ObservableCollection (); Classes.Add(new ComplexSubObject() { Name = "CustomFoo" }); Classes.Add(new ComplexSubObject() { Name = "My Other Foo" }); } } public class ComplexSubObject { public string Name { get; set; } public ObservableCollection Types { get; set; } public ComplexSubObject() { Types = new ObservableCollection (); Types.Add(new SimpleValues() { name = "foo", value = "bar" }); Types.Add(new SimpleValues() { name = "bar", value = "foo" }); } } public class SimpleValues { public string name { get; set; } public string value { get; set; } }
为了使WPF PropertyGrid能够编辑ObservableCollection中的每个项目,我们需要为集合提供类型描述符,并将项目作为可以编辑的集合的“属性”返回。 因为我们无法静态地确定集合中的项(因为每个集合具有不同数量的元素),这意味着集合本身必须是TypeDescriptor,这意味着实现ICustomTypeDescriptor
。
(请注意,只有GetProperties
对我们的目的很重要,其余的只是委托给TypeDescriptor
):
public class ExpandableObservableCollection : ObservableCollection , ICustomTypeDescriptor { PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() { // Create a collection object to hold property descriptors PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null); for (int i = 0; i < Count; i++) { pds.Add(new ItemPropertyDescriptor (this, i)); } return pds; } #region Use default TypeDescriptor stuff AttributeCollection ICustomTypeDescriptor.GetAttributes() { return TypeDescriptor.GetAttributes(this, noCustomTypeDesc: true); } string ICustomTypeDescriptor.GetClassName() { return TypeDescriptor.GetClassName(this, noCustomTypeDesc: true); } string ICustomTypeDescriptor.GetComponentName() { return TypeDescriptor.GetComponentName(this, noCustomTypeDesc: true); } TypeConverter ICustomTypeDescriptor.GetConverter() { return TypeDescriptor.GetConverter(this, noCustomTypeDesc: true); } EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() { return TypeDescriptor.GetDefaultEvent(this, noCustomTypeDesc: true); } PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() { return TypeDescriptor.GetDefaultProperty(this, noCustomTypeDesc: true); } object ICustomTypeDescriptor.GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, noCustomTypeDesc: true); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents() { return TypeDescriptor.GetEvents(this, noCustomTypeDesc: true); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, noCustomTypeDesc: true); } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) { return TypeDescriptor.GetProperties(this, attributes, noCustomTypeDesc: true); } object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) { return this; } #endregion }
另外,我们需要一个ItemPropertyDescriptor
的实现,我在这里提供:
public class ItemPropertyDescriptor : PropertyDescriptor { private readonly ObservableCollection _owner; private readonly int _index; public ItemPropertyDescriptor(ObservableCollection owner, int index) : base("#" + index, null) { _owner = owner; _index = index; } public override AttributeCollection Attributes { get { var attributes = TypeDescriptor.GetAttributes(GetValue(null), false); if (!attributes.OfType().Any()) { // copy all the attributes plus an extra one (the // ExpandableObjectAttribute) // this ensures that even if the type of the object itself doesn't have the // ExpandableObjectAttribute, it will still be expandable. var newAttributes = new Attribute[attributes.Count + 1]; attributes.CopyTo(newAttributes, newAttributes.Length - 1); newAttributes[newAttributes.Length - 1] = new ExpandableObjectAttribute(); // overwrite the array attributes = new AttributeCollection(newAttributes); } return attributes; } } public override bool CanResetValue(object component) { return false; } public override object GetValue(object component) { return Value; } private T Value => _owner[_index]; public override void ResetValue(object component) { throw new NotImplementedException(); } public override void SetValue(object component, object value) { _owner[_index] = (T)value; } public override bool ShouldSerializeValue(object component) { return false; } public override Type ComponentType => _owner.GetType(); public override bool IsReadOnly => false; public override Type PropertyType => Value?.GetType(); }
在大多数情况下,只需设置合理的默认值,您可以调整以满足您的需求。
需要注意的一点是,您可以以不同的方式实现Attributes
属性,具体取决于您的用例。 如果你没有“将它添加到属性集合中,如果它不存在”,那么你需要将属性添加到你想要扩展的类/类型中; 如果您确实保留了该代码,那么无论类/类型是否具有该属性,您都可以扩展集合中的每个项目。
然后,使用ExpandableObservableCollection
代替ObservableCollection
成为问题。 这种方式很糟糕,因为这意味着你的ViewModel
有一些视图东西,但是¯\_(ツ)_/¯
。
此外,您需要将ExpandableObjectAttribute
添加到每个属性ExpandableObservableCollection
。
代码转储
如果您在家中跟随,可以使用以下对话框代码来运行示例:
–
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows; namespace WpfDemo { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); It.SelectedObject = new MainWindowViewModel().BindingComplexObject; } } }
这是完整的ViewModel实现:
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; namespace WpfDemo { public class MainWindowViewModel { /// This the object we want to be able to edit in the data grid. public ComplexObject BindingComplexObject { get; set; } public MainWindowViewModel() { BindingComplexObject = new ComplexObject(); } } [ExpandableObject] public class ComplexObject { public int ID { get; set; } [ExpandableObject] public ExpandableObservableCollection Classes { get; set; } public ComplexObject() { ID = 1; Classes = new ExpandableObservableCollection (); Classes.Add(new ComplexSubObject() { Name = "CustomFoo" }); Classes.Add(new ComplexSubObject() { Name = "My Other Foo" }); } } [ExpandableObject] public class ComplexSubObject { public string Name { get; set; } [ExpandableObject] public ExpandableObservableCollection Types { get; set; } public ComplexSubObject() { Types = new ExpandableObservableCollection (); Types.Add(new SimpleValues() { name = "foo", value = "bar" }); Types.Add(new SimpleValues() { name = "bar", value = "foo" }); } } public class SimpleValues { public string name { get; set; } public string value { get; set; } } public class ExpandableObservableCollection : ObservableCollection , ICustomTypeDescriptor { PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() { // Create a collection object to hold property descriptors PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null); for (int i = 0; i < Count; i++) { pds.Add(new ItemPropertyDescriptor (this, i)); } return pds; } #region Use default TypeDescriptor stuff AttributeCollection ICustomTypeDescriptor.GetAttributes() { return TypeDescriptor.GetAttributes(this, noCustomTypeDesc: true); } string ICustomTypeDescriptor.GetClassName() { return TypeDescriptor.GetClassName(this, noCustomTypeDesc: true); } string ICustomTypeDescriptor.GetComponentName() { return TypeDescriptor.GetComponentName(this, noCustomTypeDesc: true); } TypeConverter ICustomTypeDescriptor.GetConverter() { return TypeDescriptor.GetConverter(this, noCustomTypeDesc: true); } EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() { return TypeDescriptor.GetDefaultEvent(this, noCustomTypeDesc: true); } PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() { return TypeDescriptor.GetDefaultProperty(this, noCustomTypeDesc: true); } object ICustomTypeDescriptor.GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, noCustomTypeDesc: true); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents() { return TypeDescriptor.GetEvents(this, noCustomTypeDesc: true); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, noCustomTypeDesc: true); } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) { return TypeDescriptor.GetProperties(this, attributes, noCustomTypeDesc: true); } object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) { return this; } #endregion } public class ItemPropertyDescriptor : PropertyDescriptor { private readonly ObservableCollection _owner; private readonly int _index; public ItemPropertyDescriptor(ObservableCollection owner, int index) : base("#" + index, null) { _owner = owner; _index = index; } public override AttributeCollection Attributes { get { var attributes = TypeDescriptor.GetAttributes(GetValue(null), false); if (!attributes.OfType().Any()) { // copy all the attributes plus an extra one (the // ExpandableObjectAttribute) // this ensures that even if the type of the object itself doesn't have the // ExpandableObjectAttribute, it will still be expandable. var newAttributes = new Attribute[attributes.Count + 1]; attributes.CopyTo(newAttributes, newAttributes.Length - 1); newAttributes[newAttributes.Length - 1] = new ExpandableObjectAttribute(); // overwrite the original attributes = new AttributeCollection(newAttributes); } return attributes; } } public override bool CanResetValue(object component) { return false; } public override object GetValue(object component) { return Value; } private T Value => _owner[_index]; public override void ResetValue(object component) { throw new NotImplementedException(); } public override void SetValue(object component, object value) { _owner[_index] = (T)value; } public override bool ShouldSerializeValue(object component) { return false; } public override Type ComponentType => _owner.GetType(); public override bool IsReadOnly => false; public override Type PropertyType => Value?.GetType(); } }
MackieChan为此提供了主要线索……
不需要inheritanceICustomTypeDescriptor,因为使用类型转换器可以实现类似的结果。
首先创建一个可扩展的对象类型转换器并覆盖GetProperties方法。 例如,如果您希望维护通用IList类型的索引顺序:
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; using System.ComponentModel; public class MyExpandableIListConverter : ExpandableObjectConverter { public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { if (value is IList ) { IList list = value as IList ; PropertyDescriptorCollection propDescriptions = new PropertyDescriptorCollection(null); IEnumerator enumerator = list.GetEnumerator(); int counter = -1; while (enumerator.MoveNext()) { counter++; propDescriptions.Add(new ListItemPropertyDescriptor (list, counter)); } return propDescriptions; } else { return base.GetProperties(context, value, attributes); } } }
ListItemPropertyDescriptor的定义如下:
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; using System.ComponentModel; public class ListItemPropertyDescriptor : PropertyDescriptor { private readonly IList owner; private readonly int index; public ListItemPropertyDescriptor(IList owner, int index) : base("["+ index+"]", null) { this.owner = owner; this.index = index; } public override AttributeCollection Attributes { get { var attributes = TypeDescriptor.GetAttributes(GetValue(null), false); //If the Xceed expandable object attribute is not applied then apply it if (!attributes.OfType().Any()) { attributes = AddAttribute(new ExpandableObjectAttribute(), attributes); } //set the xceed order attribute attributes = AddAttribute(new PropertyOrderAttribute(index), attributes); return attributes; } } private AttributeCollection AddAttribute(Attribute newAttribute, AttributeCollection oldAttributes) { Attribute[] newAttributes = new Attribute[oldAttributes.Count + 1]; oldAttributes.CopyTo(newAttributes, 1); newAttributes[0] = newAttribute; return new AttributeCollection(newAttributes); } public override bool CanResetValue(object component) { return false; } public override object GetValue(object component) { return Value; } private T Value => owner[index]; public override void ResetValue(object component) { throw new NotImplementedException(); } public override void SetValue(object component, object value) { owner[index] = (T)value; } public override bool ShouldSerializeValue(object component) { return false; } public override Type ComponentType => owner.GetType(); public override bool IsReadOnly => false; public override Type PropertyType => Value?.GetType(); }
然后,您需要使用ExpandableObjectAttribute和TypeConverterAttribute动态修饰要在属性网格中显示的类型。 我创建了一个“装饰管理器”来实现此目的如下。
using System.ComponentModel; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; public static class TypeDecorationManager { public static void AddExpandableObjectConverter(Type T) { TypeDescriptor.AddAttributes(T, new TypeConverterAttribute(typeof(ExpandableObjectConverter))); TypeDescriptor.AddAttributes(T, new ExpandableObjectAttribute()); } public static void AddExpandableIListConverter(Type T) { TypeDescriptor.AddAttributes(T, new TypeConverterAttribute(typeof(MyExpandableIListConverter))); TypeDescriptor.AddAttributes(T, new ExpandableObjectAttribute()); } }
对于您希望在属性网格中可扩展的任何类型调用AddExpandableObjectConverter,并为要在网格上展开的任何IList类型调用AddExpandableIListConverter。
例如,如果您有一个包含IList的某些属性的曲线对象,则可以使所有属性和列表项可扩展,如下所示:
ObjectDecorationManager.AddExpandableObjectConverter(typeof(Curve)); ObjectDecorationManager.AddExpandableObjectConverter(typeof(CurvePoint)); AddCoreExpandableListConverter(typeof(IList ));