You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让自定义DTable对象适配DataGridView/DataGrid实现便捷绑定?

适配DTable到DataGridView的无复制数据方案(基于IListSource)

你的判断没错——IListSource是让这类自定义集合适配WinForms数据绑定的最优方案,而且完全可以做到不复制原有数据,只做一层包装映射。下面是一步步的实现指引:

1. 让DTable实现IListSource接口

这个接口只有两个成员,我们只需要让GetList()返回一个包装类,ContainsListCollection直接返回false即可:

public class DTable : IEnumerable, IListSource
{
    // 保留你的现有成员:DKeys, DRows, DColumns, DCell...

    public bool ContainsListCollection => false;

    public IList GetList()
    {
        return new DTableListWrapper(this);
    }

    // 保留你原有的IEnumerable实现代码
}

2. 实现包装类DTableListWrapper

这个类是核心,它既实现IList提供索引访问能力,又实现ITypedList让DataGridView自动识别列信息,全程直接复用原DTable的数据,不做复制:

public class DTableListWrapper : IList, ITypedList
{
    private readonly DTable _sourceTable;

    public DTableListWrapper(DTable source)
    {
        _sourceTable = source;
    }

    // --- IList核心实现(直接映射原数据)---
    public int Count => _sourceTable.DKeys.Count;
    public bool IsReadOnly => true; // 不需要编辑就设为true,需要编辑则修改对应逻辑
    public bool IsFixedSize => true;

    public object this[int index]
    {
        get
        {
            // 按DKeys的顺序取行,直接返回原DRows中的字典(或后续的动态包装类)
            var rowKey = _sourceTable.DKeys[index];
            return new DRowDynamicWrapper(_sourceTable.DRows[rowKey]);
        }
        set => throw new NotSupportedException("集合为只读");
    }

    // 不需要编辑的话,以下IList方法直接抛出异常
    public int Add(object value) => throw new NotSupportedException();
    public void Clear() => throw new NotSupportedException();
    public bool Contains(object value) => throw new NotSupportedException();
    public int IndexOf(object value) => throw new NotSupportedException();
    public void Insert(int index, object value) => throw new NotSupportedException();
    public void Remove(object value) => throw new NotSupportedException();
    public void RemoveAt(int index) => throw new NotSupportedException();

    // ICollection接口的默认实现
    public void CopyTo(Array array, int index) => throw new NotSupportedException();
    public object SyncRoot => this;
    public bool IsSynchronized => false;

    // 枚举器实现,直接遍历DKeys映射的行
    public IEnumerator GetEnumerator()
    {
        foreach (var key in _sourceTable.DKeys)
        {
            yield return new DRowDynamicWrapper(_sourceTable.DRows[key]);
        }
    }

    // --- ITypedList实现(让DataGridView识别列)---
    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
    {
        var props = new List<PropertyDescriptor>();
        // 从DColumns中读取列信息,生成对应的PropertyDescriptor
        foreach (var col in _sourceTable.DColumns)
        {
            props.Add(new DColumnPropertyDescriptor(col.ColumnName));
        }
        return new PropertyDescriptorCollection(props.ToArray());
    }

    public string GetListName(PropertyDescriptor[] listAccessors) => "DTableRows";
}

3. 实现DColumnPropertyDescriptor

这个类负责把DataGridView的列访问映射到原DRow字典中的单元格值:

public class DColumnPropertyDescriptor : PropertyDescriptor
{
    public DColumnPropertyDescriptor(string columnName) : base(columnName, null)
    {
    }

    public override object GetValue(object component)
    {
        // 从动态行包装类中获取对应列的值
        if (component is DRowDynamicWrapper row)
        {
            return row.GetValue(Name);
        }
        return null;
    }

    // 不需要编辑的话,以下方法直接抛出异常或返回默认值
    public override void SetValue(object component, object value) => throw new NotSupportedException();
    public override bool CanResetValue(object component) => false;
    public override void ResetValue(object component) => throw new NotSupportedException();
    public override bool ShouldSerializeValue(object component) => false;

    public override Type ComponentType => typeof(DRowDynamicWrapper);
    public override bool IsReadOnly => true;
    public override Type PropertyType => typeof(object); // 可根据DColumn的格式返回具体类型,比如int/DateTime等
}

4. 可选:DRowDynamicWrapper(简化属性访问)

用DynamicObject包装行字典,让属性访问更自然,也方便后续扩展:

public class DRowDynamicWrapper : DynamicObject
{
    private readonly Dictionary<string, object> _cellDict;

    public DRowDynamicWrapper(Dictionary<string, object> cells)
    {
        _cellDict = cells;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return _cellDict.TryGetValue(binder.Name, out result);
    }

    // 暴露给PropertyDescriptor的方法
    public object GetValue(string columnName)
    {
        _cellDict.TryGetValue(columnName, out var value);
        return value;
    }

    // 需要编辑的话,实现TrySetMember方法更新_cellDict
}

5. 绑定到DataGridView

现在直接绑定即可,DataGridView会自动识别列并加载数据:

// 假设你已经实例化了DTable
var myDTable = new DTable();
// 通过BindingSource绑定(推荐,支持排序/筛选等扩展)
dataGridView1.DataSource = new BindingSource(myDTable, null);
// 或者直接绑定
// dataGridView1.DataSource = myDTable;

关键要点

  • 无数据复制:所有操作都是直接访问原DTable的DKeys、DRows和DColumns,没有创建任何数据副本。
  • 列自动生成:通过ITypedList和PropertyDescriptor,DataGridView会自动从DColumns读取列名、生成对应列。
  • 编辑支持:如果需要修改单元格数据,只需将IsReadOnly改为false,并实现SetValue方法更新原DRows中的字典值。
  • 行顺序控制:列表的显示顺序完全由DKeys的顺序决定,调整DKeys即可改变行的显示顺序,无需修改DRows。

内容的提问来源于stack exchange,提问作者Maury Markowitz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 02:20:39