自定义、复杂、动态的反射解决方案 - C#

9

我有许多自定义类,我将解释并发布示例。在说明它们的功能后,我将尝试清楚地描述出现错误的条件。

首先,我使用PropertyGrid来显示几种不同类型对象的属性。因为PropertyGrid的默认绑定不像我想要的那么描述性,所以我创建了一些自定义类,我将引用它们为“Display”类。这些Display类是通过传入一个对象并创建属性来构造的,这些属性返回漂亮格式化的字符串和描述,用于传递的真实对象的公共属性(在某些情况下还包括方法)。

我将用以下缩写示例代码进行演示:

这是一个我想在我的PropertyGrid中显示的对象示例:

public class Joint
{
   public Joint(...)
   {...}

   //properties
   public string Name { get; set;}
   public CustomObject CC { get; set;}
   public List<CustomObject> Custom List { get; set;}
}

字符串属性“Name”在PropertyGrid中显示正常。 然而,CustomObject和List的显示方式对我来说并不是很用户友好。

因此,我尝试通过编写这个类来解决问题:

public class DisplayJoint
{       

   private Joint _jnt;

   public DisplayJoint(Joint jnt)
   {
      _jnt = jnt;
   }

   //properties
   public string Name {  get { return _jnt.Name; } }

   [TypeConverterAttribute(typeof(ExpandableObjectConverter))]
   public DisplayCustomObject CC {  get { return new DisplayCustomObject(_jnt.CC); } }

   [TypeConverterAttribute(typeof(ExpandableObjectConverter))]
   public List<CustomObject> CustomList { get; set;}
}

如上所示的代码,我为我的Joint类和CustomObject类创建了特殊的DisplayClasses。在我的项目中,有许多不同类型的对象需要相同的重叠显示类属性。
如上所示,您可以看到我添加的行位于最后两个属性之上。
此行解决了我在propertGrid中想要以我想要的方式显示CustomObject的问题(几乎...稍后再说)。但是,对于我的自定义列表属性,它的工作方式并不相同。在自定义列表上,它会展开以仅显示Count和capacity(List的实际属性)。这是有道理的,但这不是我想要的。我想看到列表中包含的实际对象。

enter image description here

这里是我的复杂解决方案,最初来源于这个问题。我有两个类,用于动态添加对象到属性表格绑定列表中作为属性。第一个类(CustomClass)可以在此处下载。它用于动态创建属性。我使用的第二个类(DisplayIEnumerable)从第一个类派生而来,在此处找到。

DisplayIEnumerable类循环遍历列表对象,并向自身添加一个属性,其中包含每个对象的信息。传入DisplayClass以定义如何在网格中表示这些对象属性。

到目前为止,一切都很好!如下图所示(图片不是使用提供的类创建的,字符串在我使用的类中格式不同,已删除格式化代码以帮助你专注于相关代码:

enter image description here

现在在经过了漫长的介绍之后,真正的问题出现了。使用上述技术,我希望编写一个类,以动态地处理我没有为其编写唯一显示类的自定义对象。我打算将这段代码留给那些使用该应用程序进行测试的人,以便他们可以更有效地测试而无需针对我们公司的每个自定义对象都有完整的显示类。(有数百个)相反,通过将 propertyGrid 与以下类绑定,我希望所有具有相应 DisplayClasses 的列表和 CustomObjects 的属性被绑定到它们所在的位置。
这是我已经尝试并遇到错误的类。我还没有尝试使用我的 DisplayIEnumerable 类替换列表,我想先让基本功能正常工作:
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Reflection;
using System.Collections;
using System.Windows.Forms;

   internal class DisplayObject : CustomClass<T>
   {
      #region Variables
      protected T _obj;
      #endregion

      #region Constructor
      public DisplayObject(T obj)
      {
         if (obj != null)
         {
            try
            {
               Type currentType = typeof(T);
               foreach (PropertyInfo propertyInfo in currentType.GetProperties())
               {
                  Attribute[] attributes = new Attribute[1];
                  if (propertyInfo.GetType() is IEnumerable)
                     attributes[0] = new TypeConverterAttribute(typeof(ExpandableObjectConverter));
                  else
                     attributes[0] = null;
                  this.Add(new CustomProperty(propertyInfo.Name, propertyInfo, propertyInfo.GetType(), false, true, attributes));
               }
            }
            catch
            {
               MessageBox.Show("Failure!");
            }
         }
      }
      #endregion

      #region Properties
      [Browsable(false)]
      public object Item
      {
         get { return _obj; }
         set { _obj = value; }
      }
      #endregion
   }

当运行时,属性网格看起来应该是这样的: Before 然而,一旦您点击展开箭头,什么也不会发生,箭头就会消失: After 上面的类有什么问题,而我的DisplayIEnumerable类没有问题,导致这种行为差异?
我像这样使用DisplayObject类(在DisplayClass中):
  [TypeConverterAttribute(typeof(ExpandableObjectConverter))]
  public DisplayObject EndJoint { get { if (_member.bcEnd != null) { return new DisplayObject(_member.EndJoint); } else return null; } }

提前感谢!如果有人能回答这个问题,我会非常印象深刻。


为什么不使用标准的集合UITypeEditor(具有两列的标准表单)? - Simon Mourier
因为我无法递归地将这个可展开属性添加到包含在属性中的属性中,所以我希望用户能够钻取到他们想要的深度,完全看到不同对象之间的关系。 - jth41
标准的集合表单包括一个属性网格,因此它也是递归的。 - Simon Mourier
我能否将该数据网格动态绑定到显示类而不是基本对象本身? - jth41
它们都是标准的属性网格,因此它们支持自定义TypeDescriptors。 - Simon Mourier
2个回答

6

您无需创建特殊的类来使用属性网格。只需使用正确的属性修饰符对属性进行修饰即可。以下是一个示例:

两个自定义类:

public class MyObjType1
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return Name;
    }
}

public class MyObjType2
{
    public string Reference { get; set; }

    public override string ToString()
    {
        return Reference;
    }
}

请注意,ToString被覆盖了,如果没有为某个类型定义TypeConverter,则属性网格默认使用它。
一个“容器”类,其中包含一组自定义对象:
public class MyHolder
{
    public MyHolder()
    {
        Objects = new List<object>();
    }

    public string Name { get; set; }

    [TypeConverter(typeof(MyCollectionConverter))]
    public List<object> Objects { get; private set; }
}

请注意直接应用于Objects属性的自定义TypeConverter。以下是源代码:
public class MyCollectionConverter : ExpandableObjectConverter
{
    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
    {
        IEnumerable enumerable = value as IEnumerable;
        if (enumerable == null)
            return base.GetProperties(context, value, attributes);

        int i = 0;
        List<PropertyDescriptor> list = new List<PropertyDescriptor>();
        foreach (object obj in enumerable)
        {
            MyItemPropertyDescriptor index = new MyItemPropertyDescriptor(i.ToString(), obj);
            list.Add(index);
            i++;
        }
        return new PropertyDescriptorCollection(list.ToArray());
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType != typeof(string))
            return base.ConvertTo(context, culture, value, destinationType);

        IEnumerable enumerable = value as IEnumerable;
        if (enumerable == null)
            return base.ConvertTo(context, culture, value, destinationType);

        StringBuilder sb = new StringBuilder();
        foreach (object obj in enumerable)
        {
            if (sb.Length > 0)
            {
                sb.Append(',');
            }
            sb.AppendFormat("{0}", obj);
        }
        return sb.ToString();
    }
}

请注意,我们重写了ConvertTo并提供了一个特殊的字符串,用来显示列表中对象的逗号分隔列表。 GetProperties也被重写,并使用了一个特殊的PropertyDescriptor; 它为子对象添加了一个ExpandableObjectConverter属性,以便它们也可以展开:
public class MyItemPropertyDescriptor : PropertyDescriptor
{
    private object _value;

    public MyItemPropertyDescriptor(string name, object value)
        : base(name, new[] { new TypeConverterAttribute(typeof(ExpandableObjectConverter)) })
    {
        _value = value;
    }

    public override bool IsReadOnly
    {
        get { return false; }
    }

    public override object GetValue(object component)
    {
        return _value;
    }

    public override Type PropertyType
    {
        get { return _value == null ? typeof(object) : _value.GetType(); }
    }

    public override bool ShouldSerializeValue(object component)
    {
        return false;
    }

    public override Type ComponentType
    {
        get { return typeof(object); }
    }

    public override bool CanResetValue(object component)
    {
        return false;
    }

    public override void ResetValue(object component)
    {
    }

    public override void SetValue(object component, object value)
    {
    }
}

现在,这里有一些示例代码:
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        MyHolder holder = new MyHolder();
        for (int i = 0; i < 3; i++)
        {
            holder.Objects.Add(new MyObjType1 { Id = i, Name = i + "Name" });
        }
        for (int i = 0; i < 3; i++)
        {
            holder.Objects.Add(new MyObjType2 { Reference = "Ref" + i });
        }
        propertyGrid1.SelectedObject = holder;
    }
}

结果如下图所示:

enter image description here


该图片显示了结果。

抱歉几天前无法回复您的评论,我一直在旅行,现在才回到 SO。这个答案正是我所需要的,非常感谢。 - jth41
另外,如果您不介意我问的话...您已经回答了我的几个问题,我非常感激您的帮助,但我注意到您的个人资料上,尽管您回答了这么多问题,却没有一个提问过!? - jth41
@John - 不,我通常自己找到问题的答案(其中很多在SO上)。 :-) - Simon Mourier

1

我自己曾经使用过TypeConverters,我可以确认它们是一个大问题。你得不到任何关于实际出错的信息,只有奇怪的输出...

我不知道这是否有帮助,但也许问题在于你将一个空(null)数组添加到任何不是IEnumerable的东西上?尝试将添加指令移动到if (...)的范围内。我认为这样做没有任何危害。

此外,在最后一个示例中,您确定getter没有返回空指针吗?从我的经验来看,空白条目就像传递了空指针一样。


我明白你所说的反馈不佳和TypeConverters,但我不确定你所说的将add移动到if的范围内是什么意思。你能提供一些代码来进行可视化说明吗?谢谢。 - jth41

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接