尝试与ICustomTypeDescriptor一起使用DataGridView [英] Trying to use DataGridView together with ICustomTypeDescriptor

查看:70
本文介绍了尝试与ICustomTypeDescriptor一起使用DataGridView的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 DataGridView 显示对象列表.在我要为其提供属性的类中,我具有一些C#属性,并且出于某些原因,我还希望动态创建属性.

这里有一个示例,该示例对于C#属性(FeatureId)正常工作,但动态创建的属性(Name)返回所有实例的第一个实例的值.为什么?

首先是一个实现ICustomPropertyDescriptor接口的类

 公共抽象类PropertyPresentationSubBase:ICustomTypeDescriptor{公共字符串GetClassName(){返回TypeDescriptor.GetClassName(this,true);}公共AttributeCollection GetAttributes(){返回TypeDescriptor.GetAttributes(this,true);}公共字符串GetComponentName(){返回TypeDescriptor.GetComponentName(this,true);}公共TypeConverter GetConverter(){返回TypeDescriptor.GetConverter(this,true);}公共EventDescriptor GetDefaultEvent(){返回TypeDescriptor.GetDefaultEvent(this,true);}公共PropertyDescriptor GetDefaultProperty(){返回TypeDescriptor.GetDefaultProperty(this,true);}公共对象GetEditor(Type editorBaseType){返回TypeDescriptor.GetEditor(this,editorBaseType,true);}公共EventDescriptorCollection GetEvents(Attribute []属性){返回TypeDescriptor.GetEvents(this,attribute,true);}公共EventDescriptorCollection GetEvents(){返回TypeDescriptor.GetEvents(this,true);}公共虚拟PropertyDescriptorCollection GetProperties(Attribute []属性){PropertyDescriptorCollection rtn = TypeDescriptor.GetProperties(this);//rtn = FilterReadonly(rtn,属性);返回新的PropertyDescriptorCollection(rtn.Cast< PropertyDescriptor>().ToArray());}公共虚拟PropertyDescriptorCollection GetProperties(){返回TypeDescriptor.GetProperties(this,true);}公共对象GetPropertyOwner(PropertyDescriptor pd){返回这个;}[可浏览(假)]public PropertyPresentationSubBase父级{得到{返回m_Parent;}放{m_Parent =值;}}PropertyPresentationSubBase m_Parent = null;[可浏览(假)]公共类型ValueType{得到{返回valueType;}放{valueType =值;}}私有类型valueType = null;[可浏览(假)]公共字符串名称{得到{返回sName;}放{sName =值;}}公共抽象对象GetValue();私有字符串sName = string.Empty;公共抽象无效更改(对象值);}} 

我还有一个继承自PropertyDescriptor的类

 公共类MyCustomPropertyDescriptor:PropertyDescriptor{PropertyPresentationSubBase m_Property;公共MyCustomPropertyDescriptor(PropertyPresentationSubBase myProperty,Attribute [] attrs,int propertyNo):base(myProperty.Name + propertyNo,attrs){m_Property = myProperty;}#region PropertyDescriptor特定公共重写bool CanResetValue(对象组件){返回false;}公共替代字符串名称{得到{返回"MyName";}}公共重写类型ComponentType{得到{返回null;}}公共重写对象GetValue(对象组件){返回m_Property.GetValue();}公共替代字符串说明{得到{返回描述";}}公共客体价值{得到{返回m_Property;}}公共替代字符串类别{得到{返回类别";}}公共替代字符串DisplayName{得到{返回m_Property.Name;}}公共重写布尔IsReadOnly{得到{返回false;}}公共重写void ResetValue(对象组件){//必须实现}公共重写bool ShouldSerializeValue(对象组件){返回false;}公共重写void SetValue(对象组件,对象值){m_Property.Change(值);}公共重写类型PropertyType{得到{if((m_Property!= null)&&(m_Property.ValueType!= null)){返回m_Property.ValueType;}别的{返回System.Type.Missing.GetType();}}}#endregion 

}

一个保存数据的小类:

 公共类QuadriFeatureItem{公共QuadriFeatureItem(int featureId,字符串名称){m_featureId = featureId;m_name =名称;}public int m_featureId;公共字符串m_name;} 

发送到网格的我的类(包含FeatureId属性和动态创建的属性)

 类FeaturePropertyPresentation:PropertyPresentationSubBase{公共int FeatureId{得到{return m_feature.m_featureId;}设置{m_feature.m_featureId =值;}}公共FeaturePropertyPresentation(QuadriFeatureItem项目){m_feature =项目;}private QuadriFeatureItem m_feature;公共重写PropertyDescriptorCollection GetProperties(Attribute []属性){PropertyDescriptorCollection rtn = base.GetProperties(attributes);CreateNameAttribute(ref rtn,attributes);返回rtn;}私有无效的CreateNameAttribute(参考PropertyDescriptorCollection pdc,Attribute []属性){NameProperty namePres = null;namePres =新的NameProperty(m_feature,this);pdc.Add(new MyCustomPropertyDescriptor(namePres,attribute,pdc.Count));}公共重写void更改(对象值){抛出新的NotImplementedException();}公共重写对象GetValue(){返回这个;} 

}

实现名称属性的类:

  class NameProperty:PropertyPresentationSubBase{公共NameProperty(QuadriFeatureItem功能,FeaturePropertyPresentation父级): 根据(){m_quadriFeatureItem =功能;父母=父母;ValueType = typeof(字符串);}private QuadriFeatureItem m_quadriFeatureItem;公共重写void更改(对象值){m_quadriFeatureItem.m_name =(字符串)值;}公共重写对象GetValue(){返回m_quadriFeatureItem.m_name;}} 

还有我的表单代码:

 公共Form1(){InitializeComponent();ShowGrid();}私有void ShowGrid(){QuadriFeatureItem no1 =新的QuadriFeatureItem(1,"Nummer1");QuadriFeatureItem no2 =新的QuadriFeatureItem(2,"Nummer2");QuadriFeatureItem no3 =新的QuadriFeatureItem(3,"Nummer3");BindingSource source =新的BindingSource();FeaturePropertyPresentation no1Pres =新的FeaturePropertyPresentation(no1);FeaturePropertyPresentation no2Pres =新FeaturePropertyPresentation(no2);FeaturePropertyPresentation no3Pres =新FeaturePropertyPresentation(no3);source.Add(no1Pres);source.Add(no2Pres);source.Add(no3Pres);dataGridView1.DataSource =源;表演();} 

但是网格在所有行上都显示"Nummer1".为什么?我在propertygrid中使用了此演示文稿类,并且效果很好.我还在属性网格中使用此MyCustomPropertyDescriptor.

我现在希望能够在datagridview中重用此presentationclass和MyCustomPropertyDescriptor.可以对MyCustomPropertyDescriptor或PropertyPresentationSubBase进行任何修改吗?

解决方案

问题是您的自定义属性描述符已绑定到具体实例.当您使用单项数据绑定(例如,将 TextBox 绑定到对象属性或在 PropertyGrid 控件中选择对象)时,该方法有效.但是,当您使用需要列表数据绑定的控件时(例如 DataGridView ListView ListBox ComboBox 列表等),此技术无效.为了自动填充列, DataGridView 需要一组对所有项目 common 通用的属性.为了做到这一点,它尝试了几种获取信息的方法(可以在这里找到很好的解释

I'm trying to use the DataGridView to display a list of objects. In the class that I want to present properties for I have some C# properties and I also want to create properties dynamically for some reasons.

Here I have an example, that works fine for the C# property (FeatureId) but the dynamically created property (Name) returns the value of the first instance for all the instances. Why?

First a class that implements the ICustomPropertyDescriptor interface

public abstract class PropertyPresentationSubBase : ICustomTypeDescriptor
{
public String GetClassName()
{
  return TypeDescriptor.GetClassName(this, true);
}

public AttributeCollection GetAttributes()
{
  return TypeDescriptor.GetAttributes(this, true);
}

public String GetComponentName()
{
  return TypeDescriptor.GetComponentName(this, true);
}

public TypeConverter GetConverter()
{
  return TypeDescriptor.GetConverter(this, true);
}

public EventDescriptor GetDefaultEvent()
{
  return TypeDescriptor.GetDefaultEvent(this, true);
}

public PropertyDescriptor GetDefaultProperty()
{
  return TypeDescriptor.GetDefaultProperty(this, true);
}

public object GetEditor(Type editorBaseType)
{
  return TypeDescriptor.GetEditor(this, editorBaseType, true);
}

public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
  return TypeDescriptor.GetEvents(this, attributes, true);
}

public EventDescriptorCollection GetEvents()
{
  return TypeDescriptor.GetEvents(this, true);
}


public virtual PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
  PropertyDescriptorCollection rtn = TypeDescriptor.GetProperties(this);

  //rtn = FilterReadonly(rtn, attributes);

  return new PropertyDescriptorCollection(rtn.Cast<PropertyDescriptor>().ToArray());


}

public virtual PropertyDescriptorCollection GetProperties()
{

  return TypeDescriptor.GetProperties(this, true);

}


public object GetPropertyOwner(PropertyDescriptor pd)
{
  return this;
}

[Browsable(false)]
public PropertyPresentationSubBase Parent
{
  get
  {
    return m_Parent;
  }
  set
  {
    m_Parent = value;
  }
}

PropertyPresentationSubBase m_Parent = null;

[Browsable(false)]
public Type ValueType
{
  get
  {
    return valueType;
  }
  set
  {
    valueType = value;
  }
}

private Type valueType = null;

[Browsable(false)]
public string Name
{
  get
  {
    return sName;
  }
  set
  {
    sName = value;
  }
}



public abstract object GetValue();

private string sName = string.Empty;

public abstract void Change(object value);


  }
}

I also have a class that inherit from PropertyDescriptor

public class MyCustomPropertyDescriptor : PropertyDescriptor
{
PropertyPresentationSubBase m_Property;

public MyCustomPropertyDescriptor(PropertyPresentationSubBase myProperty, Attribute[] attrs, int propertyNo)
  : base(myProperty.Name + propertyNo, attrs)
{
  m_Property = myProperty;
}

#region PropertyDescriptor specific

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

public override string Name
{
  get
  {
    return "MyName";
  }
}

public override Type ComponentType
{
  get
  {
    return null;
  }
}

public override object GetValue(object component)
{
  return m_Property.GetValue();
}


public override string Description
{
  get
  {
    return "Description";
  }
}



public object Value
{
  get
  {
    return m_Property;
  }
}


public override string Category
{
  get
  {
    return "Category";
  }
}

public override string DisplayName
{
  get
  {
    return m_Property.Name;
  }

}


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


public override void ResetValue(object component)
{
  //Have to implement
}

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




public override void SetValue(object component, object value)
{
  m_Property.Change(value);
}

public override Type PropertyType
{

  get
  {
    if ((m_Property != null) &&  (m_Property.ValueType != null))
    {
      return m_Property.ValueType;
    }
    else
    {
      return System.Type.Missing.GetType();

    }
  }
}

#endregion

}

A small class that holds the data:

public class QuadriFeatureItem
{
public QuadriFeatureItem(int featureId, string name)
{
  m_featureId = featureId;
  m_name = name;

}
public int m_featureId;

public string m_name;
}

My class that is sent to the grid (containing both the FeatureId property and the dynamically created property)

class FeaturePropertyPresentation : PropertyPresentationSubBase
{

public int FeatureId
{
  get
  {
    return m_feature.m_featureId;

  }
  set { m_feature.m_featureId = value; }
}

public FeaturePropertyPresentation(QuadriFeatureItem item)
{
  m_feature = item;
}

private QuadriFeatureItem m_feature;

public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
  PropertyDescriptorCollection rtn = base.GetProperties(attributes);

  CreateNameAttribute(ref rtn, attributes);

  return rtn;

}

private void CreateNameAttribute(ref PropertyDescriptorCollection pdc, Attribute[] attributes)
{

  NameProperty namePres = null;
  namePres = new NameProperty(m_feature, this);

  pdc.Add(new MyCustomPropertyDescriptor(namePres, attributes, pdc.Count));

}

public override void Change(object value)
{
  throw new NotImplementedException();
}

public override object GetValue()
{
  return this;
}

}

A class that implements the nameproperty:

class NameProperty : PropertyPresentationSubBase
{

public NameProperty(QuadriFeatureItem feature, FeaturePropertyPresentation parent)
  : base()
 {
   m_quadriFeatureItem = feature;
   Parent = parent;
   ValueType = typeof(string);

 }

private QuadriFeatureItem m_quadriFeatureItem;

public override void Change(object value)
{
  m_quadriFeatureItem.m_name = (string)value;
}

public override object GetValue()
{

  return m_quadriFeatureItem.m_name;

}  

}

And my formcode:

public Form1()
{
  InitializeComponent();

  ShowGrid();
}

private void ShowGrid()
{

  QuadriFeatureItem no1 = new QuadriFeatureItem(1, "Nummer1");
  QuadriFeatureItem no2 = new QuadriFeatureItem(2, "Nummer2");
  QuadriFeatureItem no3 = new QuadriFeatureItem(3, "Nummer3");

  BindingSource source = new BindingSource();

  FeaturePropertyPresentation no1Pres = new FeaturePropertyPresentation(no1);
  FeaturePropertyPresentation no2Pres = new FeaturePropertyPresentation(no2);
  FeaturePropertyPresentation no3Pres = new FeaturePropertyPresentation(no3);

  source.Add(no1Pres);
  source.Add(no2Pres);
  source.Add(no3Pres);

  dataGridView1.DataSource = source;

  Show();      
}

But the grid shows "Nummer1" for all rows. Why? I use this presentation classes in a propertygrid and it works fine. I also use this MyCustomPropertyDescriptor in a propertygrid.

My wish is now to be able to reuse this presentationclasses and MyCustomPropertyDescriptor in a datagridview. Is it possible with any modification in the MyCustomPropertyDescriptor or PropertyPresentationSubBase?

解决方案

The problem is that your custom property descriptor is bound to a concrete instance. Which works when you use single item data binding (like TextBox to your object property or selecting your object in a PropertyGrid control). However, when you use a control that requires list data binding (like DataGridView, ListView, ListBox, ComboBox list etc.) this technique doesn't work. In order to auto populate the columns, DataGridView needs a set of properties that are common to all items. In order to do that, it tries several ways to obtain that information (a good explanation can be found here DataGridView not showing properites of objects which implement ICustomTypeDescriptor), and one of them is to take the first item of the list and ask for properties (hence your debugging experience). Anyway, in order to make this work in a list binding scenarios, your property descriptor needs to be implemented differently.

Notice the signature of the PropertyDescriptors GetValue/SetValue methods. Both they have an argument object component. This is the object instance you need to return or set the value. You can think of property descriptor being an inverse of what we usually use in a programming language. So instead of

var val = obj.Property;
obj.Property = val;

we have

var val = propertyDescriptor.GetValue(obj);
propertyDescriptor.SetValue(obj, val);

In other words, you should not "embed" your object instance inside the property descriptor, but use the passed argument.

Here is a sample generic implementation of a property descriptor doing just that:

public class SimplePropertyDescriptor<TComponent, TValue> : PropertyDescriptor
    where TComponent : class
{
    private readonly Func<TComponent, TValue> getValue;
    private readonly Action<TComponent, TValue> setValue;
    private readonly string displayName;
    public SimplePropertyDescriptor(string name, Attribute[] attrs, Func<TComponent, TValue> getValue, Action<TComponent, TValue> setValue = null, string displayName = null)
        : base(name, attrs)
    {
        Debug.Assert(getValue != null);
        this.getValue = getValue;
        this.setValue = setValue;
        this.displayName = displayName;
    }
    public override string DisplayName { get { return displayName ?? base.DisplayName; } }
    public override Type ComponentType { get { return typeof(TComponent); } }
    public override bool IsReadOnly { get { return setValue == null; } }
    public override Type PropertyType { get { return typeof(TValue); } }
    public override bool CanResetValue(object component) { return false; }
    public override bool ShouldSerializeValue(object component) { return false; }
    public override void ResetValue(object component) { }
    public override object GetValue(object component) { return getValue((TComponent)component); }
    public override void SetValue(object component, object value) { setValue((TComponent)component, (TValue)value); }
}

sample usage with your stuff:

public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
    var properties = base.GetProperties(attributes);
    // Custom name property
    properties.Add(new SimplePropertyDescriptor<FeaturePropertyPresentation, string>("FeatureName", attributes,
        getValue: component => component.m_feature.m_name,
        setValue: (component, value) => component.m_feature.m_name = value, // remove this line to make it readonly
        displayName: "Feature Name"
    ));
    return properties;
}

and, putting it all together, a small example equivalent to yours:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;

namespace Samples
{
    // Generic implemenation of a property descriptor
    public class SimplePropertyDescriptor<TComponent, TValue> : PropertyDescriptor
        where TComponent : class
    {
        private readonly Func<TComponent, TValue> getValue;
        private readonly Action<TComponent, TValue> setValue;
        private readonly string displayName;
        public SimplePropertyDescriptor(string name, Attribute[] attrs, Func<TComponent, TValue> getValue, Action<TComponent, TValue> setValue = null, string displayName = null)
            : base(name, attrs)
        {
            Debug.Assert(getValue != null);
            this.getValue = getValue;
            this.setValue = setValue;
            this.displayName = displayName;
        }
        public override string DisplayName { get { return displayName ?? base.DisplayName; } }
        public override Type ComponentType { get { return typeof(TComponent); } }
        public override bool IsReadOnly { get { return setValue == null; } }
        public override Type PropertyType { get { return typeof(TValue); } }
        public override bool CanResetValue(object component) { return false; }
        public override bool ShouldSerializeValue(object component) { return false; }
        public override void ResetValue(object component) { }
        public override object GetValue(object component) { return getValue((TComponent)component); }
        public override void SetValue(object component, object value) { setValue((TComponent)component, (TValue)value); }
    }
    // Your stuff
    public abstract class PropertyPresentationSubBase : ICustomTypeDescriptor
    {
        public string GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }

        public AttributeCollection GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this, true);
        }

        public String GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }

        public TypeConverter GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }

        public EventDescriptor GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }

        public PropertyDescriptor GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(this, true);
        }

        public object GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }

        public EventDescriptorCollection GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }

        public EventDescriptorCollection GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }

        public virtual PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection rtn = TypeDescriptor.GetProperties(this);

            //rtn = FilterReadonly(rtn, attributes);

            return new PropertyDescriptorCollection(rtn.Cast<PropertyDescriptor>().ToArray());
        }

        public virtual PropertyDescriptorCollection GetProperties()
        {

            return TypeDescriptor.GetProperties(this, true);

        }


        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }

        [Browsable(false)]
        public PropertyPresentationSubBase Parent
        {
            get
            {
                return m_Parent;
            }
            set
            {
                m_Parent = value;
            }
        }

        PropertyPresentationSubBase m_Parent = null;

        [Browsable(false)]
        public Type ValueType
        {
            get
            {
                return valueType;
            }
            set
            {
                valueType = value;
            }
        }

        private Type valueType = null;

        [Browsable(false)]
        public string Name
        {
            get
            {
                return sName;
            }
            set
            {
                sName = value;
            }
        }



        public abstract object GetValue();

        private string sName = string.Empty;

        public abstract void Change(object value);
    }
    public class QuadriFeatureItem
    {
        public QuadriFeatureItem(int featureId, string name)
        {
            m_featureId = featureId;
            m_name = name;

        }
        public int m_featureId;

        public string m_name;
    }
    class FeaturePropertyPresentation : PropertyPresentationSubBase
    {

        public int FeatureId
        {
            get
            {
                return m_feature.m_featureId;

            }
            set { m_feature.m_featureId = value; }
        }

        public FeaturePropertyPresentation(QuadriFeatureItem item)
        {
            m_feature = item;
        }

        private QuadriFeatureItem m_feature;

        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            var properties = base.GetProperties(attributes);
            // Custom name property
            properties.Add(new SimplePropertyDescriptor<FeaturePropertyPresentation, string>("FeatureName", attributes,
                getValue: component => component.m_feature.m_name,
                setValue: (component, value) => component.m_feature.m_name = value, // remove this line to make it readonly
                displayName: "Feature Name"
            ));
            return properties;
        }
        public override void Change(object value)
        {
            throw new NotImplementedException();
        }

        public override object GetValue()
        {
            return this;
        }

    }
    // Test
    static class Test
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var dataSet = Enumerable.Range(1, 10).Select(n => new FeaturePropertyPresentation(new QuadriFeatureItem(n, "Nummer" + n))).ToList();
            var form = new Form();
            var dg = new DataGridView { Dock = DockStyle.Fill, Parent = form };
            dg.DataSource = dataSet;
            Application.Run(form);
        }
    }
}

result:

这篇关于尝试与ICustomTypeDescriptor一起使用DataGridView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆