Using the same DataTemplate for two different properties of the same object in two DataGridTemplateColumns

1.8k views Asked by At

I've got two data templates in the resources and a DataTemplateSelector to choose between them:

    <DataGrid.Resources>
        <DataTemplate  x:Key="RedTemplate">
            <TextBlock Text="{Binding **Name1OrName2**}" />                        
       </DataTemplate >

       <DataTemplate x:Key="GreenTemplate">
        ....                
       </DataTemplate>

      <local:MyTemplateSelector x:Key="MyTemplateSelector"
                RedTemplate="{StaticResource RedTemplate}"
                GreenTemplate="{StaticResource GreenTemplate}" />

    </DataGrid.Resources>

Here is the code-behind of the selector:

public class MyTemplateSelector : DataTemplateSelector
    {
        public DataTemplate RedTemplate { get; set; }
        public DataTemplate GreenTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            if (item is RedItem) return RedTemplate;
            else if (item is GreenItem) return GreenTemplate; 
            else return base.SelectTemplate(item, container);                    
        }
    }

As long as I use MyTemplateSelector for one DataColumn (say, Name1), it works fine. But my DataGrid has two template columns to be bound to two string fields: Name1 and Name2

<DataGridTemplateColumn CellTemplateSelector="{StaticResource MyTemplateSelector}" > // Should be bound to Name1    
<DataGridTemplateColumn CellTemplateSelector="{StaticResource MyTemplateSelector}" > // Should be bound to Name2

My question is: how can I set the proper Path (Name1 or Name2) in the Binding (instead of Name1OrName2, see above). Thank you.

1

There are 1 answers

13
15ee8f99-57ff-4f92-890c-b56153 On

It looks like my original answer was due to a misunderstanding of the question, and the requirement isn't about the data template selector, but rather how to parameterize the property a binding binds to, so you can use the same template for two different properties.

Quick answer: That's not the way XAML is designed to be used. You can't parameterize the Path property of a Binding. The conventional solution is to write one template for each case. It would be nice if you could specify which property/field a DataGridTemplateColumn is meant to display, via a Binding or DisplayMemberPath property, and then passed that value to the template -- but it doesn't work that way.

I found a likely-looking workaround here, but I'm not sure the ROI on it would stack up well relative to copying and pasting a DataTemplate and getting on with your life.

If the templates are complicated enough for maintenance to be a concern, you can work around that like so:

XAML resources:

<DataTemplate x:Key="RedBaseTemplate">
    <Border BorderBrush="Green" BorderThickness="2" Margin="1">
        <Label x:Name="Text" Background="Red" Content="{Binding}" />
    </Border>
</DataTemplate>
<DataTemplate x:Key="GreenBaseTemplate">
    <Border BorderBrush="Red" BorderThickness="2" Margin="1">
        <Label x:Name="Text" Background="Green" Content="{Binding}" />
    </Border>
</DataTemplate>

<DataTemplate x:Key="RedTemplateA">
    <ContentControl 
        Content="{Binding A}"
        ContentTemplate="{StaticResource RedBaseTemplate}" 
        />
</DataTemplate>
<DataTemplate x:Key="RedTemplateB">
    <ContentControl 
        Content="{Binding B}"
        ContentTemplate="{StaticResource RedBaseTemplate}" 
        />
</DataTemplate>
<DataTemplate x:Key="GreenTemplateA">
    <ContentControl 
        Content="{Binding A}"
        ContentTemplate="{StaticResource GreenBaseTemplate}" 
        />
</DataTemplate>
<DataTemplate x:Key="GreenTemplateB">
    <ContentControl 
        Content="{Binding B}"
        ContentTemplate="{StaticResource GreenBaseTemplate}" 
        />
</DataTemplate>

Original Answer

This is a common pattern: You want multiple instances of the same DataTemplateSelector (or value converter, quite often), but with different parameters. The solution is to derive from MarkupExtension, so you can instantiate the thing at the point of use with its own unique parameters, rather than creating one shared instance someplace else as a resource. In this case DataTemplateSelector is a class rather than an interface, so you can't derive your selector from MarkupExtension. Instead you write a quick MarkupExtension that returns your selector.

I wanted to pass the templates themselves to RedGreenTemplateSelectorExtension using StaticResource or DynamicResource in the XAML, but the XAML parser didn't like the idea. But this works well enough.

public class RedGreenTemplateSelectorExtension : MarkupExtension
{
    public Object RedTemplateKey { get; set; }
    public Object GreenTemplateKey { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var redTemplate = new StaticResourceExtension(RedTemplateKey)
            .ProvideValue(serviceProvider) as DataTemplate;

        var greenTemplate = new StaticResourceExtension(GreenTemplateKey)
            .ProvideValue(serviceProvider) as DataTemplate;

        return new RedGreenTemplateSelector() {
            RedTemplate = redTemplate,
            GreenTemplate = greenTemplate
        };
    }
}

public class RedGreenTemplateSelector : DataTemplateSelector
{
    public DataTemplate RedTemplate { get; set; }
    public DataTemplate GreenTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is RedItem)
            return RedTemplate;
        else if (item is GreenItem)
            return GreenTemplate;
        else 
            return base.SelectTemplate(item, container);
    }
}

XAML

<StackPanel>
    <ContentControl
        ContentTemplateSelector="{local:RedGreenTemplateSelector RedTemplateKey=RedTemplate, GreenTemplateKey=GreenTemplate}"
        >
        <local:RedItem/>
    </ContentControl>
    <ContentControl
        ContentTemplateSelector="{local:RedGreenTemplateSelector RedTemplateKey=RedTemplate, GreenTemplateKey=GreenTemplate}"
        >
        <local:GreenItem/>
    </ContentControl>
</StackPanel>

P.S. StaticResource and Binding are two very different classes that do very different things. People misuse "binding" to mean "assignment". It's not. You aren't using any bindings at all here.