如何基于PrcDeployModel为WPF DataGrid动态生成列与单元格?
动态生成WPF DataGrid环境列并通过DataTemplate绑定填充单元格
现有代码与需求
C#模型类
public class PrcDeployModel : BindableBase { public ProcessComputerResult ProcessComputer { get; set; } public List<EnvironmentModel> Environments { get; set; } } public class EnvironmentModel { public string Environment { get; set; } public string Date { get; set; } public string User { get; set; } public DeployModel LatestDeployment { get; set; } }
需求
基于PrcDeployModel列表生成WPF DataGrid,包含1个计算机名列,其余列根据不同环境动态生成。已通过后台代码创建列头,但不想在后台生成单元格样式,希望借助已有的DataTemplate绑定填充环境列的单元格。
现有DataTemplate
<DataTemplate x:Key="TestDataTemplate"> <StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal"> <Image Source="{Binding Path=IconSrc}" Width="60" Height="60" Margin="0 0 5 0"/> </StackPanel> <StackPanel Orientation="Vertical"> <TextBlock Text="{Binding Path=Environment}" FontWeight="Bold"/> <TextBlock Text="{Binding Path=Date}" FontSize="14"/> <TextBlock Text="{Binding Path=User}" FontSize="14"/> </StackPanel> </StackPanel> </DataTemplate>
现有后台列生成代码
public void FillTableColumns(List<PrcDeployModel> deployModels) { DataGridDeployments.Columns.Add(new DataGridTextColumn() { Header = "PRC" }); foreach (var model in deployModels) { foreach (var environment in model.Environments) { var existingColumn = DataGridDeployments.Columns.FirstOrDefault(x => environment.Environment != null && (string)x.Header == environment.Environment); if (existingColumn == null) { DataGridDeployments.Columns.Add(new DataGridTemplateColumn() { Header = environment.Environment, IsReadOnly = true, Width = DataGridLength.Auto }); } } } }
问题
如何通过绑定实现环境列的单元格填充,或解决该动态列问题的最优方案是什么?
解决方案
一、通过DataTemplate绑定填充动态列单元格
1. 创建环境模型转换器
首先实现一个转换器,用于从当前行的PrcDeployModel中,根据环境名称匹配对应的EnvironmentModel:
public class EnvironmentModelConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var prcModel = value as PrcDeployModel; var envName = parameter as string; return prcModel?.Environments.FirstOrDefault(e => e.Environment == envName); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
在XAML资源中声明转换器:
<Window.Resources> <local:EnvironmentModelConverter x:Key="EnvConverter"/> <DataTemplate x:Key="TestDataTemplate"> <!-- 保留原DataTemplate内容不变 --> </DataTemplate> </Window.Resources>
2. 修改后台列生成代码,绑定CellTemplate
调整列生成逻辑,给每个DataGridTemplateColumn设置绑定逻辑,将转换器返回的EnvironmentModel传递给你的TestDataTemplate:
public void FillTableColumns(List<PrcDeployModel> deployModels) { // 完善PRC列的绑定(假设ProcessComputer包含Name属性) DataGridDeployments.Columns.Add(new DataGridTextColumn() { Header = "PRC", Binding = new Binding("ProcessComputer.Name") }); // 收集所有不重复的环境名称,避免重复创建列 var uniqueEnvs = deployModels.SelectMany(m => m.Environments) .Where(e => !string.IsNullOrEmpty(e.Environment)) .Select(e => e.Environment) .Distinct() .ToList(); foreach (var envName in uniqueEnvs) { var templateColumn = new DataGridTemplateColumn() { Header = envName, IsReadOnly = true, Width = DataGridLength.Auto }; // 创建绑定:以当前行的PrcDeployModel为源,通过转换器获取对应环境的模型 var binding = new Binding(".") { Converter = (IValueConverter)Resources["EnvConverter"], ConverterParameter = envName }; // 用FrameworkElementFactory创建ContentControl,绑定内容并应用DataTemplate var factory = new FrameworkElementFactory(typeof(ContentControl)); factory.SetBinding(ContentControl.ContentProperty, binding); factory.SetValue(ContentControl.ContentTemplateProperty, Resources["TestDataTemplate"]); templateColumn.CellTemplate = new DataTemplate() { VisualTree = factory }; DataGridDeployments.Columns.Add(templateColumn); } }
逻辑说明
- 转换器接收当前行的
PrcDeployModel对象,结合列对应的环境名称参数,返回匹配的EnvironmentModel。 - 通过
FrameworkElementFactory动态创建ContentControl,将转换后的EnvironmentModel作为内容,自动应用你定义的TestDataTemplate渲染单元格。
二、最优方案:MVVM友好的动态列实现
如果希望更贴合MVVM模式,减少后台操作UI的代码,可以按以下方式重构:
1. 扩展模型,添加环境索引字典
在PrcDeployModel中新增字典属性,方便通过环境名称快速定位模型:
public class PrcDeployModel : BindableBase { public ProcessComputerResult ProcessComputer { get; set; } public List<EnvironmentModel> Environments { get; set; } // 快速索引用的字典 public Dictionary<string, EnvironmentModel> EnvironmentDict { get { return Environments?.ToDictionary(e => e.Environment, e => e) ?? new Dictionary<string, EnvironmentModel>(); } } }
2. ViewModel中收集环境列列表
在ViewModel中统一管理所有需要显示的环境列名称:
public ObservableCollection<PrcDeployModel> DeployModels { get; set; } public ObservableCollection<string> EnvironmentColumnNames { get; set; } public void LoadData(List<PrcDeployModel> deployModels) { DeployModels = new ObservableCollection<PrcDeployModel>(deployModels); // 收集所有不重复的环境名称 EnvironmentColumnNames = new ObservableCollection<string>( deployModels.SelectMany(m => m.Environments) .Where(e => !string.IsNullOrEmpty(e.Environment)) .Select(e => e.Environment) .Distinct() ); }
3. 使用附加属性实现动态列绑定
原生WPF的DataGrid.Columns不直接支持绑定集合,需要借助附加属性来动态生成列(以下是简化的附加属性实现):
public static class DataGridColumnsBehavior { public static readonly DependencyProperty ColumnsSourceProperty = DependencyProperty.RegisterAttached("ColumnsSource", typeof(IEnumerable<string>), typeof(DataGridColumnsBehavior), new PropertyMetadata(null, OnColumnsSourceChanged)); public static void SetColumnsSource(DependencyObject element, IEnumerable<string> value) { element.SetValue(ColumnsSourceProperty, value); } public static IEnumerable<string> GetColumnsSource(DependencyObject element) { return (IEnumerable<string>)element.GetValue(ColumnsSourceProperty); } private static void OnColumnsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var dataGrid = d as DataGrid; var columns = e.NewValue as IEnumerable<string>; if (dataGrid == null || columns == null) return; dataGrid.Columns.Clear(); // 添加PRC列 dataGrid.Columns.Add(new DataGridTextColumn() { Header = "PRC", Binding = new Binding("ProcessComputer.Name") }); foreach (var envName in columns) { var templateColumn = new DataGridTemplateColumn() { Header = envName, IsReadOnly = true, Width = DataGridLength.Auto }; // 绑定到EnvironmentDict中的对应环境模型 var binding = new Binding($"EnvironmentDict[{envName}]"); var factory = new FrameworkElementFactory(typeof(ContentControl)); factory.SetBinding(ContentControl.ContentProperty, binding); factory.SetValue(ContentControl.ContentTemplateProperty, dataGrid.FindResource("TestDataTemplate")); templateColumn.CellTemplate = new DataTemplate() { VisualTree = factory }; dataGrid.Columns.Add(templateColumn); } } }
在XAML中使用该附加属性:
<DataGrid ItemsSource="{Binding DeployModels}" local:DataGridColumnsBehavior.ColumnsSource="{Binding EnvironmentColumnNames}"/>
总结
- 如果是快速实现需求,**第一种方案(后台生成列+转换器绑定)**最直接,逻辑清晰且无需额外封装。
- 若项目遵循严格MVVM模式,第二种附加属性方案更优雅,将列的生成逻辑与ViewModel分离,便于维护和扩展。
内容的提问来源于stack exchange,提问作者LecArne
相关产品推荐
相关产品推荐

