简体   繁体   English

Xceed WPF propertyGrid 显示扩展集合的项目

[英]Xceed WPF propertyGrid show item for expanded collection

How, do I display a ObservableCollection<> of custom objects in the Xceed WPF PropertyGrid in which each List Item can be expanded to display the custom objects properties.如何在 Xceed WPF PropertyGrid 中显示自定义对象的ObservableCollection<> ,其中每个列表项都可以展开以显示自定义对象属性。 (ie: (即:

----PropertyGrid----- ----属性网格-----

CoreClass核心类

  • (+/-) ObservableCollection< CustomClass > (+/-) ObservableCollection<CustomClass>

    • (+/-) CustomClass.Object1 (+/-) CustomClass.Object1

      • Property1: Value属性 1:值

      • Property2: Value属性 2:值

      • PropertyN: Value属性N:值

    • (+/-) CustomClass.Object2 (+/-) CustomClass.Object2

      • Property1: Value属性 1:值

      • Property2: Value属性 2:值

      • PropertyN: Value属性N:值

If I use [ExpandableObject] on the ObservableCollection<> it only shows the Counts property.如果我在ObservableCollection<>上使用[ExpandableObject] ,它只显示 Counts 属性。

Edit: (Added code)编辑:(添加代码)

MainWindow.xaml:主窗口.xaml:

<Window x:Class="PropGridExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PropGridExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <xctk:PropertyGrid x:Name="PropertyGrid" SelectedObject="{Binding BindingItem}"></xctk:PropertyGrid>
    </Grid>
</Window>

MainWindow.xaml.cs主窗口.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        MainWindowViewModel mwvm = new MainWindowViewModel();
        this.DataContext = mwvm;
        InitializeComponent();
    }
}

MainWindowViewModel.cs主窗口视图模型.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<CustomClass> Classes { get; set; }

        public Item()
        {
            ID = 1;
            Classes = new ObservableCollection<CustomClass>();
            Classes.Add(new CustomClass() { Name = "CustomFoo" });
        }
    }

    public class CustomClass
    {
        public string Name { get; set; }
        [ExpandableObject()]
        public ObservableCollection<type> Types { get; set; }

        public CustomClass()
        {
            Types = new ObservableCollection<type>();
            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; }
    }
}

Note that most of this idea comes from the CodeProject project you linked to .请注意,这个想法大部分来自您链接到CodeProject 项目 The article gets you most of the way there, but as you note, it does not expand each item in the collection for the WPF PropertyGrid.本文为您提供了大部分方法,但正如您所注意到的,它没有扩展 WPF PropertyGrid 集合中的每个项目。 In order to do that, each "item" needs to have an ExpandableObjectAttribute .为了做到这一点,每个“项目”都需要有一个ExpandableObjectAttribute

In order to allow future StackOverflow readers to understand, I'm going to start from beginning.为了让未来的 StackOverflow 读者能够理解,我将从头开始。

From the beginning从一开始

So, starting from this example:所以,从这个例子开始:

public class MainWindowViewModel
{
  /// <summary> This the object we want to be able to edit in the data grid. </summary>
  public ComplexObject BindingComplexObject { get; set; }

  public MainWindowViewModel()
  {
    BindingComplexObject = new ComplexObject();
  }
}

public class ComplexObject
{
  public int ID { get; set; }

  public ObservableCollection<ComplexSubObject> Classes { get; set; }

  public ComplexObject()
  {
    ID = 1;
    Classes = new ObservableCollection<ComplexSubObject>();
    Classes.Add(new ComplexSubObject() { Name = "CustomFoo" });
    Classes.Add(new ComplexSubObject() { Name = "My Other Foo" });
  }
}

public class ComplexSubObject
{
  public string Name { get; set; }

  public ObservableCollection<SimpleValues> Types { get; set; }

  public ComplexSubObject()
  {
    Types = new ObservableCollection<SimpleValues>();
    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; }
}

In order for the WPF PropertyGrid to be able to edit each item in the ObservableCollection, we need to provide a type descriptor for the collection which return the items as "Properties" of that collection so they can be edited.为了让 WPF PropertyGrid 能够编辑 ObservableCollection 中的每个项目,我们需要为集合提供一个类型描述符,它将项目作为该集合的“属性”返回,以便可以对其进行编辑。 Because we cannot statically determine the items from a collection (as each collection has different number of elements), it means that the collection itself must be the TypeDescriptor, which means implementing ICustomTypeDescriptor .因为我们不能静态地确定集合中的项目(因为每个集合具有不同数量的元素),这意味着集合本身必须是 TypeDescriptor,这意味着实现ICustomTypeDescriptor

(note that only GetProperties is important for our purposes, the rest just delegates to TypeDescriptor ): (请注意,只有GetProperties对我们的目的很重要,其余的只是委托给TypeDescriptor ):

public class ExpandableObservableCollection<T> : ObservableCollection<T>,
                                                 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<T>(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
}

Additionally, we need an implementation of ItemPropertyDescriptor , which I provide here:此外,我们需要一个ItemPropertyDescriptor的实现,我在这里提供:

public class ItemPropertyDescriptor<T> : PropertyDescriptor
{
  private readonly ObservableCollection<T> _owner;
  private readonly int _index;

  public ItemPropertyDescriptor(ObservableCollection<T> 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<ExpandableObjectAttribute>().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();
}

Which for the most part, just sets up reasonable defaults, which you can tweak to serve your needs.大多数情况下,只需设置合理的默认值,您可以对其进行调整以满足您的需求。

One thing to note is that you may implement the Attributes property differently, depending on your use case.需要注意的一件事是,您可以根据您的用例以不同的方式实现Attributes属性。 If you don't do the "add it to the attribute collection if it's not there", then you need to add the attribute to the classes/types that you want to expand;如果你不做“如果它不存在就添加到属性集合中”,那么你需要将该属性添加到你想要扩展的类/类型中; if you do keep that code in, then you'll be able to expand every item in the collection no matter if the class/type has the attribute or not.如果您确实保留了该代码,那么无论类/类型是否具有该属性,您都可以扩展集合中的每个项目。

It then becomes a matter of using ExpandableObservableCollection in place of ObservableCollection .然后就变成了使用ExpandableObservableCollection代替ObservableCollection This kind of sucks as it means your ViewModel has view-stuff-ish stuff in it, but ¯\\_(ツ)_/¯ .这种很糟糕,因为这意味着您的ViewModel有视图内容,但是¯\\_(ツ)_/¯

Additionally, you need to add the ExpandableObjectAttribute to each of the properties that is a ExpandableObservableCollection .此外,您需要将添加ExpandableObjectAttribute到每一个是属性的ExpandableObservableCollection

Code Dump代码转储

If you're following along at home, you can use the following dialog code to run the example:如果您在家中学习,则可以使用以下对话框代码来运行示例:

<Window x:Class="WpfDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfDemo"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
      <xctk:PropertyGrid x:Name="It" />
    </Grid>
</Window>

- ——

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace WpfDemo
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();

      It.SelectedObject = new MainWindowViewModel().BindingComplexObject;
    }
  }
}

And here's the complete ViewModel implementation:这是完整的 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
  {
    /// <summary> This the object we want to be able to edit in the data grid. </summary>
    public ComplexObject BindingComplexObject { get; set; }

    public MainWindowViewModel()
    {
      BindingComplexObject = new ComplexObject();
    }
  }

  [ExpandableObject]
  public class ComplexObject
  {
    public int ID { get; set; }

    [ExpandableObject]
    public ExpandableObservableCollection<ComplexSubObject> Classes { get; set; }

    public ComplexObject()
    {
      ID = 1;
      Classes = new ExpandableObservableCollection<ComplexSubObject>();
      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<SimpleValues> Types { get; set; }

    public ComplexSubObject()
    {
      Types = new ExpandableObservableCollection<SimpleValues>();
      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<T> : ObservableCollection<T>,
                                                   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<T>(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<T> : PropertyDescriptor
  {
    private readonly ObservableCollection<T> _owner;
    private readonly int _index;

    public ItemPropertyDescriptor(ObservableCollection<T> 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<ExpandableObjectAttribute>().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 provided the major clues for this... MackieChan为此提供了主要线索……

There is no need to inherit from ICustomTypeDescriptor as similar results can be achieved using type converters.无需从 ICustomTypeDescriptor 继承,因为使用类型转换器可以获得类似的结果。

Firstly create an expandable object type converter and override the GetProperties method.首先创建一个可扩展的对象类型转换器并覆盖 GetProperties 方法。 For example, if you wish to maintain the index order of a generic IList type:例如,如果您希望维护通用 IList 类型的索引顺序:

using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;

public class MyExpandableIListConverter<T> : ExpandableObjectConverter
{
    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
    {
        if (value is IList<T> list)
        {
            PropertyDescriptorCollection propDescriptions = new PropertyDescriptorCollection(null);
            IEnumerator enumerator = list.GetEnumerator();
            int counter = -1;
            while (enumerator.MoveNext())
            {
                counter++;
                propDescriptions.Add(new ListItemPropertyDescriptor<T>(list, counter));

            }
            return propDescriptions;
        }
        else
        {
            return base.GetProperties(context, value, attributes);
        }
    }        
}

With the ListItemPropertyDescriptor being defined as follows: ListItemPropertyDescriptor 定义如下:

using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;

public class ListItemPropertyDescriptor<T> : PropertyDescriptor
{
    private readonly IList<T> owner;
    private readonly int index;

    public ListItemPropertyDescriptor(IList<T> 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<ExpandableObjectAttribute>().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();

}

Then you need to dynamically decorate the types you wish to display in the property grid with ExpandableObjectAttribute and TypeConverterAttribute.然后,您需要使用 ExpandableObjectAttribute 和 TypeConverterAttribute 动态修饰您希望在属性网格中显示的类型。 I create a 'decoration manager' to achieve this as follows.我创建了一个“装饰管理器”来实现这一点,如下所示。

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<I>(Type T)
    {
        TypeDescriptor.AddAttributes(T, new TypeConverterAttribute(typeof(MyExpandableIListConverter<I>)));
        TypeDescriptor.AddAttributes(T, new ExpandableObjectAttribute());
    }
}

Call AddExpandableObjectConverter for any type you would like to be expandable in the property grid and AddExpandableIListConverter for any IList type you would like to be expandable on the grid.为您希望在属性网格中展开的任何类型调用 AddExpandableObjectConverter,为您希望在网格上展开的任何 IList 类型调用 AddExpandableIListConverter。

For example, if you have a curve object with some properties including an IList then all of the properties and list items can be made expandable as follows:例如,如果您有一个带有一些属性的曲线对象,包括 IList,那么所有属性和列表项都可以按如下方式展开:

TypeDecorationManager.AddExpandableObjectConverter(typeof(Curve));
TypeDecorationManager.AddExpandableObjectConverter(typeof(CurvePoint));

AddCoreExpandableListConverter<CurvePoint>(typeof(IList<CurvePoint>));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM