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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 05:39:51