DataGrid rows not able to display different values in WPF

64 Views Asked by At

I want to create a DataGrid in my WPF Application which has 2 rows, and 14 columns. I have defined a method which creates this data-grid during runtime.

The issue that I'm facing is that both the rows display the same text.

Screenshot

As can be seen in the attached image, both the rows are displaying coach numbers from C01 to C14. However, I want the first row to remain as it is, and the second row to display coach numbers from C15 to C28.

The method that I'm currently using is:

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

I tried something like this:

// Adjust the labels for each cell
    for (int i = 0; i < 14; i++)
    {
        ((TextBlock)((StackPanel)dataGridCoachComposition.Columns[i].GetCellContent(dataGridCoachComposition.Items[0])).Children[0]).Text = "C" + (i + 1).ToString("00");
        ((TextBlock)((StackPanel)dataGridCoachComposition.Columns[i].GetCellContent(dataGridCoachComposition.Items[1])).Children[0]).Text = "C" + (i + 15).ToString("00");
    }

However, this was throwing a NullReferenceException. To fix this, I tried using the Dispatcher class to schedule the execution of the code.

The error still persists.

Update: Tried a different approach by creating a DataTable.

My XAML code:

<DataTemplate x:Key="ComboBoxTemplate">
    <StackPanel>
        <TextBlock Text="{Binding}" />
        <ComboBox Margin="5">
            <ComboBoxItem>ENG</ComboBoxItem>
            <ComboBoxItem>HIN</ComboBoxItem>
        </ComboBox>
    </StackPanel>
</DataTemplate>

<DataGrid x:Name="dataGridCoachComposition" AutoGenerateColumns="True" CanUserAddRows="False" HeadersVisibility="None" Margin="16,468,14,0" VerticalAlignment="Top" RowHeight="50" ColumnWidth="*"/>

My SetupDataGrid() method:

private void SetupDataGrid()
{
    DataTable dataTable = new DataTable();

    // Define 14 columns
    for (int i = 1; i <= 14; i++)
    {
        dataTable.Columns.Add($"C{i:D2}");
    }

    // Add two rows of data
    string[] firstRow = new string[14];
    string[] secondRow = new string[14];

    for (int i = 0; i < 14; i++)
    {
        firstRow[i] = $"C{i + 1:D2}";
        secondRow[i] = $"C{i + 15:D2}";
    }

    dataTable.Rows.Add(firstRow);
    dataTable.Rows.Add(secondRow);

    // Bind the DataTable to the DataGrid
    dataGridCoachComposition.ItemsSource = dataTable.DefaultView;

    // Clear auto generated columns
    dataGridCoachComposition.AutoGenerateColumns = false;
    dataGridCoachComposition.Columns.Clear();

    // Add columns with ComboBox
    for (int i = 0; i < 14; i++)
    {
        DataGridTemplateColumn column = new DataGridTemplateColumn();
        column.Header = $"C{i + 1:D2}";
        column.CellTemplate = this.FindResource("ComboBoxTemplate") as DataTemplate;
        dataGridCoachComposition.Columns.Add(column);
    }
}

This is displaying the coach numbers and combo-boxes. However, instead of displaying just the coach numbers, entire DataRow object is being displayed.

Something like this:
Error SS

Any solutions?

1

There are 1 best solutions below

0
BionicCode On

This answer assumes that you have correctly created the DataTable that the DataGrid is going to present.

The following example shows how to achieve a fully dynamic DataGrid using a shared DataGridTemplateColumn definition. To add more columns, simply create a new DataTable as data source.
A higher degree of generalization is possible but requires a more complex logic.

The key is to enable auto column generation (enabled by default) and intercept the generated columns and replace them (if required). Then we bind the template internals to the parent DataGridCell (the row item container) and finally extract the current cell's value with the help of a IValueConverter. The DataGridCell is the object of choice because it provides full access to the column's context: the cell container (DataGridCell), the data context (DataRowView), the column (DataGridTemplateColumn) and via the column we get the column's index (DataGridColumn.DisplayIndex).

MainWindow.xaml

<DataGrid ItemsSource="{Binding TableData}"
          AutoGenerateColumns="True"
          AutoGeneratingColumn="DataGrid_AutoGeneratingColumn">  
  <DataGrid.Resources>
    <local:DataRowViewToCellValueConverter x:Key="DataRowViewToCellValueConverter" />
    <DataTemplate x:Key="DefaultCellTemplate">
      <StackPanel>
        <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=DataGridCell}, Converter={StaticResource DataRowViewToCellValueConverter}}" />

        <!-- 
             To bind ComboBox.ItemsSource to an ObservableCollection you would 
             have to use a value converter in a similar fashion to the one used to 
             get the current cell value. 
             But instead of using the column index to access the 
             DataRowView, you would use it to retrieve the appropriate
             source collection from a static Dictionary, 
             where the key is the column index and the value the ObservableCollection.
        -->
        <ComboBox>
          <ComboBoxItem>ENG</ComboBoxItem>
          <ComboBoxItem>HIN</ComboBoxItem>
        </ComboBox>
      </StackPanel>
    </DataTemplate>
  </DataGrid.Resources>
</DataGrid>

MainWindow.xaml.cs

// Handling the AutoGeneratingColumn event allows to replace (and rearrange) 
// the originally generated column.
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
  var dataGrid = (DataGrid)sender;
  var cellTemplate = (DataTemplate)dataGrid.Resources["DefaultCellTemplate"];
  var templateColumn = new DataGridTemplateColumn()
  {
    Header = e.Column.Header,
    CellTemplate = cellTemplate,
        
  };
      
  e.Column = templateColumn;
}

DataGridCellToCellValueConverter.cs

public class DataGridCellToCellValueConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    var dataGridCell = (DataGridCell)value;

    // Current row could be the placeholder row
    if (dataGridCell.DataContext is not DataRowView dataRowView)
    {
      return Binding.DoNothing;
    }

    int columnIndex = dataGridCell.Column.DisplayIndex;
    return dataRowView[columnIndex];
  }

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    => throw new NotSupportedException();
}