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));