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
相关产品推荐
相关产品推荐

