C#如何在DataGridView中显示含嵌套对象的对象列表?附排序疑问
问题描述
我定义了两个类:
Person类
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public Pet MyPet { get; set; } }
和Pet类
public class Pet { public string Name { get; set; } public string Animal { get; set; } }
创建实例并绑定到DataGridView:
Pet myDog = new Pet("Fluffy", "Dog"); Person A = new Person("John", "Smith", myDog); Pet myCat = new Pet("Furball", "Cat"); Person B = new Person("Jane", "Doe", myCat); List<Person> people = new List<Person> { A, B }; dataGridView1.DataSource = people;
遇到的问题:
- DataGridView自动生成的列只有FirstName、LastName、MyPet,没有Pet的Name和Animal属性
- MyPet列显示
TestDemo.Pet而非宠物具体信息 - 手动添加列设置DataPropertyName(比如
MyPet.Name),宠物相关列无法正常显示
另外想知道如何让DataGridView的列支持排序。
一、宠物属性列不显示的原因及解决方法
原因
DataGridView默认只会解析数据源对象的直接值类型/字符串类型属性,对于MyPet这种引用类型属性,它无法自动递归解析内部属性,只会调用该对象的默认ToString()方法,输出类的完全限定名(也就是TestDemo.Pet)。同时DataGridView不支持直接通过MyPet.Name这种嵌套路径绑定属性,所以手动设置也无效。
解决方法
方案1:在Person类中添加包装属性
直接在Person类里封装Pet的属性,让DataGridView能直接识别:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public Pet MyPet { get; set; } // 包装Pet的属性,空值处理避免报错 public string PetName => MyPet?.Name ?? string.Empty; public string PetAnimal => MyPet?.Animal ?? string.Empty; }
这样自动生成列时会包含PetName和PetAnimal,显示正常;如果是手动添加列,设置DataPropertyName="PetName"即可生效。
方案2:自定义DataGridView列解析嵌套属性
如果不想修改Person类,可以自定义列来解析嵌套属性路径:
public class NestedPropertyDataGridViewTextBoxColumn : DataGridViewTextBoxColumn { public string NestedPropertyPath { get; set; } protected override object GetCellValue(int rowIndex, int columnIndex) { var rowData = DataGridView.Rows[rowIndex].DataBoundItem; if (rowData == null || string.IsNullOrEmpty(NestedPropertyPath)) return base.GetCellValue(rowIndex, columnIndex); // 拆分属性路径,比如"MyPet.Name"拆成["MyPet", "Name"] var propertyParts = NestedPropertyPath.Split('.'); object currentValue = rowData; foreach (var part in propertyParts) { var prop = currentValue.GetType().GetProperty(part); if (prop == null) { currentValue = null; break; } currentValue = prop.GetValue(currentValue); if (currentValue == null) break; } return currentValue ?? DBNull.Value; } }
使用时关闭自动列生成,手动添加自定义列:
dataGridView1.AutoGenerateColumns = false; // 添加宠物名字列 var petNameCol = new NestedPropertyDataGridViewTextBoxColumn(); petNameCol.HeaderText = "宠物名字"; petNameCol.NestedPropertyPath = "MyPet.Name"; dataGridView1.Columns.Add(petNameCol); // 添加宠物类型列 var petAnimalCol = new NestedPropertyDataGridViewTextBoxColumn(); petAnimalCol.HeaderText = "宠物类型"; petAnimalCol.NestedPropertyPath = "MyPet.Animal"; dataGridView1.Columns.Add(petAnimalCol); // 再添加Person的直接属性列 var firstNameCol = new DataGridViewTextBoxColumn(); firstNameCol.HeaderText = "名字"; firstNameCol.DataPropertyName = "FirstName"; dataGridView1.Columns.Add(firstNameCol);
二、让DataGridView列支持排序
默认List<T>绑定后不支持排序,因为它没实现IBindingList接口,以下两种方案可以解决:
方案1:使用可排序的BindingList
自定义继承BindingList<T>的排序类:
public class SortableBindingList<T> : BindingList<T> { private bool _isSorted; private ListSortDirection _sortDir; private PropertyDescriptor _sortProp; protected override bool SupportsSortingCore => true; protected override bool IsSortedCore => _isSorted; protected override ListSortDirection SortDirectionCore => _sortDir; protected override PropertyDescriptor SortPropertyCore => _sortProp; protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) { var list = Items as List<T>; if (list == null || prop == null) return; // 用LINQ实现排序 list.Sort((a, b) => { var valA = prop.GetValue(a); var valB = prop.GetValue(b); return direction == ListSortDirection.Ascending ? Comparer.Default.Compare(valA, valB) : Comparer.Default.Compare(valB, valA); }); _sortProp = prop; _sortDir = direction; _isSorted = true; OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); } protected override void RemoveSortCore() { _isSorted = false; _sortProp = null; OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); } }
使用时替换原List<Person>:
var sortablePeople = new SortableBindingList<Person> { A, B }; dataGridView1.DataSource = sortablePeople;
点击列头即可触发排序,包括方案1中添加的PetName这类包装属性也能正常排序。
方案2:手动处理列头点击事件
如果不想用BindingList,可以直接监听列头点击事件自己实现排序:
private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) { var column = dataGridView1.Columns[e.ColumnIndex]; if (string.IsNullOrEmpty(column.DataPropertyName)) return; // 切换排序方向 var newSortDir = column.HeaderCell.SortGlyphDirection == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending; var people = dataGridView1.DataSource as List<Person>; if (people == null) return; // 根据列的DataPropertyName执行排序 switch (column.DataPropertyName) { case "FirstName": people = new List<Person>(newSortDir == SortOrder.Ascending ? people.OrderBy(p => p.FirstName) : people.OrderByDescending(p => p.FirstName)); break; case "PetName": people = new List<Person>(newSortDir == SortOrder.Ascending ? people.OrderBy(p => p.PetName) : people.OrderByDescending(p => p.PetName)); break; // 其他需要排序的列同理添加 } // 重新绑定数据源 dataGridView1.DataSource = null; dataGridView1.DataSource = people; // 更新排序图标 foreach (DataGridViewColumn col in dataGridView1.Columns) { col.HeaderCell.SortGlyphDirection = SortOrder.None; } column.HeaderCell.SortGlyphDirection = newSortDir; }
这种方式需要为每个要排序的列编写逻辑,适合简单场景。
内容的提问来源于stack exchange,提问作者kenLeeDep
相关产品推荐
相关产品推荐

