WPF动态创建DataGrid无法随选中行更新UI问题求助
WPF动态DataGrid更新UI无响应问题解决
问题详情
开发WPF应用时,通过CreateCoachCompositionDataGrid()动态创建第二个DataGrid,期望在第一个DataGrid(dataGridTrainHome)的SelectionChanged事件触发时,调用UpdateCoachCompositionDataGrid()更新第二个DataGrid的UI(包括单元格背景色、ComboBox启用状态等)。目前标签内容能正常更新,但第二个DataGrid的UI无变化,尝试UpdateLayout()和DataTable/DataView方式均无效。
相关代码
创建DataGrid的方法
private void CreateCoachCompositionDataGrid() { // Clear the existing columns in the data grid dataGridCoachComposition.Columns.Clear(); // Set the margin for the data grid dataGridCoachComposition.Margin = new Thickness(16, 468, 230, 18); // Hide the column headers dataGridCoachComposition.HeadersVisibility = DataGridHeadersVisibility.None; // Set the row height dataGridCoachComposition.RowHeight = 50; // Set this to your desired row height // Set the vertical alignment to top to remove empty space at the bottom dataGridCoachComposition.VerticalAlignment = VerticalAlignment.Top; // Create 14 columns for (int i = 1; i <= 14; i++) { // Create a new DataGridTemplateColumn DataGridTemplateColumn column = new DataGridTemplateColumn(); column.Width = new DataGridLength(1, DataGridLengthUnitType.Star); // Set the column width to fill the available space // Create a DataTemplate for the cell template DataTemplate cellTemplate = new DataTemplate(); // Create a StackPanel to hold the TextBlock and ComboBox FrameworkElementFactory stackPanel = new FrameworkElementFactory(typeof(StackPanel)); stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Vertical); // Create a TextBlock for the coach number FrameworkElementFactory textBlock = new FrameworkElementFactory(typeof(TextBlock)); textBlock.SetValue(TextBlock.TextProperty, "C" + i.ToString("00")); textBlock.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Center); // Center the text block horizontally stackPanel.AppendChild(textBlock); // Create a ComboBox for the coach type FrameworkElementFactory comboBox = new FrameworkElementFactory(typeof(ComboBox)); comboBox.SetValue(ComboBox.ItemsSourceProperty, new string[] { "ENG", "HIN" }); comboBox.SetValue(FrameworkElement.MarginProperty, new Thickness(5)); // Set a margin around the ComboBox stackPanel.AppendChild(comboBox); // Set the cell template's visual tree to the StackPanel cellTemplate.VisualTree = stackPanel; // Set the column's cell template column.CellTemplate = cellTemplate; // Add the column to the data grid dataGridCoachComposition.Columns.Add(column); } // Add two empty rows to the data grid dataGridCoachComposition.Items.Add(new object()); dataGridCoachComposition.Items.Add(new object()); }
更新DataGrid的方法
private void UpdateCoachCompositionDataGrid(DataRow selectedRow) { // Get the TrainNumber, TrainName, and PlatformNumber from the selected row string trainNumber = selectedRow["TrainNumber"].ToString(); string trainName = selectedRow["TrainName"].ToString(); string platformNumber = selectedRow["PlatformNumber"].ToString(); // Display train information in the labels lblTrainNo.Content = "Train Number: " + trainNumber; lblTrainName.Content = "Train Name: " + trainName; lblPlatformNo.Content = "Platform Number: " + platformNumber; // Get the coach numbers from the database List<string> coachNumbers = GetCoachNumbersFromDatabase(trainNumber); // Iterate over each cell in the data grid for (int i = 0; i < dataGridCoachComposition.Columns.Count; i++) { for (int j = 0; j < dataGridCoachComposition.Items.Count; j++) { // Get the cell content var cellContent = dataGridCoachComposition.Columns[i].GetCellContent(dataGridCoachComposition.Items[j]); // Get the StackPanel in the cell StackPanel stackPanel = cellContent as StackPanel; if (stackPanel != null) { // Get the ComboBox in the StackPanel ComboBox comboBox = stackPanel.Children.OfType<ComboBox>().FirstOrDefault(); if (comboBox != null) { // Check if the coach number is present if (coachNumbers.Contains("C" + (i + 1 + j * 14).ToString("00"))) { // Auto-populate the ComboBox with 'ENG' comboBox.SelectedIndex = 0; // Enable the ComboBox comboBox.IsEnabled = true; // Set the cell background to white ((DataGridCell)cellContent.Parent).Background = new SolidColorBrush(Colors.White); } else { // Clear the ComboBox selection comboBox.SelectedIndex = -1; // Disable the ComboBox comboBox.IsEnabled = false; // Set the cell background to grey ((DataGridCell)cellContent.Parent).Background = new SolidColorBrush(Colors.Gray); } } } } } }
触发更新的事件方法
private void dataGridTrainHome_SelectionChanged(object sender, SelectionChangedEventArgs e) { // Get the currently selected items in the DataGrid var selectedRows = dataGridTrainHome.SelectedItems; // If any rows are selected if (selectedRows != null && selectedRows.Count > 0) { // Get the last selected row DataRowView lastSelectedRow = (DataRowView)selectedRows[selectedRows.Count - 1]; // Use the Dispatcher to delay the call to UpdateCoachCompositionDataGrid Dispatcher.BeginInvoke(new Action(() => { // Update the coach composition DataGrid UpdateCoachCompositionDataGrid(lastSelectedRow.Row); }), DispatcherPriority.Render); } }
问题根源
直接遍历可视化树修改控件属性的方式不符合WPF数据驱动的设计理念,动态创建的模板元素可能因DataGrid虚拟化、模板复用等原因导致修改无法同步到UI;此外,直接修改控件属性不会自动触发UI更新机制。
修复方案
改用数据绑定模式,通过实体类封装车厢数据,让UI响应数据源变化:
1. 创建车厢实体类(实现INotifyPropertyChanged)
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows.Media; public class CoachItem : INotifyPropertyChanged { private string _coachNumber; private string _selectedType; private bool _isEnabled; private Brush _background; public string CoachNumber { get => _coachNumber; set { _coachNumber = value; OnPropertyChanged(); } } public string SelectedType { get => _selectedType; set { _selectedType = value; OnPropertyChanged(); } } public bool IsEnabled { get => _isEnabled; set { _isEnabled = value; OnPropertyChanged(); } } public Brush Background { get => _background; set { _background = value; OnPropertyChanged(); } } public string[] TypeOptions { get; } = new[] { "ENG", "HIN" }; public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
2. 重构CreateCoachCompositionDataGrid方法
绑定数据源到实体集合,模板通过绑定关联实体属性:
private void CreateCoachCompositionDataGrid() { dataGridCoachComposition.Columns.Clear(); dataGridCoachComposition.Margin = new Thickness(16, 468, 230, 18); dataGridCoachComposition.HeadersVisibility = DataGridHeadersVisibility.None; dataGridCoachComposition.RowHeight = 50; dataGridCoachComposition.VerticalAlignment = VerticalAlignment.Top; dataGridCoachComposition.AutoGenerateColumns = false; // 构建数据源:2行,每行14个车厢 var coachRows = new List<List<CoachItem>>(); for (int rowIndex = 0; rowIndex < 2; rowIndex++) { var rowItems = new List<CoachItem>(); for (int colIndex = 1; colIndex <= 14; colIndex++) { int coachSeq = rowIndex * 14 + colIndex; rowItems.Add(new CoachItem { CoachNumber = $"C{coachSeq:00}", IsEnabled = false, Background = Brushes.Gray }); } coachRows.Add(rowItems); } dataGridCoachComposition.ItemsSource = coachRows; // 创建14列,绑定对应位置的实体属性 for (int col = 0; col < 14; col++) { DataGridTemplateColumn column = new DataGridTemplateColumn(); column.Width = new DataGridLength(1, DataGridLengthUnitType.Star); // 构建单元格模板 DataTemplate cellTemplate = new DataTemplate(); FrameworkElementFactory stackPanel = new FrameworkElementFactory(typeof(StackPanel)); stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Vertical); // 绑定车厢编号文本 FrameworkElementFactory textBlock = new FrameworkElementFactory(typeof(TextBlock)); textBlock.SetBinding(TextBlock.TextProperty, new Binding($"[{col}].CoachNumber")); textBlock.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Center); stackPanel.AppendChild(textBlock); // 绑定ComboBox属性 FrameworkElementFactory comboBox = new FrameworkElementFactory(typeof(ComboBox)); comboBox.SetBinding(ComboBox.ItemsSourceProperty, new Binding($"[{col}].TypeOptions")); comboBox.SetBinding(ComboBox.SelectedItemProperty, new Binding($"[{col}].SelectedType") { Mode = BindingMode.TwoWay }); comboBox.SetBinding(ComboBox.IsEnabledProperty, new Binding($"[{col}].IsEnabled")); comboBox.SetValue(FrameworkElement.MarginProperty, new Thickness(5)); stackPanel.AppendChild(comboBox); cellTemplate.VisualTree = stackPanel; column.CellTemplate = cellTemplate; // 绑定单元格背景色 column.CellStyle = new Style(typeof(DataGridCell)); column.CellStyle.Setters.Add(new Setter(DataGridCell.BackgroundProperty, new Binding($"[{col}].Background"))); dataGridCoachComposition.Columns.Add(column); } }
3. 重构UpdateCoachCompositionDataGrid方法
直接修改数据源属性,通过INotifyPropertyChanged自动触发UI更新:
private void UpdateCoachCompositionDataGrid(DataRow selectedRow) { string trainNumber = selectedRow["TrainNumber"].ToString(); string trainName = selectedRow["TrainName"].ToString(); string platformNumber = selectedRow["PlatformNumber"].ToString(); lblTrainNo.Content = $"Train Number: {trainNumber}"; lblTrainName.Content = $"Train Name: {trainName}"; lblPlatformNo.Content = $"Platform Number: {platformNumber}"; List<string> coachNumbers = GetCoachNumbersFromDatabase(trainNumber); var coachRows = dataGridCoachComposition.ItemsSource as List<List<CoachItem>>; if (coachRows == null) return; foreach (var row in coachRows) { foreach (var coach in row) { bool exists = coachNumbers.Contains(coach.CoachNumber); coach.IsEnabled = exists; coach.Background = exists ? Brushes.White : Brushes.Gray; coach.SelectedType = exists ? "ENG" : null; } } }
4. 优化SelectionChanged事件(可选)
移除Dispatcher延迟,数据驱动模式下无需等待渲染:
private void dataGridTrainHome_SelectionChanged(object sender, SelectionChangedEventArgs e) { var selectedRows = dataGridTrainHome.SelectedItems; if (selectedRows != null && selectedRows.Count > 0) { DataRowView lastSelectedRow = (DataRowView)selectedRows[selectedRows.Count - 1]; UpdateCoachCompositionDataGrid(lastSelectedRow.Row); } }
核心说明
- 采用数据绑定+INotifyPropertyChanged的模式,让UI自动响应数据源变化,符合WPF设计思想。
- 避免直接操作可视化树,从根源上解决模板复用、虚拟化导致的UI更新失效问题。
- 单元格背景通过CellStyle绑定实现,确保样式变化能同步到UI。
内容的提问来源于stack exchange,提问作者SEIL
相关产品推荐
相关产品推荐

