Add data binding for DataGridTemplateColumn created in code

Question:

Is there a way to define a DataTemplate in XAML and create an instance in the code (instead of retrieving a singleton using FindResource ) and change its VisualTree before sending it to where the DataTemplate is needed, such as DataGridTemplateColumn.CellTemplate ?

Background:

I am showing a two-dimensional array of data[][] in a DataGrid , adding the columns of the DataGridTemplateColumn myself, and in XAML there is a DataTemplate that knows how to represent each element in the array. However, by default, the DataContext for each cell is a row, that is, data[x] . Therefore, I need to "parameterize" the DataTemplate for each column by setting the root visual DataContext element to the binding "[y]" , where y is the column index. Currently, a DataTemplate is defined as in DataGrid.Resources and FindResource() is retrieved, which returns the same instance each time. Also, calling LoadContent() gives me a UIElement tree instead of loading VisualTree in the DataTemplate itself. I am looking for a way to create an instance of a DataTemplate in the code, perform the necessary modification, and set the DataGridTemplateColumn.CellTemplate .

+4
source share
3 answers

Inspired by Sisyphe's answer, I found this more portable solution:

 public class DataGridBoundTemplateColumn : DataGridTemplateColumn { public string BindingPath { get; set; } protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) { var element = base.GenerateEditingElement(cell, dataItem); element.SetBinding(ContentPresenter.ContentProperty, new Binding(this.BindingPath)); return element; } protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) { var element = base.GenerateElement(cell, dataItem); element.SetBinding(ContentPresenter.ContentProperty, new Binding(this.BindingPath)); return element; } } 

Using:

 var cellTemplate = (DataTemplate)this.dataGrid.FindResource("cellTemplate"); foreach (var c in data.Columns) { var col = new DataGridBoundTemplateColumn { Header = c.HeaderText, CellTemplate = cellTemplate, BindingPath = string.Format("[{0}]", c.Index) }; this.dataGrid.Columns.Add(col); } 

Hope this helps someone who has the same requirement as in my question.

+6
source

You should see the DataTemplate in WPF as Factory. So, I think that you really don't need a new instance of the DataTemplate , you just want it to be applied differently depending on your context.

If I understand your problem correctly, the problem is that the DataContext your DataGrid Cells are incorrect: this is a Row ViewModel, whereas you want it to be a Cell ViewModel (which makes sense). This, however, is the main behavior of the DataGrid and is probably due to the fact that the cells in each row are held by the DataGridCellsPresenter (which is mainly ItemsControl ), the ItemsSource property is not set (thus explaining the bad DataContext ).

I ran into this problem and found two ways to fix this (but I only managed to do one job).

The first of these is a subclass of DataGridCellsPresenter and overrides the OnItemChanged method to set the ItemsSource element manually.

 protected override void OnItemChanged(object oldItem, object newItem) { var rowViewModel = newItem as ViewModel; if (rowViewModel != null) { ItemsSource = rowViewModel.Items; } else { ItemsSource = null; } } 

where rowViewModel.Items should point to something like data [x] in your case. However, I ran into some problems using this fix and was unable to get it to work correctly.

The second solution is a subclass of DataGridCell and updates the dataContext when ColumnProperty changes. You must also subclass DataGridCellsPresenter to create the correct cell controls.

 public class MyDataGridCell : DataGridCell { protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { if (e.Property == ColumnProperty) { var viewModel = DataContext as YourViewModelType; if (viewModel != null) { var column = (e.NewValue as DataGridTemplateColumn); if (column != null) { var cellViewModel = viewModel[column.DisplayIndex]; DataContext = cellViewModel; } } } base.OnPropertyChanged(e); } } public class MyDataGridCellsPresenterControl : DataGridCellsPresenter { protected override System.Windows.DependencyObject GetContainerForItemOverride() { return new MyDataGridCell(); } } 

Finally, you will also have to override the default ControlTemplate DataGridRow so that it uses your own DataGridCellsPresenter instead of the original DataGridCellsPresenter .

 <ControlTemplate x:Key="DataGridRowControlTemplate" TargetType="{x:Type DataGridRow}"> <Border x:Name="DGR_Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True"> <SelectiveScrollingGrid> <SelectiveScrollingGrid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </SelectiveScrollingGrid.ColumnDefinitions> <SelectiveScrollingGrid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </SelectiveScrollingGrid.RowDefinitions> <local:MyDataGridCellsPresenter Grid.Column="1" ItemsPanel="{TemplateBinding ItemsPanel}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> <DataGridDetailsPresenter Grid.Column="1" Grid.Row="1" Visibility="{TemplateBinding DetailsVisibility}"> <SelectiveScrollingGrid.SelectiveScrollingOrientation> <Binding Path="AreRowDetailsFrozen" RelativeSource="{RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type DataGrid}}"> <Binding.ConverterParameter> <SelectiveScrollingOrientation>Vertical</SelectiveScrollingOrientation> </Binding.ConverterParameter> </Binding> </SelectiveScrollingGrid.SelectiveScrollingOrientation> </DataGridDetailsPresenter> <DataGridRowHeader Grid.RowSpan="2" SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"> <DataGridRowHeader.Visibility> <Binding Path="HeadersVisibility" RelativeSource="{RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type DataGrid}}"> <Binding.ConverterParameter> <DataGridHeadersVisibility>Row</DataGridHeadersVisibility> </Binding.ConverterParameter> </Binding> </DataGridRowHeader.Visibility> </DataGridRowHeader> </SelectiveScrollingGrid> </Border> </ControlTemplate> 
+2
source
 (templateKey as DataTemplate).LoadContent() 

Description: When you call LoadContent , UIElement objects are created in the DataTemplate , and you can add them to the visual tree of another UIElement .

+1
source

All Articles