ContentPresenter's Binding not updated when bound object is updated - wpf

I have a master view and two subviews. I would like to switch from SubViewA to SubViewB when clicking on the button on SubViewA. The masterview contains a contentpresenter which is binded to View, and initialized to SubViewB when loaded. When clicking on the button on SubViewA the SubViewB constructor is called, but the control is never loaded. What am I missing? I've also tried by just setting the contenttemplate:
<ContentPresenter x:Name="contentPresenter" Content="{Binding View, PresentationTraceSources.TraceLevel=High}" />
which does not work either.
MainWindow:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:WpfApplication2">
<Grid>
<TextBlock Text="MasterViewPage" />
<ContentControl x:Name="content" Content="{Binding View}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:SubViewModelA}">
<local:SubViewA></local:SubViewA>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SubViewModelB}">
<local:SubViewB></local:SubViewB>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
public partial class MainWindow
{
public MainWindow()
{
Loaded += MainWindow_Loaded;
InitializeComponent();
}
private void MainWindow_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
DataContext = new MainViewModel();
}
}
SubViewA:
<UserControl x:Class="WpfApplication2.SubViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Margin="0,40,0,0">
<TextBlock Text="Subview A" />
<Button Height="50" Width="120" Content="Open View B" Command="{Binding OpenViewCommand}" />
</Grid>
</UserControl>
public partial class SubViewA
{
public SubViewA()
{
Loaded += SubViewA_Loaded;
InitializeComponent();
}
private void SubViewA_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
DataContext = new SubViewModelA();
}
}
ViewModels:
public class MainViewModel : NotifyPropertyChanged
{
private object _view;
public object View
{
get { return _view; }
set
{
_view = value;
RaisePropertyChanged(() => View);
}
}
public MainViewModel()
{
View = new SubViewA();
}
}
public class SubViewModelA : MainViewModel
{
public ICommand OpenViewCommand
{
get { return new DelegatingCommand(OpenView); }
}
private void OpenView()
{
View = new SubViewB();
}
}
public class SubViewModelB : MainViewModel
{
}
Thanks in advance.

View-models should not contain references to the view, instead have a property ViewMode which can be an enum and trigger on that, here is an example (you can set the ContentTemplate instead of Content as well).

OK, the solution that worked for me was:
MainView.xaml:
<ContentControl x:Name="content">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding View, PresentationTraceSources.TraceLevel=High}" Value="SubViewA">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:SubViewA />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding View, PresentationTraceSources.TraceLevel=High}" Value="SubViewB">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:SubViewB />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
and in SubViewA.xaml:
<Button Height="50" Width="120" Content="Open View B" Command="{Binding Path=DataContext.OpenViewCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}, PresentationTraceSources.TraceLevel=High}" />
Reason was that View wasn't set on MainViewModel, but on the subview instead (which inherited from MainViewModel). When removing the inheritance to actually set the view on MainViewModel everything worked.

I can agree on that it's not good to set the view in model directly. But anyway, I tried your solution (both of them) and SubViewB is still not loaded. The constructor is called, but never SubViewB_Loaded. So, the result is that the SubViewB is never shown.
The datacontextchanged is never triggered on the contentcontrol. So, still, I'm missing something.
The main view:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:WpfApplication2">
<Grid>
<TextBlock Text="MasterViewPage" />
<ContentControl x:Name="content">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding View}" Value="SubViewA">
<Setter Property="Content">
<Setter.Value>
<local:SubViewA />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding View}" Value="SubViewB">
<Setter Property="Content">
<Setter.Value>
<local:SubViewB />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</Window>
And in the viewmodel:
public class MainViewModel : NotifyPropertyChanged
{
private string _view;
public string View
{
get { return _view; }
set
{
_view = value;
RaisePropertyChanged(() => View);
}
}
public MainViewModel()
{
View = "SubViewA";
}
}
public class SubViewModelA : MainViewModel
{
public ICommand OpenViewCommand { get { return new DelegatingCommand(OpenView); } }
private void OpenView()
{
View = "SubViewB";
}
}
public class SubViewModelB : MainViewModel
{
}

Related

How to binding different data context to data template?

I try to simplify my Main user control that contains 8 user controls that are exactly the same but they are binding to different VM to display its data.
Currently, I have to create a template for each of my user control and binding to each of VM.
It seems that I can create one data template for all 8 user controls and apply the data template to each of the user control with different instance of VM.
Here are my code that current I have to use different templates for different dependency of View Model containing the data of each gauge
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue1VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue2VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO1Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
<Grid Background="#FFE3E2D7" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge2">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO2Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Here I try to create one Data template for all 8 user controls but it does not work
<DataTemplate x:Key="AnalogIOTemplate" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl Content="{Binding Path=GaugeValue1VMDP}" x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIOTemplate}" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Is there a way to binding different data context to the data template?
Thanks
If class of UserControl, class of VM and bindings between them are identical and the only difference is instances of VM, creating a Style for UserControl and binding each instance of VM with DataContext of corresponding instance of UserControl would be enough.
Since we don't know actual code of your UserControl and VM, I will show this by samples.
Sample UserControl which has Id dependency property and can show its value:
<UserControl x:Class="WpfApp.SampleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock x:Name="IdTextBlock"/>
</StackPanel>
</UserControl>
public partial class SampleUserControl : UserControl
{
public int Id
{
get { return (int)GetValue(IdProperty); }
set { SetValue(IdProperty, value); }
}
public static readonly DependencyProperty IdProperty =
DependencyProperty.Register("Id", typeof(int), typeof(SampleUserControl),
new PropertyMetadata(0, (d, e) => ((SampleUserControl)d).IdTextBlock.Text = e.NewValue.ToString()));
public SampleUserControl()
{
InitializeComponent();
}
}
Sample VM which has Id property and MainWindow's VM which has instances of sample VM:
// using Microsoft.Toolkit.Mvvm.ComponentModel;
public class SampleViewModel : ObservableObject
{
private int _id;
public int Id
{
get => _id;
set => SetProperty(ref _id, value);
}
}
public class MainWindowViewModel : ObservableObject
{
public SampleViewModel? VM1 { get; }
public SampleViewModel? VM2 { get; }
public MainWindowViewModel()
{
VM1 = new SampleViewModel { Id = 10 };
VM2 = new SampleViewModel { Id = 20 };
}
}
Finally, bind each instance of sample VM with DataContext of corresponding instance of sample UserControl so that Id of sample VM is bound with Id of sample UserControl.
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MainWindow"
Width="400" Height="200">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type local:SampleUserControl}">
<Setter Property="Id" Value="{Binding Id}"/>
</Style>
</Window.Resources>
<StackPanel>
<local:SampleUserControl DataContext="{Binding VM1}"/>
<local:SampleUserControl DataContext="{Binding VM2}"/>
</StackPanel>
</Window>

wpf how to get value from textbook in ControlTemplate treeivwitem

how to get txt_add.text value?
this style applied to TreeViewitem in code behind
<Page.Resources>
<Style TargetType="{x:Type TreeViewItem}" x:Key="add" >
<Setter Property="Background" Value="{DynamicResource WhiteBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem" >
<StackPanel Orientation="Horizontal" Margin="5">
<TextBox Width="300" Controls:TextBoxHelper.Watermark="Account Name" Margin="2" x:Name="txt_add"/>
<Button Content="{x:Static lang:ResLang.insert}" Style="{StaticResource ButtonSystem}" Width="100" Margin="2" Click="Button_AddNewSubOk_Click"/>
<Button Content="{x:Static lang:ResLang.btn_cancel}" Style="{StaticResource ButtonCancel }" Width="100" Margin="2" Click="Button_AddNewSubCancel_Click"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
</Page>
You need to bind it using mvvm pattern, create a viewmodel class which inherits inotifypropertychanged then bind your text to property in that class.
<Window.DataContext>
<model:viewmodel x:Key="viewmodel"/>
</Window.DataContext>
<!-- where ever you got your textbox -->
<TextBox Text="{Binding Mode=TwoWay,Source={StaticResource viewmodel},Path=stringproperty"/>
and simple viewmodel class :
public class viewmodel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public String stringproperty{ get; set; } ;
}
there is simple implementation of "property changed" in web if this is not working for you.
and you can access it like this but this is wrong you should'nt do this
(this.DataContext as viewmodel).stringproperty
after binding only use bindings to access your data , if you need them in some actions or events pass as parameter to "command" you could search about that

WPF Switching Views through Command Button

I'm quite new to WPF and what I'm trying to do is switch views through different command buttons.. this is my current code...
Basically I have two buttons in my MainView, SetupLan and SetupSerialPort.
For simplicity, I'm currently trying to make SetupSerialPort working first. But when I click the button for it, the command executes but the view doesn't change...
App.xaml
<Application.Resources>
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SetupLanViewModel}">
<Views:SetupLanView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SetupSerialPortViewModel}">
<Views:SetupSerialPortView />
</DataTemplate>
</Application.Resources>
MainWindow.xaml
<Grid>
<ContentControl Content="{Binding}" />
</Grid>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
MainViewModel
public DisplaySetupSerialPortCommand SetupSerialPort
{
get; set;
}
public MainViewModel()
{
SetupSerialPort = new DisplaySetupSerialPortCommand(this);
}
MainView
<StackPanel>
<Button Height="50" Width="150" HorizontalAlignment="Left"Command="{Binding SetupLan}">Lan</Button>
<Button Height="50" Width="150" HorizontalAlignment="Left" Command="{Binding SetupSerialPort}">Serial Port</Button>
</StackPanel>
DisplaySetupSerialPortCommand
class DisplaySetupSerialPortCommand : ICommand
{
private BaseViewModel _baseViewModel;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public DisplaySetupSerialPortCommand(BaseViewModel baseViewModel)
{
_baseViewModel = baseViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_baseViewModel.ViewModel = new SetupSerialPortViewModel();
}
}
A few things.
Your command is not doing anything at all, it's simply just creating a ViewModel. You need to add additional logic for this to work.
If you want to switch out the DataTemplate to different views, you will need to implement some type of Trigger.
First, update your DataTemplates to have an associated key.
<Application.Resources>
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}" x:Key="MainView">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SetupLanViewModel}" x:Key="Lan">
<Views:SetupLanView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SetupSerialPortViewModel}" x:Key="SerialPort">
<Views:SetupSerialPortView />
</DataTemplate>
</Application.Resources>
By adding a key, it will allow you to reference each one, where/when needed.
Next, inside your MainWindow.xaml, this is where the Triggers will occur.
Update your ContentControl to Trigger and switch it's DataTemplate based on some condition. In this case, the condition I created is called ViewType.
<Grid>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource MainView}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType}" Value="SerialPort">
<Setter Property="ContentTemplate" Value="{StaticResource SerialPort}" />
</DataTrigger>
<DataTrigger Binding="{Binding ViewType}" Value="Lan">
<Setter Property="ContentTemplate" Value="{StaticResource Lan}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Lastly, you will need to have a ViewType property, and set that property based on the specific command that was executed. Add to ViewType property your BaseViewModel since the command has a reference to this.
public string ViewType
{
get { return _viewType; }
set
{
_viewType = value;
RaisePropertyChanged();
}
}
Now, you can change the ViewType whenever the command is clicked.
public void Execute(object parameter)
{
_baseViewModel.ViewType = "SerialPort";
_baseViewModel.ViewModel = new SetupSerialPortViewModel();
}
Note: I used a string for a ViewType type just for demo purposes only. You would probably want to use an enumeration instead.

XAML to add header to radio button

So with a lot of looking around I am hoping to make a GroupBox that acts like a Radio button. The header section would act as the bullet. I took some code from this question
Styling a GroupBox
that is how I want it to look. But I want to have it as a Radio button. So I put in this code (mind you I've only been doing WPF for a week or 2 now)
<Style TargetType="{x:Type RadioButton}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<BulletDecorator>
<BulletDecorator.Bullet>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border x:Name="SelectedBorder"
Grid.Row="0"
Margin="4"
BorderBrush="Black"
BorderThickness="1"
Background="#25A0DA">
<Label x:Name="SelectedLabel" Foreground="Wheat">
<ContentPresenter Margin="4" />
</Label>
</Border>
<Border>
</Border>
</Grid>
</BulletDecorator.Bullet>
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="SelectedBorder" Property="Background" Value="PaleGreen"/>
<Setter TargetName="SelectedLabel"
Property="Foreground"
Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have a feeling that I can add a label to the second row of my grid, but then I don't know how to access it. I have that template in a test project in the Window.Resources section (I plan on moving it to a resource dictionary in my main project)
the xaml for my window is this
<Grid>
<GroupBox Name="grpDoor" Margin ="8" Grid.Row="0" Grid.Column="0">
<GroupBox.Header>
WPF RadioButton Template
</GroupBox.Header>
<StackPanel Margin ="8">
<RadioButton FontSize="15" Content="Dhaka" Margin="4" IsChecked="False"/>
<RadioButton FontSize="15" Content="Munshiganj" Margin="4" IsChecked="True" />
<RadioButton FontSize="15" Content="Gazipur" Margin="4" IsChecked="False" />
</StackPanel>
</GroupBox>
</Grid>
I then hoping for something like this (not sure how I'd do it yet though)
<Grid>
<GroupBox Name="grpDoor" Margin ="8" Grid.Row="0" Grid.Column="0">
<GroupBox.Header>
WPF RadioButton Template
</GroupBox.Header>
<StackPanel Margin ="8">
<RadioButton FontSize="15"
Content="Dhaka"
Margin="4"
IsChecked="False">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
<RadioButton FontSize="15"
Content="Munshiganj"
Margin="4"
IsChecked="True">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
<RadioButton FontSize="15"
Content="Gazipur"
Margin="4"
IsChecked="False">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
</StackPanel>
</GroupBox>
</Grid>
Based on your clarification, here is a very simple example with a RadioButton that looks like a GroupBox.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:SimpleViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:SimpleOption}">
<RadioButton GroupName="choice" IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<RadioButton.Template>
<ControlTemplate TargetType="{x:Type RadioButton}">
<GroupBox x:Name="OptionBox" Header="{Binding Path=DisplayName, Mode=OneWay}">
<TextBlock Text="{Binding Path=Description, Mode=OneWay}"/>
</GroupBox>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, Mode=OneWay}" Value="True">
<Setter TargetName="OptionBox" Property="Background" Value="Blue"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=Options, Mode=OneWay}"/>
</Grid>
</Window>
public class SimpleViewModel
{
public SimpleViewModel()
{
Options = new ObservableCollection<SimpleOption>();
var _with1 = Options;
_with1.Add(new SimpleOption {
DisplayName = "Dhaka",
Description = "This is a description for Dhaka."
});
_with1.Add(new SimpleOption {
DisplayName = "Munshiganj",
Description = "This is a description for Munshiganj.",
IsSelected = true
});
_with1.Add(new SimpleOption {
DisplayName = "Gazipur",
Description = "This is a description for Gazipur."
});
}
public ObservableCollection<SimpleOption> Options { get; set; }
}
public class SimpleOption : INotifyPropertyChanged
{
public string DisplayName {
get { return _displayName; }
set {
_displayName = value;
NotifyPropertyChanged("DisplayName");
}
}
private string _displayName;
public string Description {
get { return _description; }
set {
_description = value;
NotifyPropertyChanged("Description");
}
}
private string _description;
public bool IsSelected {
get { return _isSelected; }
set {
_isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
private bool _isSelected;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
}
I'd do it with a custom attached property. That way, you can bind to it from a ViewModel, or apply it directly in XAML.
First, create a new class in your Style assembly:
public static class RadioButtonExtender
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.RegisterAttached(
"Description",
typeof(string),
typeof(RadioButtonExtender),
new FrameworkPropertyMetadata(null));
[AttachedPropertyBrowsableForType(typeof(RadioButton))]
public static string GetDescription(RadioButton obj)
{
return (string)obj.GetValue(DescriptionProperty);
}
public static void SetDescription(RadioButton obj, string value)
{
obj.SetValue(DescriptionProperty, value);
}
}
And your style's Bullet would change so that the label is:
<Label x:Name="SelectedLabel"
Foreground="Wheat"
Content="{Binding (prop:RadioButtonExtender.Description), RelativeSource={RelativeSource TemplatedParent}} />
You could then use it in your final XAML:
<RadioButton FontSize="15"
Content="Dhaka"
Margin="4"
IsChecked="False">
<prop:RadioButtonExtender.Description>
This is a description that would show under my Header
</prop:RadioButtonExtender.Description>
</RadioButton>
As an added bonus, since you're creating the Style in a separate assembly, you can create a custom XAML namespace to make using your property easier.

ContentTemplateSelector is only called one time showing always the same datatemplate

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

Resources