【问题标题】:Issue with Grid data binding网格数据绑定问题
【发布时间】:2011-08-05 01:02:42
【问题描述】:

我们的应用程序以特定结构生成大量结果列表。问题是,如果我想在 DataGrid 中显示它,我必须创建一个 DataTable 并将其设置为将使用内存的网格的数据源。因此,我创建了一个我创建的类的 BindingList(称为 myRow),并且在 myRow 的结构中,我拥有我需要的所有字段作为指向中值的属性结果的实际列表。但问题是用户可以添加自定义列的结果列表;我无法动态更改 myRow 的属性,并且我不想使用 DataTable(因为它将与我的实际结果重复)并且如果我直接在 dataGrid 中创建自定义列和逐个单元格设置它们的值,内存中网格的大小非常高(我认为这是因为逐个单元格设置值会导致为每个单元格保存单元格的属性,而不是更大的标准)。那么有谁知道如何使用不同于使用属性作为列的策略创建一个行类,以便我可以在运行时动态设置列数?

【问题讨论】:

  • 是的...其实是一个UltraGrid,应该和普通的Grid一样

标签: c# winforms data-binding memory


【解决方案1】:

我认为这可以通过使用TypeDescriptionProvider 来完成。

坏消息是:我以前从未这样做过,无法提供太多帮助

好消息是:我在这里找到了一个示例:DataGridView not showing properites of objects which implement ICustomTypeDescriptor

//编辑

我使用代码(参见上面的链接)构建了一个示例,如何避免每个对象的字典...

public class myRow
{
    //your data storage class ... 
    public string txt { get; set; }
    public int id { get; set; }
}
public class MyView:ICustomTypeDescriptor
{//your extendable view class ...
    private static PropertyDescriptorCollection props = null;
    static MyView()
    {
        TypeDescriptionProvider defaultProvider = TypeDescriptor.GetProvider(typeof(MyView));
        props = new PropertyDescriptorCollection(defaultProvider.GetTypeDescriptor(typeof(MyView)).GetProperties().Cast<PropertyDescriptor>().ToArray(), true);
    }

    public static void addProperty(string name, DataTable dt, Func<DataRow, object> getter, Action<DataRow, object> setter, Func<DataTable, MyView, DataRow> rowSelector, Type PropType)
    {
        List<PropertyDescriptor> tmp;
        if (props != null) tmp = props.Cast<PropertyDescriptor>().ToList();
        else tmp = new List<PropertyDescriptor>();
        PropertyDescriptor pd = TypeDescriptor.CreateProperty(typeof(MyView), name, PropType, null);
        pd = new MyViewPropertyDescriptor(pd, dt, getter, setter, rowSelector, PropType);
        tmp.Add(pd);
        props = new PropertyDescriptorCollection(tmp.ToArray(), true);
    }

    //the data storage obj this view is referencing
    public myRow obj;

    public string TXT { // view-member known at compile time
        get { return obj.txt; }
        set { obj.txt = value; }
    }

    internal class MyViewPropertyDescriptor : PropertyDescriptor
    {   // an example property descriptor that can link to data in a DataTable ... 
        DataTable dt;
        Func<DataRow, object> getter;
        Action<DataRow, object> setter;
        Func<DataTable, MyView, DataRow> rowSelector;
        Type type;
        public MyViewPropertyDescriptor(PropertyDescriptor descr, DataTable dt, Func<DataRow, object> getter, Action<DataRow, object> setter, Func<DataTable, MyView, DataRow> rowSelector, Type PropType)
            : base(descr)
        {
            this.dt = dt; // storage for additional data referenced by this property
            this.getter = getter; //a getter that will take a DataRow, and extract the property value
            this.setter = setter; //a setter that will take a DataRow and a value
            this.rowSelector = rowSelector;//a row selector ... takes a dataset and the view object and has to return the assiciated datarow
            this.type = PropType; // the type of this property
        }

        public override object GetValue(object component)
        {
            // using row selector and getter to return the current value ... you should add errorhandling here
            return getter(rowSelector(dt, (MyView)component));
        }
        public override void SetValue(object component, object value)
        {   // the setter ... needs errorhandling too
            setter(rowSelector(dt, (MyView)component), value);
        }
        public override void ResetValue(object component)
        {

        }
        public override bool CanResetValue(object component)
        {
            return false;
        }
        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }
        public override Type PropertyType
        {
            get { return type; }
        }
        public override bool IsReadOnly
        {
            get { return false; }
        }
        public override Type ComponentType
        {
            get { return typeof(MyView); }
        }

    }

    ICustomTypeDescriptor defaultDescriptor = TypeDescriptor.GetProvider(typeof(MyView)).GetTypeDescriptor(typeof(MyView));
    AttributeCollection ICustomTypeDescriptor.GetAttributes()
    {
        return defaultDescriptor.GetAttributes();
    }
    string ICustomTypeDescriptor.GetClassName()
    {
        return defaultDescriptor.GetClassName();
    }
    string ICustomTypeDescriptor.GetComponentName()
    {
        return defaultDescriptor.GetComponentName();
    }
    TypeConverter ICustomTypeDescriptor.GetConverter()
    {
        return defaultDescriptor.GetConverter();
    }
    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
    {
        return defaultDescriptor.GetDefaultEvent();
    }
    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
    {
        return defaultDescriptor.GetDefaultProperty();
    }
    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return defaultDescriptor.GetEditor(editorBaseType);
    }
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return defaultDescriptor.GetEvents(attributes);
    }
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return defaultDescriptor.GetEvents();
    }
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        return props; // should really be filtered, but meh!
    }
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return props;
    }
    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }

}

还有一个利用这个的小例子......

private void button1_Click(object sender, EventArgs e)
{
    if (dataGridView1.DataSource == null)
    {
        List<myRow> data = new List<myRow>();
        data.Add(new myRow { id = 1, txt = "test 1" });
        data.Add(new myRow { id = 2, txt = "test 2" });
        data.Add(new myRow { id = 3, txt = "test 3" });
        DataTable dt = new DataTable();
        dt.Columns.Add("id", typeof(int));
        dt.Columns.Add("additionalData1", typeof(int));
        dt.Columns.Add("additionalData2", typeof(int));
        Random rnd = new Random();
        foreach (var item in data)
        {
            dt.Rows.Add(new object[] { item.id, rnd.Next(), rnd.Next() });
        }
        MyView.addProperty("additionalData1", dt, row => row["additionalData1"], (row, val) => row["additionalData1"] = val, (tab, v) => tab.Rows.OfType<DataRow>().First(x => x["id"].Equals(v.obj.id)), typeof(int));
        MyView.addProperty("additionalData2", dt, row => row["additionalData2"], (row, val) => row["additionalData2"] = val, (tab, v) => tab.Rows.OfType<DataRow>().First(x => x["id"].Equals(v.obj.id)), typeof(int));

        dataGridView1.DataSource = new BindingList<MyView>(data.Select(x => new MyView { obj = x }).ToList());
    }
}

当然,您会希望提供更好的 rowSelector 或将 DataTable 替换为 Hashtable 或您想要的任何数据结构......只是一个示例

【讨论】:

  • 好的,这是更新。我使用 TypeDescriptionProvider 来动态定义属性;但使用它并不完全像常规属性。它消耗了更大的内存;我猜这是因为我们必须将每个实例的属性值保存在字典中,这与常规属性相比优化程度较低。
  • 我编辑了帖子并添加了一个 ICustomTypeDescriptor 示例,该示例可以使用一个数据表作为其他列的后备存储。也许这有助于减少每个对象的开销......
  • 谢谢,这是一个非常有用的代码;我喜欢将属性定位器传递给“MyView”的想法;我想我可以将原始 Result 数组的引用与定位器一起传递,并避免拥有 DataTable 的额外副本。
  • 我已经考虑了一点......这个方案将更适用于某种上下文,以允许在“每个上下文”基础上而不是“每个视图类型”上的附加属性
猜你喜欢
  • 2011-04-23
  • 1970-01-01
  • 2011-11-08
  • 1970-01-01
  • 1970-01-01
  • 2012-10-26
  • 2012-12-21
  • 2011-07-14
相关资源
最近更新 更多