WPF DataGrid通用化绑定多类型数据源及自定义列头实现咨询
嘿,你的需求完全可以搞定!我分两部分给你详细说清楚怎么实现:
一、实现通用构造函数,告别重复代码
你现在写多个构造函数的方式太繁琐了,咱们可以用泛型协变的思路,只写一个构造函数就能兼容所有你的实体类(包括以后新增的ClassC、ClassD这类)。
首先把原来的两个构造函数删掉,换成下面的代码:
private IEnumerable<object> _dataSource; public ListaDanych(IEnumerable<object> data) { InitializeComponent(); _dataSource = data; // 直接绑定数据源到DataGrid DGDane.ItemsSource = _dataSource; // 这里可以调用后续设置自定义列头的方法 }
调用的时候完全不用改你原来的代码,直接传List
// ClassA的调用代码完全不变 ListaDanych FLista = new ListaDanych(a); FLista.Show(); // ClassB的调用也一样 ListaDanych FLista = new ListaDanych(b); FLista.Show();
为啥能直接传?因为IEnumerable<T>是协变接口,当T是引用类型(你的ClassA、ClassB都是类)时,IEnumerable<ClassA>可以自动转换成IEnumerable<object>,完美适配咱们的通用构造函数。
二、根据类类型自定义DataGrid列头
这里给你两种常用的实现方式,你可以根据自己的场景选:
方式1:用特性标记(最优雅)
给实体类的属性加上DisplayNameAttribute,然后在DataGrid的自动生成列事件里读取这个特性作为列头。
首先修改你的实体类:
using System.ComponentModel; // 别忘了加这个命名空间 public class ClassA { [DisplayName("A类编号")] public decimal ID_A { get; set; } = 0; [DisplayName("通用属性1")] public string MyProperty1 { get; set; } = ""; [DisplayName("通用属性2")] public string MyProperty2 { get; set; } = ""; } public class ClassB { [DisplayName("B类编号")] public decimal ID_B { get; set; } = 0; [DisplayName("通用属性1")] public string MyProperty1 { get; set; } = ""; [DisplayName("通用属性2")] public string MyProperty2 { get; set; } = ""; [DisplayName("B类专属属性")] public string MyProperty3 { get; set; } = ""; }
然后在ListaDanych窗口的XAML里,给DataGrid添加AutoGeneratingColumn事件:
<DataGrid x:Name="DGDane" AutoGeneratingColumn="DGDane_AutoGeneratingColumn" />
最后在后台代码实现这个事件:
private void DGDane_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) { // 读取当前属性的DisplayName特性 var displayNameAttr = e.PropertyDescriptor.Attributes .OfType<DisplayNameAttribute>() .FirstOrDefault(); if (displayNameAttr != null) { // 把列头换成特性里的自定义文本 e.Column.Header = displayNameAttr.DisplayName; } // 可选:如果没有加特性,自动把驼峰命名转成空格分隔的名称(比如MyProperty1变成"Property 1") // else // { // e.Column.Header = System.Text.RegularExpressions.Regex.Replace(e.PropertyName, @"(\B[A-Z])", " $1"); // } }
这种方式的好处是,列头配置和实体类绑定在一起,以后新增属性或者类,直接加特性就行,非常直观。
方式2:用字典映射(适合复杂场景)
如果你的列头需要根据类类型做不同的配置(比如ClassA的MyProperty1叫"A属性1",ClassB的叫"B属性1"),可以维护一个字典来做映射:
首先在ListaDanych窗口里定义映射字典:
// 键是(实体类型+属性名),值是自定义列头文本 private readonly Dictionary<(Type ItemType, string PropertyName), string> _headerMappings = new() { { (typeof(ClassA), nameof(ClassA.ID_A)), "A类编号" }, { (typeof(ClassA), nameof(ClassA.MyProperty1)), "A属性1" }, { (typeof(ClassA), nameof(ClassA.MyProperty2)), "A属性2" }, { (typeof(ClassB), nameof(ClassB.ID_B)), "B类编号" }, { (typeof(ClassB), nameof(ClassB.MyProperty1)), "B属性1" }, { (typeof(ClassB), nameof(ClassB.MyProperty2)), "B属性2" }, { (typeof(ClassB), nameof(ClassB.MyProperty3)), "B专属属性3" }, };
然后还是用AutoGeneratingColumn事件来设置列头:
private void DGDane_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) { var dataGrid = sender as DataGrid; if (dataGrid?.ItemsSource == null) return; // 获取当前数据源的实体类型 var itemType = dataGrid.ItemsSource.GetType().GetGenericArguments().FirstOrDefault(); if (itemType == null) return; // 查找对应的自定义列头 var mappingKey = (itemType, e.PropertyName); if (_headerMappings.TryGetValue(mappingKey, out var customHeader)) { e.Column.Header = customHeader; } }
这种方式灵活性更高,适合需要对不同类型做差异化列头配置的场景。
备注:内容来源于stack exchange,提问作者PawelZ

