Tuesday 24 December 2013

WPF DataGrid - Custom template for generic columns

Recently I had to bind a WPF DataGrid to a System.Data.DataSet. This is quite straightforward and there are many tutorials on how to achieve this.

By default all table columns are auto-generated using 4 predefined templates (Text, Hyperlink, CheckBox, and ComboBox) that support read-only and edit modes. If you wish to customize the way some columns are rendered you can also define a custom template and assign it to some columns by hooking into the AutoGeneratingColumns event of the DataGrid as described here.

Problem with generic columns

As you can see creating custom templates for columns is pretty straightforward as long as column names are fixed. If your WPF app uses a table that doesn't change dynamically you are all good. The problem starts when you use your datagrid to display tables, whose columns` names change e.g. tables loaded from a file at runtime. This is because you can't use the column name in your custom template.

Solution 1 - Create template programmatically

In this solution you build your custom template in code and assign it to the chosen column at runtime, in the AutoGeneratingColoumn event handler.
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    // First get the corresponding DataColumn
    var colName = e.PropertyName;
    var table this.DataContext as DataTable;
    var tableColumn = table.Columns[colName];

    // choose columns to customize e.g. by type
    if (YOUR CONDITION E.G. COLUMN TYPE)
    {
       var templateColumn = new DataGridTemplateColumn();
       templateColumn.Header = colName;
       templateColumn.CellTemplate = this.BuildCustomCellTemplate(colName);
       templateColumn.SortMemberPath = colName;
       e.Column = templateColumn;
    }
}

// builds custom template
private DataTemplate BuildCustomCellTemplate(string columnName)
{
    var template = new DataTemplate();

    var button = new FrameworkElementFactory(typeof (Button));
    template.VisualTree = button;

    var binding = new Binding();
    binding.Path = new PropertyPath(columnName);
     
    button.SetValue(ContentProperty, binding);

    return template;
}
The code above would create the following template for selected columns:

    
Obviously this is just an example - in real life you would need more than just a button that does nothing. In your code you can define full templates, use binding converters, assign commands etc. However, the code gets pretty complex. Therefore this solution is suitable for simple templates.

Solution 2 - Create template skeleton

Alternatively, you can create the template skeleton in XAML and replace all bindings in your event handler:


    
And the event handler:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    (...)

    if (YOUR CONDITION E.G. COLUMN TYPE)
    {
       // Create wrapping template in code and populate all bindings accordingly 
       string xaml = @"";
       var template = (DataTemplate)XamlReader.Load(string.Format(xaml, "{Binding " + colName + "}", "{StaticResource customCellTemplate}"));
       templateColumn.CellTemplate = template;
       
       (...)
    }
}
The advantage of this approach is that you can create more complex templates in XAML and in your event handler code only populate all required bindings. The limitation of this method is that the custom template needs to be defined at the application level. I found this solution here.

No comments: