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

WPF中如何通过ViewModel绑定为DataGrid动态添加列

实现DataGrid动态列绑定方案

核心思路

通过附加属性实现DataGrid列集合与ViewModel的绑定,结合带索引器的数据模型存储动态误差值,全程在ViewModel中完成列和数据的动态更新,无需直接操作DataGrid实例。


1. 定义动态列模型

用于描述动态生成的列信息:

public class DataGridDynamicColumn : BindableBase
{
    private string _header;
    public string Header
    {
        get => _header;
        set => SetProperty(ref _header, value);
    }

    private string _bindingPath;
    public string BindingPath
    {
        get => _bindingPath;
        set => SetProperty(ref _bindingPath, value);
    }
}

2. 实现DataGrid列绑定附加属性

解决WPF原生DataGrid.Columns无法直接绑定的问题:

using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

public static class DataGridColumnsBehavior
{
    public static readonly DependencyProperty DynamicColumnsProperty =
        DependencyProperty.RegisterAttached(
            "DynamicColumns",
            typeof(ObservableCollection<DataGridDynamicColumn>),
            typeof(DataGridColumnsBehavior),
            new PropertyMetadata(null, OnDynamicColumnsChanged));

    public static void SetDynamicColumns(DependencyObject element, ObservableCollection<DataGridDynamicColumn> value)
    {
        element.SetValue(DynamicColumnsProperty, value);
    }

    public static ObservableCollection<DataGridDynamicColumn> GetDynamicColumns(DependencyObject element)
    {
        return (ObservableCollection<DataGridDynamicColumn>)element.GetValue(DynamicColumnsProperty);
    }

    private static void OnDynamicColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is not DataGrid dataGrid) return;

        if (e.OldValue is ObservableCollection<DataGridDynamicColumn> oldColumns)
        {
            oldColumns.CollectionChanged -= OnColumnsCollectionChanged;
            foreach (var col in oldColumns.Select(c => dataGrid.Columns.FirstOrDefault(x => x.Tag == c.BindingPath)).Where(col => col != null))
            {
                dataGrid.Columns.Remove(col);
            }
        }

        if (e.NewValue is ObservableCollection<DataGridDynamicColumn> newColumns)
        {
            newColumns.CollectionChanged += OnColumnsCollectionChanged;
            foreach (var column in newColumns)
            {
                AddDynamicColumn(dataGrid, column);
            }
        }
    }

    private static void OnColumnsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (sender is not ObservableCollection<DataGridDynamicColumn> columns) return;
        var dataGrid = FindVisualParent<DataGrid>(columns.FirstOrDefault());
        if (dataGrid == null) return;

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (DataGridDynamicColumn column in e.NewItems)
                    AddDynamicColumn(dataGrid, column);
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (DataGridDynamicColumn column in e.OldItems)
                {
                    var colToRemove = dataGrid.Columns.FirstOrDefault(x => x.Tag == column.BindingPath);
                    if (colToRemove != null) dataGrid.Columns.Remove(colToRemove);
                }
                break;
        }
    }

    private static void AddDynamicColumn(DataGrid dataGrid, DataGridDynamicColumn column)
    {
        var templateColumn = new DataGridTemplateColumn
        {
            Header = column.Header,
            Tag = column.BindingPath,
            Width = new DataGridLength(1, DataGridLengthUnitType.Star)
        };

        var cellTemplate = new DataTemplate();
        var labelFactory = new FrameworkElementFactory(typeof(Label));
        labelFactory.SetBinding(Label.ContentProperty, new Binding($"[{column.BindingPath}]")
        {
            UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
            StringFormat = "F2"
        });
        labelFactory.SetValue(VerticalContentAlignmentProperty, VerticalAlignment.Center);
        labelFactory.SetValue(HorizontalContentAlignmentProperty, HorizontalAlignment.Center);
        cellTemplate.VisualTree = labelFactory;

        templateColumn.CellTemplate = cellTemplate;
        dataGrid.Columns.Add(templateColumn);
    }

    private static T FindVisualParent<T>(DependencyObject child) where T : DependencyObject
    {
        while (child != null)
        {
            if (child is T parent) return parent;
            child = VisualTreeHelper.GetParent(child);
        }
        return null;
    }
}

3. 定义数据项模型

包含固定列字段+动态误差值索引器:

public class AnalyzerDataItem : BindableBase
{
    public string Address { get; set; }
    public string Type { get; set; }

    private readonly Dictionary<string, double> _errorValues = new();
    public double this[string temperatureKey]
    {
        get => _errorValues.TryGetValue(temperatureKey, out var value) ? value : 0;
        set
        {
            _errorValues[temperatureKey] = value;
            OnPropertyChanged($"[{temperatureKey}]");
        }
    }
}

4. 修改ViewModel

添加动态列集合、数据集合,完善文件读取逻辑:

using Microsoft.Win32;
using System.IO;
using System.Windows.Input;

public class AnalyzerVM : BindableBase
{
    public DelegateCommand OpenCommand { get; set; }

    private ObservableCollection<AnalyzerDataItem> _dataGridAnalyzerData;
    public ObservableCollection<AnalyzerDataItem> DataGridAnalyzerData
    {
        get => _dataGridAnalyzerData;
        set => SetProperty(ref _dataGridAnalyzerData, value);
    }

    private ObservableCollection<DataGridDynamicColumn> _dynamicColumns;
    public ObservableCollection<DataGridDynamicColumn> DynamicColumns
    {
        get => _dynamicColumns;
        set => SetProperty(ref _dynamicColumns, value);
    }

    private readonly Action<Action> _runOnUI;

    public AnalyzerVM(Action<Action> runOnUI)
    {
        _runOnUI = runOnUI;
        OpenCommand = new DelegateCommand(OpenHandler);
        DataGridAnalyzerData = new ObservableCollection<AnalyzerDataItem>();
        DynamicColumns = new ObservableCollection<DataGridDynamicColumn>();
    }

    private void OpenHandler()
    {
        List<string> fileNames = SelectFiles();
        if (!fileNames.Any()) return;

        _runOnUI(() =>
        {
            DynamicColumns.Clear();
            DataGridAnalyzerData.Clear();
        });

        GetDataOpenFile(fileNames);
    }

    private List<string> SelectFiles()
    {
        var dialog = new OpenFileDialog
        {
            Filter = "文本文件|*.txt",
            Multiselect = true
        };
        return dialog.ShowDialog() == true ? dialog.FileNames.ToList() : new List<string>();
    }

    private void GetDataOpenFile(List<string> fileNames)
    {
        var temperatureData = new Dictionary<string, List<double>>();

        foreach (var filePath in fileNames)
        {
            var errorValues = ReadErrorPointsFromFile(filePath);
            if (errorValues.Count != 10) continue;

            var tempKey = Path.GetFileNameWithoutExtension(filePath);
            temperatureData[tempKey] = errorValues;

            _runOnUI(() =>
            {
                DynamicColumns.Add(new DataGridDynamicColumn
                {
                    Header = tempKey,
                    BindingPath = tempKey
                });
            });
        }

        _runOnUI(() =>
        {
            for (int i = 0; i < 10; i++)
            {
                var item = new AnalyzerDataItem
                {
                    Address = $"Point {i+1}",
                    Type = "Error"
                };

                foreach (var kvp in temperatureData)
                {
                    item[kvp.Key] = kvp.Value[i];
                }

                DataGridAnalyzerData.Add(item);
            }
        });
    }

    private List<double> ReadErrorPointsFromFile(string filePath)
    {
        var errors = new List<double>();
        if (File.Exists(filePath))
        {
            var lines = File.ReadAllLines(filePath);
            foreach (var line in lines.Take(10))
            {
                if (double.TryParse(line.Trim(), out var error))
                {
                    errors.Add(error);
                }
            }
        }
        return errors;
    }
}

5. 修改XAML

添加附加属性命名空间并绑定动态列:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:behaviors="clr-namespace:YourNamespace.Behaviors"
        xmlns:local="clr-namespace:YourNamespace"
        Title="Analyzer" Height="450" Width="800">
    <Grid>
        <DataGrid Name="DataGridAnalyzer" 
                  ItemsSource="{Binding DataGridAnalyzerData, UpdateSourceTrigger=PropertyChanged}"
                  AutoGenerateColumns="False" 
                  CanUserAddRows="False" 
                  IsReadOnly="False" 
                  GridLinesVisibility="All" 
                  Width="Auto" 
                  Height="200" 
                  Margin="10"
                  behaviors:DataGridColumnsBehavior.DynamicColumns="{Binding DynamicColumns}">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Address" Width="*" IsReadOnly="True" >
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Label Content="{Binding Address, UpdateSourceTrigger=PropertyChanged}" 
                                   VerticalContentAlignment="Center" 
                                   HorizontalContentAlignment="Center" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="Type" Width="*" IsReadOnly="True" >
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Label Content="{Binding Type, UpdateSourceTrigger=PropertyChanged}" 
                                   VerticalContentAlignment="Center" 
                                   HorizontalContentAlignment="Center" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
        <Button Command="{Binding OpenCommand}" Content="Open" Margin="10" VerticalAlignment="Bottom"/>
    </Grid>
</Window>

内容的提问来源于stack exchange,提问作者gregor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 11:20:39