How can I create a reusable CellTemplate for a DataGridTemplateColumn in a DataGrid? (XAML only)

Viewed 1703

I have been searching for what I was hoping should be a very simple solution to this problem, but although I've found quite a few other questions on stackoverflow that seem to ask something similar, with some suggested answers, I've been through them all, and the answers either don't work, or don't actually answer the question.

I have a WPF DataGrid with a few columns, which I have set up as DataGridTemplateColumns, like so:

<DataGridTemplateColumn Header="Price 1" Width="60">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ContentPresenter Content="{Binding Price1}" ContentTemplate="{StaticResource NumericCellDataTemplate}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Price 2" Width="60">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ContentPresenter Content="{Binding Price2}" ContentTemplate="{StaticResource NumericCellDataTemplate}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Price 3" Width="60">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ContentPresenter Content="{Binding Price3}" ContentTemplate="{StaticResource NumericCellDataTemplate}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

I have defined a reusable DataTemplate called "NumericCellDataTemplate", which provides some common features to each of the columns, including number formatting, alignment, and background highlighting on value changes. Note this is all driven off some special properties I've defined in my ViewModel:

<DataTemplate x:Key="NumericCellDataTemplate">
    <Border Name="cellBorder" BorderThickness="0" BorderBrush="Transparent">
        <Border.Style>
            <Style TargetType="{x:Type Border}">
                <Style.Setters>
                    <Setter Property="Background" Value="Transparent" />
                </Style.Setters>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsRecentlyChanged}" Value="True">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard Storyboard="{StaticResource IsRecentlyChangedEnterStoryboard}" />
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <BeginStoryboard Storyboard="{StaticResource IsRecentlyChangedExitStoryboard}" />
                        </DataTrigger.ExitActions>
                    </DataTrigger>                                
                </Style.Triggers>
            </Style>
        </Border.Style>
        <TextBlock Text="{Binding Value, StringFormat='#,##0.00'}" HorizontalAlignment="Right" />
    </Border>
</DataTemplate>

I have set up storyboards for those background color animations, to be triggered when the values change. The viewModel is tracking this and setting/unsetting my IsRecentlyChanged property for each value individually:

<Storyboard x:Key="IsRecentlyChangedEnterStoryboard">
    <ColorAnimation Duration="0:0:0.3" To="LightSteelBlue" FillBehavior="HoldEnd" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" />
</Storyboard>
<Storyboard x:Key="IsRecentlyChangedExitStoryboard">
    <ColorAnimation Duration="0:0:0.3" To="Transparent" FillBehavior="HoldEnd" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" />
</Storyboard>

Everything here seems to work fine. Note that one of the reasons I am using a custom DataGridTemplateColumn, and not just a simple DataGridTextColumn, is that my property I am binding to (such as Price1) is a complex object, and not just a simple value type or string to be displayed. It is actually an IPropertyModel, which is an interface I have defined with a few useful properties for the view layer to bind to:

public interface IPropertyModel : INotifyPropertyChanged
{
    string Name { get; }
    bool IsRecentlyChanged { get; }
}

public interface IPropertyModel<T> : IPropertyModel
{
    T Value { get; set; }
}

So all I would like to do is find a way to simplify the definition of those DataGridTemplateColumns I mentioned above, to a simple one-line thing like this:

<DataGridTemplateColumn Header="Price1" Width="60" Binding="{Binding Price1}" ContentTemplate="{StaticResource NumericCellDataTemplate}" />

Of course the problem here is that the DataGridTemplateColumn does not have a Binding property the way the DataGridTextColumn does. I have tried many of the suggestions, such as using a Style on the DataGridCell to completely override its Template with a new ControlTemplate, and provide a custom ContentPresenter. I have also tried doing this with a DataGridTextColumn, in order to take advantage of the fact that it already has a Binding property we could use - but I can't see how to access it from within the Style or the ContentPresenter.

Some of the suggestions talk about using the RelativeAncestor or TemplateBinding to get back to the DataGridTemplateColumn, and then read an attached property from it, such as ap:MyAttachedProperties.Binding - but as some have noted, once you are in the DataGridCell template, you no longer have access to the original DataGridTemplateColumn, because it is not in the Visual/Logical tree I guess? So after much experimentation that also did not prove helpful.

Any other suggestions would be appreciated. I am happy to use the DataGridTextColumn, DataGridTemplateColumn, or something else if anyone has a better idea. In this example I only have 3 columns, and one reusable DataTemplate for all of them. But Imagine a more complex scenario, where we have 20+ columns, possibly binding to one of several different reusable DataTemplates to do different kinds of formatting and display logic - so this is going to get messy if every column needs 7+ lines of XAML code.

Thanks!

0 Answers
Related