Search code examples
c#wpfdata-bindingdatagrid

Update a dynamically created DataGrid in WPF


I'm developing a WPF application and have created two data-grids as shown in the attached image. The second data-grid is being created dynamically using a CreateCoachCompositionDataGrid() method. There is another method named UpdateCoachCompositionDataGrid() method which is used to update the UI of the data-grid (cells' background colour, combo-box enable/disable, etc.) based on the SelectionChanged event of the dataGridTrainHome (first data-grid).

The issue that I have been facing is that the UI of the data-grid is not being updated when I select a row from the dataGridTrainHome. However, I'm sure that the UpdateCoachCompositionDataGrid() method is being called since the labels (Train Number, Train Name, Platform Number) are being updated.

I'm blocked on what the issue is.

Screenshot of the Window

The method that I'm using to create the data-grid:

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());
}

The method I'm using to update the data-grid:

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);
                    }
                }
            }
        }
    }
}

The UpdateCoachCompositionDataGrid() is being called through:

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);
    }
}

I tried using dataGridCoachComposition.UpdateLayout() to forcefully update the UI of the data-grid. It did not work.

I also tried creating a DataView from the DataTable and then updating the UI of the data-grid. But, that did not change anything either.


Solution

  • The problem is that: StackPanel stackPanel = cellContent as StackPanel is always null cause cellContent returns ContentPresenter

    you can easily change the part of the code to look into the visual tree:

    var stackPanel =VisualTreeHelper.GetChild(cellContent, 0) as StackPanel;
    

    Zero means that looking for the first child.

    It is not the very stable solution cause if you change the StackPanel to other layout the system will break.

    I would recommend to use DataTable and later MVVM that easily update state of the grids.

    It works for me.