ContentTemplateSelector is only called one time showing always the same datatemplate
Asked Answered
G

3

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;
    }
}

:)

Grof answered 2/4, 2010 at 9:8 Comment(4)
Better post code snippets instead of offering a download, people are most likely not going to download that.Heilner
ok after some research: joshsmithonwpf.wordpress.com/2007/03/18/… 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!Grof
please tell me you don't actually have IsBoy and IsGirl properties in your real class :-)Autecology
NO I have not :P this whole code is just a sample... but there is nothing wrong putting Gender Property in my model.Grof
A
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>
Autecology answered 12/10, 2010 at 22:43 Comment(1)
#5771862Envenom
A
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>
Autecology answered 12/10, 2010 at 22:54 Comment(0)
K
-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;
    }
Kalagher answered 26/7, 2013 at 9:48 Comment(1)
While somewhat correct, it does not allow you to perform content-binding to the full object as in <ContentControl Content="{Binding}" ..> while allowing the template selector to observe different thing than content, like .IsBoy vs .IsGirl as in the question.Eckert

© 2022 - 2024 — McMap. All rights reserved.