I need to change the view of a TabControl's content on-the-fly.
I am guessing the best way to accomplish this is to define the view as a DataTemplate, and then change said template using a trigger.
In my test app, the background color is tied to the same data trigger as the template. The background color updates immediately upon making the radio button selection.
Here's my Minimal, Complete, and Verifiable example:
Window XAML
<Window x:Class="ChangeView.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
Title="Window1" Height="350" Width="400">
<Window.Resources>
<DataTemplate x:Key="ContentTemplate1">
<Grid>
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding MyBlurb}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ContentTemplate2">
<Grid>
<Label HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Content="{Binding MyHeader}" Background="Black" Foreground="White" FontSize="72"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType1}" Value="False">
<Setter Property="Background" Value="Chartreuse"/>
</DataTrigger>
<DataTrigger Binding="{Binding ViewType1}" Value="True">
<Setter Property="Background" Value="Bisque"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Margin="10,38,0,0" Text="Content Template:"/>
<RadioButton x:Name="radio1" Margin="120,40,0,0" Grid.ColumnSpan="2" Content="1" GroupName="ViewSelect" IsChecked="{Binding Path=ViewType1}"/>
<RadioButton Margin="170,40,0,0" Grid.ColumnSpan="2" Content="2" GroupName="ViewSelect"/>
<TabControl Grid.Row="1" ItemsSource="{Binding TabGroup}">
<TabControl.Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Margin" Value="10"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType1}" Value="True">
<Setter Property="ContentTemplate" Value="{DynamicResource ContentTemplate1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ViewType1}" Value="False">
<Setter Property="ContentTemplate" Value="{DynamicResource ContentTemplate2}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.ItemTemplate>
<DataTemplate>
<Border x:Name="headerBorder">
<Label Content="{Binding MyHeader}" FontSize="20"/>
</Border>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
Code Behind
namespace ChangeView
{
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window, INotifyPropertyChanged
{
public ObservableCollection<TabData> TabGroup { get; set; } = new ObservableCollection<TabData>();
private bool _viewType1 = true;
public bool ViewType1
{
get { return _viewType1; }
set { _viewType1 = value; RaisePropertyChanged(nameof(ViewType1)); }
}
public Window1()
{
TabGroup.Add(new TabData("♻️", "Recycle"));
TabGroup.Add(new TabData("⚔", "Swords"));
TabGroup.Add(new TabData("⚗", "Chemistry"));
TabGroup.Add(new TabData("🌵", "Cactus"));
TabGroup.Add(new TabData("👺", "Tengu"));
TabGroup.Add(new TabData("🐙", "Octopus"));
DataContext = this;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class TabData : INotifyPropertyChanged
{
private string _myHeader, _myBlurb;
public TabData(string header, string blurb)
{
MyHeader = header;
MyBlurb = blurb;
}
public string MyHeader
{
get { return _myHeader; }
set { _myHeader = value; RaisePropertyChanged(nameof(MyHeader)); }
}
public string MyBlurb
{
get { return _myBlurb; }
set { _myBlurb = value; RaisePropertyChanged(nameof(MyBlurb)); }
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
After changing the radio button state, change the selected tab. You will then see the correct content template.
It looks as if, in a TabControl, changing the content template alone does not cause the content to be rendered. If you render new content by switching the selected tab, the current content template will then be used.
So let's write one ContentTemplate, which creates a ContentControl and switches the ContentControl's ContentTemplate. I've tested, and the ContentControl will re-render its content when its ContentTemplate changes. The bindings get a little bit verbose.
<TabControl ItemsSource="{Binding TabGroup}" Grid.Row="1">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl
x:Name="ContentCtl"
Content="{Binding}"
/>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding DataContext.ViewType1, RelativeSource={RelativeSource AncestorType=TabControl}}"
Value="True">
<Setter
TargetName="ContentCtl"
Property="ContentTemplate"
Value="{DynamicResource ContentTemplate1}"
/>
</DataTrigger>
<DataTrigger
Binding="{Binding DataContext.ViewType1, RelativeSource={RelativeSource AncestorType=TabControl}}"
Value="False"
>
<Setter
TargetName="ContentCtl"
Property="ContentTemplate"
Value="{DynamicResource ContentTemplate2}"
/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemTemplate>
<DataTemplate>
<Border x:Name="headerBorder">
<Label Content="{Binding MyHeader}" FontSize="20"/>
</Border>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
You could also do something ugly in your code behind to make the TabControl render itself again on command. Or maybe you can replace the metadata on TabControl.ContentTemplate.