I am writing a program that requires information provided in a grid-like format, with user specifying both the number of columns (that I call tracks for this purpose) and rows (that I call segments).
For that I employed one DataGrid that will have the number of columns (tracks) and 1 row, and each of these cells should hold the number of segments for each track. Once this information is provided, it will populate a second DataGrid with the number of columns and rows defined for each of them. This means the columns may have different number of rows.
See these 2 pictures from Excel to illustrate what I am after, where I first define the number of columns and add 1 row so the number of rows of the second grid can be specified:
And then I expect a second grid with the added rows, ready for further user input which is what I will use for further processing. I added borders just so you see that these are the usable cells for each track (CanUserAddRows is False in the XAML, so the user needs to specify the exact numbers in the 1st datagrid as they will guide my loops and I can't have empty cells).
Please check my minimum working sample, with XAML and C# codes below:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="204*"/>
<ColumnDefinition Width="69*"/>
<ColumnDefinition Width="227*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="60"/>
<RowDefinition Height="40*"/>
<RowDefinition Height="160*"/>
</Grid.RowDefinitions>
<Label Content="Number of tracks" Grid.Row="0" Grid.Column="0"></Label>
<TextBox x:Name="txt_tracks" Grid.Row="0" Grid.Column="1" Width="50" Height="20"/>
<Button x:Name="bt_settracks" Grid.Row="0" Grid.Column="2" Content="Set tracks" Width="100" Height="20" Click="bt_settracks_Click"/>
<ScrollViewer Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<DataGrid x:Name="dgrid_tracks" CanUserAddRows="False"></DataGrid>
</ScrollViewer>
<Button x:Name="bt_setsegments" Content="Set segments" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Width="100" Height="20" Click="bt_setsegments_Click"/>
<ScrollViewer Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<DataGrid x:Name="dgrid_segments" CanUserAddRows="False"></DataGrid>
</ScrollViewer>
</Grid>
</Window>
using System;
using System.Collections.ObjectModel;
using System.Data;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public class dTracks
{
public ushort rowTracks { get; set; }
}
private void Create_Headers(DataGrid dgrid, ushort NUM_TRACKS)
{
dgrid.ItemsSource = null;
dgrid.Columns.Clear();
for (ushort i = 0; i < NUM_TRACKS; i++)
{
DataGridTextColumn newtrack = new DataGridTextColumn();
newtrack.Header = "Track " + i.ToString();
newtrack.Binding = new Binding("rowTracks");
newtrack.IsReadOnly = false;
newtrack.Width = 100;
dgrid.Columns.Add(newtrack);
}
}
private void Set_Tracks(DataGrid dgrid, ushort NUM_TRACKS)
{
Create_Headers(dgrid, NUM_TRACKS);
ObservableCollection<dTracks> tracks = new ObservableCollection<dTracks>() { new dTracks() { rowTracks = 0 } };
dgrid.ItemsSource = tracks;
}
private void Set_Segments(DataGrid dg_tracks, DataGrid dg_segments, ushort NUM_TRACKS)
{
Create_Headers(dg_segments, NUM_TRACKS);
MessageBox.Show(dg_tracks.Columns.Count.ToString());
MessageBox.Show(dg_tracks.Items.Count.ToString());
for (int j = 0; j < dg_tracks.Columns.Count; j++)
{
for (int i = 0; i < dg_tracks.Items.Count - 1; i++)
{
String s = (dg_tracks.Items[i] as DataRowView).Row.ItemArray[j].ToString();
MessageBox.Show(s);
}
}
}
private void bt_settracks_Click(object sender, RoutedEventArgs e) => Set_Tracks(dgrid_tracks, Convert.ToUInt16(txt_tracks.Text));
private void bt_setsegments_Click(object sender, RoutedEventArgs e) => Set_Segments(dgrid_tracks, dgrid_segments, Convert.ToUInt16(txt_tracks.Text));
}
}
When you run it, you will already see some problems:
- In the first DataGrid, it creates an additional column with the name of the variable in the class associated to the ObservableCollection;
- When you type the number of segments for each track, it applies to all cells instead of only the current cell;
- When you set the segments, the second DataGrid correctly shows the number of tracks you specified.
Along the function Set_Segments() I attempt to print some values just for debugging. The number of columns for the 1st DataGrid shows 1 more column then it should, and then I try to cycle through the cells to get their values and add them to the second DataGrid (to replicate the second picture), but these values are not coming and the MessageBox in the loop doesn't even come up with anything, not even an error.
Evidently there is something missing/wrong in the C# part, but so far I couldn't find any sample that considers different numbers of rows for each column, as most of the demos populate the datagrid with objects already in memory.
Maybe you guys can demonstrate how to solve those points mentioned above.


The main idea is to use a horizontally oriented
ListBoxfor the configuration table (in place of the single-rowDataGrid) and aDataGridfor the actual column view. For this view we have to deisable the cell and its borders to make it appear like a column-basedDataGrid.We basically fill a
DataTablewithNULLto mark a non-data cell:All cells that contain a
nullvalue are not rendered. The advantage over using a simpleListBoxper column is that you get the sort and reorder functionality of theDataGridcontrol.The following example shows how you can achieve the desired visual presentation by using a value converter and defineing the
DataGrid.CellStyle.Microsoft learn: Data binding overview (WPF .NET)
Microsoft learn: Data Templating Overview
MainWindow.xaml
MainWindow.xaml.cs
CellBorderThicknessConverter.cs
TableColumnInfo.cs