Hi
Like many others, I have a need for some dynamically created columns in a DataGrid within a WPF 4.5 project. As well as some static columns, new columns can be created or removed via the interface depending on user requirements. The project takes a View-Model-first approach and uses the Caliburn Micro framework (v1.4.1).
To tackle the problem of dynamic columns, I’m using an Observable Collection of DataGrid columns, per http://stackoverflow.com/questions/3065758/wpf-mvvm-datagrid-dynamic-columns. I then create (or remove) the columns in the View Model.
In order to overcome the problem of how to bind the cells within the new columns to the backing data context, I’ve been experimenting with inheriting from the DataGridTemplateColumn thus:
Public Class DataGridBoundTemplateColumn Inherits DataGridTemplateColumn Public Property BindingPath() As String Protected Overrides Function GenerateElement(ByVal cell As DataGridCell, ByVal dataItem As Object) As FrameworkElement Dim element = MyBase.GenerateElement(cell, dataItem) element.SetBinding(ContentPresenter.ContentProperty, New Binding(Me.BindingPath)) Return element End Function Protected Overrides Function GenerateEditingElement(ByVal cell As DataGridCell, ByVal dataItem As Object) As FrameworkElement Dim element = MyBase.GenerateEditingElement(cell, dataItem) element.SetBinding(ContentPresenter.ContentProperty, New Binding(Me.BindingPath)) Return element End Function End Class
This is a VB translation of the work presented at http://stackoverflow.com/questions/14000873/define-datatemplate-in-xaml-instantiate-and-modify-in-code.
When it comes time to add a column, I do the following:
Dim myCol As New DataGridBoundTemplateColumn Dim viewTemplate = CType(Application.Current.TryFindResource("EnvTemplate"), DataTemplate) Dim editTemplate = CType(Application.Current.TryFindResource("EnvEditingTemplate"), DataTemplate) EnvVarsDT.Columns.Add(EnvName) With myCol .Header = EnvName .Width = 100 .CellTemplate = viewTemplate .CellEditingTemplate = editTemplate .BindingPath = EnvName End With DataGridColumns.Add(myCol)
The XAML code for the DataGrid is simply:
<DataGrid Name="EnvVarsDT" Grid.Row="1" CanUserAddRows="False" Margin="5" AutoGenerateColumns="False" jas:DataGridExtension.Columns="{Binding Path=DataContext.DataGridColumns, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
… where jas: is the namespace for my project. EnvVarsDT is a DataTable within the View Model and the DataGrid is named the same, following Caliburn conventions. So far so good; the columns get added, with the correct headers.
Now to the problem. Using a templated approach (either the custom DataGridBoundTemplateColumn or a plain vanilla DataGridTemplateColumn), I am unable to get the bindings to work. No binding errors show up in the Immediate window (or Output for that matter). Meanwhile Snoop shows the DataContext property on each cell in the column to be empty. User edits get lost as soon as they tab off the cell, and the values are not available via the DataTable.
I can however get the bindings working if I use a regular DataGridTextColumn:
Dim myCol As New DataGridTextColumn With myCol .Binding = New Binding(EnvName) .Width = 100 .CellStyle = CType(Application.Current.TryFindResource("CellStyle"), Style) .Header = EnvName End With
Now each cell in the dynamically added columns is properly bound and user edits are retained.
But I need the construction of the cells to vary according to another data value in the row, so I don’t think I can use a DataGridTextColumn. The editing template referenced in the template column approach specifies a button in one case and a textbox otherwise (and these show up properly, albeit with no binding value, when using a DataGridTemplateColumn). I have experimented just with plain textboxes so as to take a complicated data template out of the equation, but to no avail. Using a DataGridTemplateColumn, the editing controls are revealed and one can enter text, but the data is lost on tabbing away. Using the DataGridBoundTemplateColumn, the controls are not revealed when attempting to edit – it is as if the cells were Read Only.
The DataTemplates I’m using are, for simple test purposes, as follows:
<DataTemplate x:Key="EnvTemplate"><TextBlock Text="{Binding Path=.}"/></DataTemplate><DataTemplate x:Key="EnvEditingTemplate"><TextBox Text="{Binding Path=.}"/></DataTemplate>
I’ve tried playing with setting the DataContext in these templates, eg DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type data:DataRowView}}}",but without success.
I feel close to a working solution, with the binding on a dynamically added DataGridTemplate column being the outstanding matter. Any guidance as to things to try will be much appreciated.
With thanks and regards
Sebastian Crewe