1

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.

Jupirage
  • 11
  • 2

1 Answers1

3

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.

Community
  • 1
  • 1
  • Thank you Edward for your reply. I'd just like to clarify the question of binding to fields Name1 and Name2 from DataGridTemplateColumn. – Jupirage Dec 22 '16 at 10:33
  • I'm sorry, I misunderstood your needs. That's impossible. – 15ee8f99-57ff-4f92-890c-b56153 Dec 22 '16 at 12:17
  • On the Internet, I found a clue. Inside the SelectTemplate, at the moment the DataTemplate is about to return, we may modify the template’s bindings using FrameworkElementFactory in order to assign the proper Path: "Name1" or "Name2". Like factory.SetBinding(ContentPresenter.ContentProperty, new Binding(Path) ); I’m not quite sure yet. What would you say? – Jupirage Dec 22 '16 at 13:24
  • @Jupirage Sorry about that comment, I tried to delete it but clearly didn't. A `MarkupExtension` isn't a dependency object so there's no way to use bindings on its properties -- but of course there may always be a viable workaround. – 15ee8f99-57ff-4f92-890c-b56153 Dec 22 '16 at 14:06
  • @Jupirage But are you saying that you're happy with your template selector as it is, but what you really want to parameterize is the name of the property the templates bind to? (IOW I gave you a nice answer to a totally unrelated question -- oops!) – 15ee8f99-57ff-4f92-890c-b56153 Dec 22 '16 at 14:10
  • @Jupirage Updated answer with my best current guess at what you're trying to do. Still not sure though. – 15ee8f99-57ff-4f92-890c-b56153 Dec 22 '16 at 14:35
  • In fact, each grid line presents either a RED item or a GREEN item. And there are TWO similar columns bound to string fields Name1 and Name2 of each record. As those columns looks the same and differ in binding path only I decided to make both of them be based on the same pair of data templates and one selector. So I succeeded in parameterizing RED/GREEN but not Name1/ Name2. Actually the task may be stated leaving the Selector out. Just how to use one DataTemplate with several data fields (grid columns). – Jupirage Dec 22 '16 at 15:04
  • At first the task seemed rather easy to me: you just have to extract the binding from the template and modify its Path. Then I understood it was not a trivial task. And I gave up now! I just copy/pasted my two templates, changed the Path and that is all. Now I have 4 templates instead of 2. – Jupirage Dec 22 '16 at 15:04
  • OH, OK, I must have been half awake: RedItem and GreenItem each have Name1 and Name2, or something like that? I think four templates is best. If the templates are complicated enough for maintenance to be a concern, you could do a template within a template -- I'm adding that in an update. – 15ee8f99-57ff-4f92-890c-b56153 Dec 22 '16 at 15:12
  • "RedItem and GreenItem each have Name1 and Name2" - that is right. Thank you and sorry for my unclearness. – Jupirage Dec 22 '16 at 15:35
  • @Jupirage No, not at all. I jumped to conclusions instead of thinking about what you were saying. I liked my `MarkupExtension` idea so much, I went off to write code before I had read your question closely. – 15ee8f99-57ff-4f92-890c-b56153 Dec 22 '16 at 15:45
  • @Jupirage I did edit your title though, hope you don't mind. – 15ee8f99-57ff-4f92-890c-b56153 Dec 22 '16 at 15:46
  • 1
    The title is great now! I saved your code with MarkupExtension, hope to use it one day. – Jupirage Dec 22 '16 at 16:33