How to reuse WPF DataGridTemplateColumn (including binding)
Asked Answered
P

2

19

In WPF datagrids, I have a column defined as DataGridTemplateColumn which I'll need to be using on all kinds of columns. As a very simplified example please consider the below as a dummy sample:

<DataGrid ItemsSource="{Binding Path=ItemList, Mode=OneWay}" AutoGenerateColumns="False" >                
    <DataGrid.Columns>

        <DataGridTemplateColumn Header="Name" MinWidth="130" Width="Auto">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <DockPanel LastChildFill="True">
                        <Image Source="component/Images/test.png"/>
                        <TextBlock Text="{Binding Path=Name, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
                    </DockPanel>                                
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <DockPanel LastChildFill="True">
                        <Image Source="component/Images/test.png"/>
                        <TextBox Text="{Binding Path=Name, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
                    </DockPanel>
                </DataTemplate>                            
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>

        <DataGridTextColumn Header="Company" Binding="{Binding Company, ValidatesOnDataErrors=True}" MinWidth="115" Width="Auto"/>                    
    </DataGrid.Columns>
</DataGrid>

For a simple example, how could I apply the same template used for column with Header=Name to the column with Header=Company without having to reproduce the entire template for every column?

I've found an answer with this previous SO question, where they explain using resources like:

<Application.Resources> 
     <DataTemplate x:Key="CellTemplate"> 
     ... 
     </DataTemplate> 
     <DataTemplate x:Key="CellEdintingTemplate"> 
     ... 
     </DataTemplate> 
</Application.Resources> 

<DataGrid Style="{StaticResource MainGridStyle}"> 
    <DataGrid.Columns> 
        <DataGridTemplateColumn CellTemplate="{StaticResource MyFirstColumnCellTemplate}" CellEdintingTemplate="{StaticResource MyFirstColumnCellEdintingTemplate}"/> 
        ... 
    </DataGrid.Columns> 
<DataGrid>  

That gets me 95% there, but the final piece I'm missing is how to handle the data binding? How do I create some type of place holder in the template and then do the actual binding in the grid?

EDIT I've kept looking and found the question Create Common DataGridTemplateColumn which sounds like what I want to do may in fact be impossible currently. So if anyone else is ever trying to do this, and sees this question I cannot guarantee it is impossible, but from this link seems it may be. So will just need to duplicate all the tempalte code for every column.

Publea answered 7/8, 2012 at 15:43 Comment(0)
A
15

You can set the CellStyle property to a style that overwrites the Template for the DataGridCell.

In the Template, use a ContentPresenter that is bound to the TemplatedParent.Content wherever you want to place the DataGridCell's Content, since the TemplatedParent is the DataGridCell

For example,

<Style x:Key="MyCellStyle" TargetType="{x:Type DataGridCell}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <DockPanel LastChildFill="True">
                    <Image Source="component/Images/test.png"/>
                    <ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" />
                </DockPanel>  
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<DataGrid ItemsSource="{Binding ItemList}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" CellStyle="{StaticResource MyCellStyle}" MinWidth="130" Width="Auto" />
        <DataGridTextColumn Header="Company" Binding="{Binding Company}" CellStyle="{StaticResource MyCellStyle}" MinWidth="115" Width="Auto"/>                    
    </DataGrid.Columns>
</DataGrid>
Amoreta answered 7/8, 2012 at 18:20 Comment(1)
Would it be possible for ContentPresenter to use a DataTemplate defined in resources somewhere nearby?Vernacularize
T
5

There is a solution in the link Create Common DataGridTemplateColumn.

Poptart911 made a DatagridBoundTemplateColumn which can be used for replacing DataGridTemplateColumn, allowing you to set the "Binding" property on the column. Hence you can reuse the DataTemplate and assign different DataContext to the template for different column.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace OwensWpfFunStuff
{
public class DataGridBoundTemplateColumn : DataGridBoundColumn
{
    public DataTemplate CellTemplate { get; set; }
    public DataTemplate CellEditingTemplate { get; set; }

    protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        return Generate(dataItem, CellTemplate);
    }

    protected override System.Windows.FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        return Generate(dataItem, CellEditingTemplate);
    }

    private FrameworkElement Generate(object dataItem, DataTemplate template)
    {
        var contentControl = new ContentControl { ContentTemplate = template, Content = dataItem };
        BindingOperations.SetBinding(contentControl, ContentControl.ContentProperty, Binding);
        return contentControl;
    }
}
}

For example, XAML:

<DataGrid ItemsSource="{Binding Devices}" AutoGenerateColumns="False">
    <DataGrid.Resources>
        <DataTemplate x:Key="VariableTemplate">
            <TextBlock Text="{Binding VarName}"/>
        </DataTemplate>
        <DataTemplate x:Key="VariableEditingTemplate">
            <TextBox Text="{Binding VarName, UpdateSourceTrigger=PropertyChanged}"/>
        </DataTemplate>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <local:DataGridBoundTemplateColumn Header="Input Variable"
                                            Binding="{Binding InputVariable}"
                                            CellTemplate="{StaticResource VariableTemplate}"
                                            CellEditingTemplate="{StaticResource VariableEditingTemplate}">
        </local:DataGridBoundTemplateColumn>
        <local:DataGridBoundTemplateColumn Header="Output Variable"
                                            Binding="{Binding OutputVariable}"
                                            CellTemplate="{StaticResource VariableTemplate}"
                                            CellEditingTemplate="{StaticResource VariableEditingTemplate}">
        </local:DataGridBoundTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

ViewModel and data types

class MainWindowVM
{
    public List<Device> Devices { get; set; } = new List<Device>()
    {
        new Device()
        {
            InputVariable = new MyVariable()
            {
                VarName = "theInputVar"
            },
            OutputVariable = new MyVariable()
            {
                VarName = "theOutputVar"
            }
        }
    };
}

public class Device
{
    public MyVariable InputVariable { get; set; }
    public MyVariable OutputVariable { get; set; }
}

public class MyVariable
{
    public string VarName { get; set; }
}
Trolly answered 28/3, 2018 at 10:0 Comment(6)
Note: To use the binding within the DataTemplate, simply use {Binding .}Mopey
@Mopey Or more simply {Binding}Nittygritty
How would this work with a two-way binding in the CellEditingTemplate?Pilkington
@MichelJansson Check out the example I added.Trolly
@YantingChen, thank you very much for the updates - it highlighted why I could not get it to work. I didn't expect to have to create model structure where each property that I want to bind to needs a child property with a generic name. To work around that requirement I added a binding proxy in the DataGridBoundTemplateColumn that allows me to bind to my primitive properties as normal. If anyone is interested in that, I could post it as a separate answer.Pilkington
@Michel Jansson could you please share your approach?Blankly

© 2022 - 2024 — McMap. All rights reserved.