9

I have made a sample demo VS 2010 RC sample project, because in my production project I have the same error using MVVM.

In my sample demo project I use only Code-behind without 3rd party dependencies so you can download the demo project here and run it for yourself: http://www.sendspace.com/file/mwx7wv

Now to the problem: When I click the girls/boys button it should switch the datatemplate, not?

What do I wrong?

OK I offer here a code snippet too:

Code-Behind MainWindow.cs:

namespace ContentTemplateSelectorDemo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Person person;

        public MainWindow()
        {
            InitializeComponent();

            person = new Person(){ Gender = "xxx"};
            person.IsBoy = true;    

            ContentGrid.DataContext = person;
        }

        private void btnBoys_Click(object sender, RoutedEventArgs e)
        {
            person.IsBoy = true;
            person.IsGirl = false;
            this.ContentGrid.DataContext = person;
        }

        private void btnGirls_Click(object sender, RoutedEventArgs e)
        {
            person.IsGirl = true;
            person.IsBoy = false;
            this.ContentGrid.DataContext = person;    
        }        
    }
}

XAML MainWindow.xaml:

<Window x:Class="ContentTemplateSelectorDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ContentTemplateSelectorDemo"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>            
        <DataTemplate x:Key="girlsViewTemplate">
            <local:UserControl1 />
        </DataTemplate>

        <DataTemplate x:Key="boysViewTemplate" >
            <local:UserControl2 />
        </DataTemplate>

        <local:PersonDataTemplateSelector x:Key="PersonSelector" />            
    </Window.Resources>

    <Grid x:Name="ContentGrid" >
        <StackPanel>
            <Button Name="btnGirls" Click="btnGirls_Click">Switch Girls</Button>
            <Button Name="btnBoys" Click="btnBoys_Click">Switch Boys</Button>
        <ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ResourceKey=PersonSelector}" />
        </StackPanel>
    </Grid>
</Window>

DataTemplateSelector class:

public class PersonDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item,DependencyObject container)
    {
        if (item is Person)
        {
            Person person = item as Person;

            Window window = Application.Current.MainWindow;

            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode( window))
                return null;

            if (person.IsBoy)               
                return window.FindResource("boysViewTemplate") as DataTemplate;
            if (person.IsGirl)            
                return window.FindResource("girlsViewTemplate") as DataTemplate;

        }
        return null;
    }
}

:)

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
msfanboy
  • 5,273
  • 13
  • 69
  • 120
  • 1
    Better post code snippets instead of offering a download, people are most likely not going to download that. – Gerrie Schenck Apr 02 '10 at 09:17
  • 1
    ok after some research: http://joshsmithonwpf.wordpress.com/2007/03/18/updating-the-ui-when-binding-directly-to-business-objects-that-are-modified/ like josh said in his code comments: "...This is necessary because the WPF binding system will ignore a // PropertyChanged notification if the property returns the same object reference as before..." I must not return the same object instead I have to recreate the Person object in the buttons eventhandler like: ... = new Person(){ IsBoy = true, IsGirl = false} etc... then it works. thank you josh! – msfanboy Apr 03 '10 at 10:29
  • please tell me you don't actually have IsBoy and IsGirl properties in your real class :-) – Simon_Weaver Oct 12 '10 at 22:54
  • NO I have not :P this whole code is just a sample... but there is nothing wrong putting Gender Property in my model. – msfanboy Dec 31 '10 at 16:16

3 Answers3

5

I like Neil's solution (found on Josh's post via the link you provided):

<DataTemplate DataType="{x:Type local:MyType}">
    <ContentPresenter Content="{Binding}" Name="cp" />
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=IsRunning}" Value="True">
            <Setter TargetName="cp" Property="ContentTemplate" Value="{StaticResource StopTemplate}" />
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=IsRunning}" Value="False">
            <Setter TargetName="cp" Property="ContentTemplate" Value="{StaticResource StartTemplate}" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

Edit: I couldn't actually get the above code to work, but this works using a style:

<ContentControl DockPanel.Dock="Bottom" >
    <ContentControl.Style>
        <Style>      
            <Style.Triggers> 
                <DataTrigger Binding="{Binding Path=SourceSystem.SourceSystemName}" Value="mysite.com">
                    <Setter Property="ContentControl.ContentTemplate" Value="{StaticResource mysiteToolbar}" />
                </DataTrigger>

                <DataTrigger Binding="{Binding Path=SourceSystem.SourceSystemName}" Value="mysite2.com">
                    <Setter Property="ContentControl.ContentTemplate" Value="{StaticResource mysiteToolbar2}" />
                </DataTrigger>
            </Style.Triggers>            
        </Style>
    </ContentControl.Style>  
</ContentControl>
Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
3

Note: I think this method is quite clumsy, but could work for some scenarios. I favor the method of using a trigger (from Neil) that I posted as a separate answer.


Another possible way is to bind the Content of the ContentTemplateSelector to the property that determines the template that should be selected. For instance here I have two different toolbars chosen based upon the value of SourceSystem. I set the Content to be the sourcesystem property itself.

<ContentControl ContentTemplateSelector="{StaticResource toolbarTemplateSelector}" 
                DataContext="{Binding}" Content="{Binding SourceSystem}" />

The template selector simply looks at the source system and returns the necessary template.

If the template needs access to the datacontext of the control, just use element binding to set it.

 <UserControl.Resources>
    <DataTemplate x:Key="toolbar1">
        <views:OrdersToolbar1View Margin="0,5,0,0" 
               DataContext="{Binding ElementName=control,Path=DataContext}"/>
    </DataTemplate>
    <DataTemplate x:Key="toolbar2">
        <views:OrdersToolbar2View Margin="0,5,0,0" 
               DataContext="{Binding ElementName=control,Path=DataContext}"/>
    </DataTemplate>
 </UserControl.Resources>
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
-2

Use this method for custom Content Selector:

private void ReloadContent()
{
    MainContentControl.ContentTemplate = MainContentControl.ContentTemplateSelector.SelectTemplate(null, MainContentControl);
}

In xaml:

<ContentControl Content="{Binding}" x:Name="MainContentControl">
    <ContentControl.ContentTemplateSelector >
            <templateSelectors:MainViewContentControlTemplateSelector>
                <templateSelectors:MainViewContentControlTemplateSelector.BoysTemplate>
                    <DataTemplate>
                       <local:UserControl1 />
                    </DataTemplate>
                    </templateSelectors:MainViewContentControlTemplateSelector.BoysTemplate>
                <templateSelectors:MainViewContentControlTemplateSelector.GirlsTemplate>
                    <DataTemplate>
                        <local:UserControl2 />
                     </DataTemplate>
                     </templateSelectors:MainViewContentControlTemplateSelector.GirlsTemplate>
    </ContentControl>

And Selector :

public class MainViewContentControlTemplateSelector : DataTemplateSelector
{
    public DataTemplate BoysTemplate{ get; set; }
    public DataTemplate GirlsTemplate{ get; set; }


    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var contentControl = container.GetVisualParent<ContentControl>();
        if (contentControl == null)
        {
            return BoysTemplate;
        }

        if (//Condition)
        {
            return GirlsTemplate;

        }

        return BoysTemplate;
    }
  • 1
    While somewhat correct, it does not allow you to *perform content-binding to the **full object*** as in `` while allowing the _template selector to **observe different** thing than content_, like `.IsBoy` vs `.IsGirl` as in the question. – quetzalcoatl Apr 22 '15 at 09:28