如何让自定义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
相关产品推荐
相关产品推荐

