1

I have the following:

<DataTemplate DataType="{x:Type vm:MyViewModel}">
  <Grid>
    ...
    <ContentControl Content="{Binding}"
                    ContentTemplateSelector="{StaticResource MySelector}"/>
    ...
  </Grid>
</DataTemplate>

where MySelector gives a different view of the MyViewModel as indicated by MyViewModel.ViewName, and

class MyViewModel : INotifyPropertyChanged
{
    ...
    public string ViewName
    {
        get { return _viewName; }
        set
        {
            _viewName = value;
            OnPropertyChanged(() => ViewName);
        }
    }
    ...
}

How can I get the binding in the content control to update when ViewName changes?

NOTE I've also tried creating a property on MyViewModel that simply returns itself, binding to that, and then raising PropertyChanged for that property whenever ViewName changes.

<DataTemplate DataType="{x:Type vm:MyViewModel}">
  <Grid>
    ...
    <ContentControl Content="{Binding This}"
                    ContentTemplateSelector="{StaticResource MySelector}"/>
    ...
  </Grid>
</DataTemplate>

and

class MyViewModel : INotifyPropertyChanged
{
    ...
    public string ViewName
    {
        get { return _viewName; }
        set
        {
            _viewName = value;
            OnPropertyChanged(() => ViewName);
            OnPropertyChanged(() => This);
        }
    }
    public MyViewModel This { get { return this; } }
    ...
}

But my template selector is not invoked when ViewName changes in either case.

gregsdennis
  • 7,218
  • 3
  • 38
  • 71
  • 1
    Bit of a kludge, but might work: Use a `MultiBinding`. That requires a multi-value converter, so write a stub multivalue converter that just returns `values[0]` from Convert. Then you can populate your multi `MultiBinding` with Bindings: First the pathless Binding, followed by bindings to any properties that you wish to have trigger an update. When they raise PropertyChanged, the framework will cheerfully call your converter. I'd want to test to see if the template selector would get invoked again if the return from Convert never changes, that's the only thing. – 15ee8f99-57ff-4f92-890c-b56153 Jun 02 '16 at 14:00
  • @EdPlunkett, nope. Still not invoking the selector. – gregsdennis Jun 02 '16 at 14:26
  • Interestingly, the converter *is* invoked, however. – gregsdennis Jun 02 '16 at 14:32
  • 3
    I'd recommend using a `DataTrigger` to change your `ContentTemplate` instead of using a `ContentTemplateSelector`, like [this](http://stackoverflow.com/a/8927226/302677). It is known that one of the biggest limitations with `ContentTemplateSelector` is they they only re-evaluate when the `.Content` property changes, so in your case you'd have to bind the `.Content` to `ViewName` for it to re-evaluate the template to use. I would guess your second bit of code doesn't work because `This` never actually changes, and is the same reference of the object. – Rachel Jun 02 '16 at 14:41
  • At first, I thought there would be some typo in the binding. It took a while to get the point. Could you try changing templates with a DataTrigger? – PScr Jun 02 '16 at 14:41
  • 1
    Oops I used the wrong link for an example of using a DataTrigger to change ContentTemplate... I meant [this one](http://stackoverflow.com/a/13142699/302677) – Rachel Jun 02 '16 at 14:50
  • 1
    I wouldn't waste any more time on my idea -- id try Rachel's. – 15ee8f99-57ff-4f92-890c-b56153 Jun 02 '16 at 14:56
  • A simple workaround might be to make the `This` property settable, and set it to `null` and then back to `this`. – Clemens Jun 02 '16 at 15:05
  • @Rachel, I have nearly 20 different templates that I could choose from, and I'm using the selector in other parts of the application (with it working just fine). I'd rather not duplicate that code for just this usage, especially in `DataTrigger`s. – gregsdennis Jun 02 '16 at 16:21
  • @Clemens, I'm not setting `ViewName` in code. It's part of the `...` in the XAML bound to a `ListBox` selection. Also, the workaround isn't working around. – gregsdennis Jun 02 '16 at 16:27
  • @Rachel, though I *do* think my answer is in there: "A `DataTemplateSelector` does not respond to `PropertyChange` notifications, so it doesn't get re-evaluated when your properties change." Looks like I'll have to re-evaluate my approach to this view. Thanks. – gregsdennis Jun 02 '16 at 16:30
  • I used DataTemplateSelectors a few times for Silverlight, and never liked them so try to avoid them as much as possible. [This comment](http://stackoverflow.com/questions/8926024/8927226#comment14956745_8927226) suggests that they should respond to changes in the Content property, but I never got around to actually testing that. Its possible @Clemens is correct that setting it to null and back again might work, but it also might not because WPF binding engine would read the new value as the same as the old value, so not process the update. Good luck with whatever you choose to use :) – Rachel Jun 02 '16 at 16:40

1 Answers1

2

So it looks like Rachel led me to the right answer (or at least something that works).

It feels a little hacky, but it works. I subclassed ContentControl and overrode the OnContentChanged() method to explicity re-invoke the template selector when the content changed.

public class DynamicTemplatedContentControl : ContentControl
{
    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);

        if (ContentTemplateSelector == null) return;

        ContentTemplate = ContentTemplateSelector.SelectTemplate(newContent, this);
    }
}

Then in my XAML, I just use the second approach (in the question above) of creating a This property to which I bind my content.

gregsdennis
  • 7,218
  • 3
  • 38
  • 71