I have a WPF window that has a DataGrid as one of its children. The number of columns in the DataGrid is only determined at runtime. Here is the code for the DataGrid in my window.xaml file:
<DataGrid Name="pathLossGrid"
AutoGenerateColumns="True"
ItemsSource="{Binding }"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
HorizontalAlignment="Center"
GridLinesVisibility="All">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:PathLossCell/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
In the Windows.xaml.cs, I've declared a dependency property for a DataTable property:
public static DependencyProperty PathLossTableProperty = DependencyProperty.Register(nameof(PathLossTable), typeof(DataTable), typeof(PathLossSelector));
public DataTable PathLossTable
{
get => (DataTable)GetValue(PathLossTableProperty);
set => SetValue(PathLossTableProperty, value);
}
I should note at this point that each cell in the DataGrid is of a custom user control type, which I have called a PathLossCell in my example code and that user control is a simple grid with two columns:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*"/>
<ColumnDefinition Width="0.2*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding S2pId }"
MinWidth="50"/>
<Button Grid.Column="1"
Margin="3 0 0 0"
Command="{Binding SelectS2pFileCommand}">
<Viewbox Stretch="Uniform">
<ContentControl Content="{DynamicResource icon_plus}"/>
</Viewbox>
</Button>
</Grid>
the constructor of my Windows.xaml.cs file calls the following method:
public void InitPathLossTable()
{
if (PathLossTable == null && SelectedPortList != null)
{
var tmpTable = new DataTable("pathLossTable");
foreach (var port in SelectedPortList)
{
var portLoss = 0;
DataColumn newColumn = new DataColumn($"{port} (Max Loss= {portLoss} dB)");
newColumn.DataType = typeof(PathLossCell);
tmpTable.Columns.Add(newColumn);
}
//add default row
DataRow newRow = tmpTable.NewRow();
for (int col = 0; col < tmpTable.Columns.Count; col++)
{
newRow[col] = new PathLossCell() { S2pFile = "no data", RowId = 0, ColumnId = col, PortName = tmpTable.Columns[col].ColumnName };
}
tmpTable.Rows.Add(newRow);
PathLossTable = tmpTable;
pathLossGrid.DataContext = PathLossTable.DefaultView;
}
}
Based on my research on this site and other sources, this is the way to bind a DataTable to a DataGrid and what I expected to see in my window is a grid with 2 or 3 columns (depending on the number of items in the SelectedPortList and one row of default PathLossCell items. What I see when I run this code is a table that has the right number of columns (and the correct column headers), but only the cell in position [0,0] has the PathLossCell control in it and all the other cells display the fully qualified type string for PathLossCell and somehow an extra row:
I've tried setting the binding directly in xaml to: ItemsSource="{Binding PathLossTable.DefaultView }" with no discernable difference in the result and not also not including the DataGridTemplateColumn definition, in which case I don't see even the one instance of the PathLossCell user control in the first column. Any ideas would be appreciated.
Edit after applying fix suggested by @BionicCode:
Replaced the DataGrid.Columns template with DataGrid.CellStyle as suggested and here is the result:
When you define a column then you are doing just that, defining a column explicitly. This is not to be confused with a DataTemplate
e.g. for all cells.
If you set DataGrid.AutogenerateColumns
to true
then the DataGrid
will generate the column definitions for you by adding a DataGridTextColumn
to the DataGrid.Columns
collection for each column. As the name DataGridTextColumn
suggests, it only displays text (the object.ToString
value of the cell's item) as the ControlTemplate
of the DataGridCell
will simply contain a TextBlock
. If you need a more sophisticated column layout you can define a DataGridTemplateColumn
which supports a custom column layout by providing a cell template - only for the current column. That's why the name DataGridTemplateColumn
contains the word "Template". It's not a template for all columns but for the particular column definition.
To create a default template for all cells of the DataGrid
you simply provide a Style
for the DataGridCell
and assign it to the DataGrid.CellStyle
property.
Regarding your DataGrid.ItemsSource
bindig, you don't have to explicitly bind to the DataTable.DataView
property. The binding will automatically get the data view.
<DataGrid ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=PathLossTable}">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<!-- The DataContext is the current cell value (not the row item) -->
<local:PathLossCell/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CellStyle>
</DataGrid>