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

如何基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 02:36:03