WPF DataGrid: Using DataTemplates for auto-generated columns






4.78/5 (15 votes)
An article on using templates with auto-generated columns in the WPF DataGrid.
Introduction
The DataGrid
is a control for displaying tabular data. Setting AutoGenerateColumns
to true will automatically generate a column for each field. While this is quite useful, it does not give much control about the generated column. This article shows how you can use a DataTemplate
for automatically generated columns with an attached property.
Implementation
The DataGrid
provides an event called AutoGeneratingColumn
. This event is fired for each individual column that is generated. You can adjust or cancel the generated column in the event handler. By setting DataGridAutoGeneratingColumnEventArgs.Column
, you can provide your own column.
There is already a column class with support for templates, called DataGridTemplateColumn
. This column has no binding support like DataGridBoundColumn
. The data context for DataGridTemplateColumn
is DataRowView
. In order to reuse data templates for multiple fields, I created a custom class, CustomDataGridTemplateColumn
, that extends DataGridTemplateColumn
and adds binding support.
The AutoGeneratingColumn
event handler is listed below. In this event handler, a new DataGridTemplateColumn
column is created when the current column name is found in the AutoGenerateColumnCollection
.
public static void _grid_AutoGeneratingColumn(
object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataGrid grid = sender as DataGrid;
if (grid == null) { return; }
AutoGenerateColumnCollection coll = GetColumns(grid);
foreach (AutoGenerateColumn col in coll)
{
if (e.PropertyName == col.Column)
{
CustomDataGridTemplateColumn templateColumn =
new CustomDataGridTemplateColumn();
templateColumn.Header = e.Column.Header;
if (col.CellTemplate != null)
{
templateColumn.CellTemplate = col.CellTemplate;
}
if (col.CellEditingTemplate != null)
{
templateColumn.CellEditingTemplate = col.CellEditingTemplate;
}
if (col.Binding != null)
{
templateColumn.Binding = col.Binding;
}
templateColumn.SortMemberPath = e.Column.SortMemberPath;
e.Column = templateColumn;
return;
}
}
return;
}
The AutoGenerateColumnCollection
originates from the attached property GenerateTemplateColumn.Columns
. AutoGenerateColumnCollection
is a class that extends ObservableCollection<AutoGenerateColumn>
and is used to store the column names and templates.
The AutoGenerateColumn
class is listed below:
public class AutoGenerateColumn
{
public string Column
{
get; set;
}
public DataTemplate CellTemplate
{
get; set;
}
public DataTemplate CellEditingTemplate
{
get; set;
}
public System.Windows.Data.BindingBase Binding
{
get; set;
}
}
The AutoGeneratingColumn
event handler is registered when the attached property changes. The PropertyChangedCallback
for the attached property is shown below:
private static void OnColumnsChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
AutoGenerateColumnCollection coll =
e.NewValue as AutoGenerateColumnCollection;
DataGrid grid = coll.Owner as DataGrid;
if (grid != null)
{
grid.AutoGeneratingColumn += _grid_AutoGeneratingColumn;
}
}
if (e.OldValue != null)
{
AutoGenerateColumnCollection coll =
e.OldValue as AutoGenerateColumnCollection;
DataGrid grid = coll.Owner as DataGrid;
if (grid != null)
{
grid.AutoGeneratingColumn -= _grid_AutoGeneratingColumn;
}
}
}
Using the code
To use the code in your own project, you will need to include AutoGenerateColumn.cs and CustomDataGridTemplateColumn.cs.
You can specify the templates using the attached property GenerateTemplateColumn.Columns
. The XAML below (from the sample application) shows how to specify templates for a column:.
<toolkit:DataGrid x:Name="dataGrid1" AutoGenerateColumns="True">
<local:GenerateTemplateColumn.Columns>
<local:AutoGenerateColumn Column="ItemCreatedStamp"
CellTemplate="{StaticResource TimeStampCellTemplate}"
CellEditingTemplate="{StaticResource CellEditTemplate}"
Binding="{Binding Path=ItemCreatedStamp}"/>
<local:AutoGenerateColumn Column="ItemUpdatedStamp"
CellTemplate="{StaticResource TimeStampCellTemplate}"
CellEditingTemplate="{StaticResource CellEditTemplate}"
Binding="{Binding Path=ItemUpdatedStamp}"/>
<local:AutoGenerateColumn Column="ItemLink"
CellTemplate="{StaticResource HttpHyperCellTemplate}"
CellEditingTemplate="{StaticResource CellEditTemplate}"
Binding="{Binding Path=ItemLink}"/>
<local:AutoGenerateColumn Column="ItemStatusId"
CellTemplate="{StaticResource StatusCellTemplate}"
CellEditingTemplate="{StaticResource CellEditTemplate}"
Binding="{Binding Path=ItemStatusId}" />
</local:GenerateTemplateColumn.Columns>
</toolkit:DataGrid>
A template can be as simple as:
<DataTemplate x:Key="CellTemplate">
<TextBlock Text="{Binding}" BorderThickness="0" Padding="0" />
</DataTemplate>
This way, you can easily customize the look of auto-generated columns.
Sample application
To build the demo project, you will need SQLite.NET and the WPF Toolkit.
The sample application includes a sample SQLite3 database (database.db) to demonstrate the use of templates. In the sample application, you can run any query and open different SQLite databases. By using templates, you can make the output more useful.
To query data from the main table:
SELECT * FROM Item
Running this query will show columns where templates are used. For example, a template is used for showing clickable links.
Sample application extra functionality
To make the sample application a bit more useful, code to allow the user to copy the data from the DataGrid
to the clipboard is included. The code therefore was based on the code found in the links listed below:
- http://blogs.msdn.com/vinsibal/archive/2008/08/11/net-3-5-sp1-and-wpf-datagrid-ctp-is-out-now.asp
- http://blogs.msdn.com/vinsibal/archive/2008/12/18/wpf-datagrid-sample-locking-input-to-the-row-being-edited.aspx
TextBox context menu in DataGrid edit modus
While using the DataGrid
, I noticed that the context menu of the TextBox
was not working correctly in edit modus. When you right click on a TextBox
in edit mode, the context menu is shown, but the items are disabled, which prevents you from using it. The reason for this is that when opening the context menu, the focus changes and the DataGridCell
goes out of edit modus.
The solution is based on WPF DataGrid Sample: Locking input to the row being edited. In the CellEditEnding
event, you cancel the event when a context menu is open. In order to detect whether a context menu is open, you can use the ContextMenuOpening
and ContextMenuClosing
events. The TextBox
inside the DataGridCell
uses TextEditorContextMenu.OnContextMenuOpening
, which prevents you from receiving the events. In order to catch the event, you can use a custom ContextMenu
like shown below:
<ContextMenu x:Key="textBoxMenu">
<MenuItem Command="ApplicationCommands.Cut">
<MenuItem.Icon>
<Image Source="Resources\cut.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="ApplicationCommands.Copy">
<MenuItem.Icon>
<Image Source="Resources\copy.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="ApplicationCommands.Paste">
<MenuItem.Icon>
<Image Source="Resources\paste.png"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
You can set this context menu in various ways, when using auto-generated columns directly on the TextBox
in the template, or using a style. See _grid_AutoGeneratingColumn
in the sample application for non-templated auto generated columns.
Points of interest
While using templates for auto-generated columns is useful, the templates are applied based on the column name. When querying different data with the same column names, this can lead to unintended results.
History
- 4-11-2009: Initial article upload.