UniformGrid with DataTemplate trigger not working as expected, WPF - wpf

I host a list of 64 UserControl in an ItemsControl, the DataContext is an array of objects. Then the DataContext for the individual instance of the UserControl becomes the instance of the object.
The objects have a boolean variable called Exists, this is a DataTemplate trigger to determine if the Usercontrol will be displayed or not.
I use a Uniformgrid to display the list, but I'm experiencing some weird behavior. The Usercontrol don't resize. See attached picture. If I use a StackPanel instead, it works just fine. But I would like to use the UnifromGrid instead.
Here is the code - Only 4 objects have the Exist variable set to true.
<Grid Grid.Row="1" Grid.Column="1" x:Name="gridSome" Background="#FF5AC1F1">
<Viewbox>
<ItemsControl ItemsSource="{Binding SomeVM.SomeModel.SomeArray}"
Margin="15" HorizontalAlignment="Center" VerticalContentAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<tensioner:UCView Margin="5"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Exists}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--<StackPanel IsItemsHost="true"/> This works-->
<UniformGrid Columns="1"/> <!-- This does not work-->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Viewbox>
</Grid>
-----Update------
//SSCCE
MainWindow
<Window x:Class="WpfAppItemIssue.MainWindow"
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"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppItemIssue"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<!--<Viewbox>-->
<ItemsControl ItemsSource="{Binding Model.Cars}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="ABC"></TextBox>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding exists}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<!--</Viewbox>-->
</Grid>
</Window>
MainViewModel
using System.ComponentModel;
namespace WpfAppItemIssue
{
class MainViewModel:INotifyPropertyChanged
{
public MainViewModel()
{
Model = new MainModel();
}
private MainModel model;
public MainModel Model
{
get
{
return model;
}
set
{
model = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
Model
namespace WpfAppItemIssue
{
class MainModel
{
public Car[] Cars { get; set; }
public MainModel()
{
Cars = new Car[64];
for (int i = 0; i < Cars.Length; i++)
{
Cars[i] = new Car(i);
}
}
}
internal class Car
{
public int someVal { get; set; }
public bool exists { get; set; }
public Car(int someVal)
{
this.someVal = someVal;
if (someVal < 5) //Just enable few items for debug
{
exists = true;
}
else
{
exists = false;
}
}
}
}
See attached images :
Picture 1 shows Design View. Why are the user controls not being resized?
Picture 2 shows On Execute. Why are the user controls not being resized?
Picture 3 shows On Any resize event. The Controls are being resized correctly.

Well I finally got your problem after discussion in comments. It is all about DataTrigger in your ItemTemplate. Just move it to ItemContainerStyle Triggers and elements will be resized correctly.
<ItemsControl ItemsSource="{Binding Model.Cars}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<DataTrigger Binding="{Binding exists}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="ABC"></TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note that TextBox'es will be resized "by border" only (this behavior is shown on your last picture), font size will not be changed. If you want to scale your elements uniformly with their content you really need to wrap ItemsControl to Viewbox.

This is not a weird behavior, but it's the way the UniformGrid works.
As an ItemsPanel of the ItemsControl, the UniformGrid uses the ItemSource collection to determine the row count and the column count. It doesn't matter whether the items that will be placed in the UniformGrid are visible or not - all the grid cells have the same width and height. So your DataTrigger has no effect on the layout of the UniformGrid, it only affects the visibility of the items.
The StackPanel works in a different way. There are no cells, the StackPanel arranges all the visible items in such a way that they occupy the available space.

Related

MouseDragElementBehavior in DataTemplate

So here's an issue that I'm having. I'm trying to use MouseDragElementBehavior in listbox. I was able to make it work when I was creating items in listbox directly, as in this example:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<Border Width="20" Height="20">
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</i:Interaction.Behaviors>
<Rectangle Fill="Red"/>
</Border>
</ItemsControl.Items>
</ItemsControl>
But as soon as I've started using DataTemplate, it stopped working.
<ItemsControl Grid.Column="1" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
Test item
</ItemsControl.Items>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="20" Height="20">
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</i:Interaction.Behaviors>
<Rectangle Fill="Red"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Any ideas as to why? I can't really figure out how a DataTemplate would affect MouseDragElementBehavior.
The MouseDragElementBehavior acts upon the FrameworkElement you attach it to. In your case it is the Border element which will be contained by a ContentPresenter which is the container generated by the ItemsControl. You have set ConstrainToParentBounds="True" which will ensure the visual will not be displayed outside its container, in this case the ContentPresenter. There are a few options, some easy, one probably not worth undertaking (but I did to figure some stuff out).
Set ConstrainToParentBounds="False". I am supposing that you don't want the Border to leave the ItemsControl so this probably won't suit.
Set the ItemContainerStyle to a Style which sets the Template to and adds the interaction to a similarly configured ContentPresenter. The base implementation of the ItemsControl uses a vanilla ContentPresenter. A caveat here is that if you aren't using UI elements as items you will need to wrap the item in one using a custom items control (see this answer on setting the container style):
<ItemsControl Grid.Column="1" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<ContentPresenter>
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</i:Interaction.Behaviors>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style >
</ItemsControl.ItemContainerStyle>
<ItemsControl.Items>
Test item
</ItemsControl.Items>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="20" Height="20">
<Rectangle Fill="Red"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Attach the interaction using the ItemsControl.ItemContainerStyle. This is a little involved because the Interaction.Behaviors attached property only has a setter:
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="beh:AddCollectionsToSetter.Behaviors">
<Setter.Value>
<beh:BehaviorCollection>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</beh:BehaviorCollection>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
For this I had to create a separate attached property AddCollectionsToSetter.Behaviors which is read/write and a BehaviorCollection that allows the interactions to be added to.
public static class AddCollectionsToSetter
{
#region Behaviors Dependency Property (Attached)
/// <summary>Gets the behaviours to add.</summary>
public static BehaviorCollection GetBehaviors(DependencyObject obj)
{
return (BehaviorCollection)obj.GetValue(BehaviorsProperty);
}
/// <summary>Sets the behaviours to add.</summary>
public static void SetBehaviors(DependencyObject obj, BehaviorCollection value)
{
obj.SetValue(AddCollectionsToSetter.BehaviorsProperty, value);
}
/// <summary>DependencyProperty backing store for <see cref="Behaviors"/>. Represents the behaviours to add.</summary>
/// <remarks></remarks>
public static readonly DependencyProperty BehaviorsProperty =
DependencyProperty.RegisterAttached("Behaviors", typeof(BehaviorCollection), typeof(AddCollectionsToSetter), new PropertyMetadata(null, BehaviorsPropertyChanged));
private static void BehaviorsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var oldBehaviors = (BehaviorCollection)e.OldValue;
var newBehaviors = (BehaviorCollection)e.NewValue;
var interaction = Interaction.GetBehaviors(d);
interaction.RemoveRange(oldBehaviors); // extension method, simple iterate and remove
interaction.AddRange(newBehaviors.Clone()); // extension method, simple iterate and add
}
#endregion Behaviors Dependency Property (Attached)
}
public class BehaviorCollection : FreezableCollection<System.Windows.Interactivity.Behavior>
{
public BehaviorCollection()
: base()
{
}
public BehaviorCollection(int capacity)
: base(capacity)
{
}
public BehaviorCollection(IEnumerable<System.Windows.Interactivity.Behavior> behaviors)
: base(behaviors)
{
}
}

Events in Canvas works only for last element

I use ListView to display list of Buttons. While using StackPanel or WrapPanel as ItemsPanel of the ListView, everything works fine. When I try Canvas only the last one button from a collection is working, others doesn't even change their background when mouse is over them. Can I make all them work normally?
<ListView ItemsSource="{Binding collection1}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Button Margin="{Binding margin}" Width="40" Height="40" Click=button_click />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
code behind:
public class object1
{
public double x0 { get; set; }
public double y0 { get; set; }
public Thickness margin { get { return new Thickness(x0, y0, 0, 0); } }
public object1(double x, double y)
{
x0= x;
y0 = y;
}
}
public MainWindow : Window
{
public ObservableCollection<object1> collection1 {get;set;}
public MainWindow()
{
InitializeComponent();
DataContext = this;
collection1=new ObservableCollection<object1>();
collection1.Add(new object1(20,20));
collection1.Add(new object1(20,80));
collection1.Add(new object1(80,20));
collection1.Add(new object1(80,80)); // ONLY THAT ONE WORKS
}
}
The problem is the way in which you do the positioning of the Buttons with a Margin inside the ListViewItem. All the ListViewItems lie on top of each other in the Canvas, and only the topmost gets input events.
Move the Margin binding out of the ItemTemplate into an ItemContainerStyle, so that it sets the Margin of a ListViewItem instead of a Button:
<ListView>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Margin" Value="{Binding margin}"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Button Width="40" Height="40" .../>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When using a Canvas as ItemsPanel I'd strongly suggest to use the Canvas.Left and Canvas.Top properties for positioning item containers. You would usually bind them to properties of the data item type, like your x0 and y0:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Canvas.Left" Value="{Binding x0}"/>
<Setter Property="Canvas.Top" Value="{Binding y0}"/>
</Style>
</ListView.ItemContainerStyle>

How to loop through ItemsControl in WPF?

How can I loop through this ItemsControl and change it's TextBlock background in this Xaml's code behind page on some mouse event. I am new to WPF.
<ItemsControl ItemsSource="{Binding Path= HeaderList}" Name="Headers">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Name="Data" Text="{Binding }" Width="100" HorizontalAlignment="Left" PreviewMouseLeftButtonDown="MouseLeftButtonDown_Handler"
MouseEnter="MouseEnter_Handler" MouseLeave="MouseLeave_Handler">
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Thanks in advance!!
Actually my requirement is to change individual TextBlock's background color on different mouse events. So i need to get access of TextBlock in code behind and depending upon login I can change that Textblock's background color accordingly. So i think need to iterate ItemsControl. in case if I bind Background Property then all on property change would have effect on all the Textblocks in that ItemsControl. I don't want it in this way. I want to set and change every individual textblock's color differently.
I have access to single one in the eventhandlers that caused that event, but I want to access all the textblocks that are in itemscontrol and change their color acoording to some logic
Solution with background binding like axelle suggested:
You can iterate through the items in the HeaderList and set the background-property.
The Header class must implement the INotifyPropertyChanged Interface
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Path=HeaderList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Background="{Binding Background}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public partial class MainWindow : Window
{
public class Header : NotificationObject
{
public string Text { get; set; }
public Brush Background { get; set; }
}
public IList<Header> HeaderList { get; set; }
public MainWindow()
{
HeaderList = new List<Header>
{
new Header {Text = "header1", Background = Brushes.Red},
new Header {Text = "header2", Background = Brushes.Blue},
new Header {Text = "header3", Background = Brushes.Chartreuse},
};
DataContext = this;
InitializeComponent();
}
}
If I understand your question correctly, you'd want to bind the TextBlock background to a value in your datacontext, and change that value on your mouse event.
don't loop through the itemscontrol, better use a Trigger to apply the changes to your textblock :)
<ItemsControl ItemsSource="{Binding Path= HeaderList}" Name="Headers">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>

Change control in a DataGridRow programmatically

I have a WPF DataGrid contains a list of Products with an image button in the last column to add the Product item into the Orders_Products collection. That works.
Now I'd like to change the "add" image button with a "delete" image button if the Product item is already in the Orders_Products collection.
I tried to use the LoadingRow event but it seems there's no way to access the Image object because is not ready yet in the LoadingRow event...
I tried the Load event of the Image object, but it's not fired if the row is not visible in the form (when I must scroll the datagrid to see that row). It fires when I sort a column and the row is directly visible in the form. I'm going crazy... :(
I think I'm not doing something unusual but I'm new to WPF and probably I miss something.
Can someone give me an hint?
Thanks in advance,
Joe
P.S.: I'm using Entity Framework.
The proper way of doing this is by binding the DataGrid to the collection of objects you want to display (you are probably doing this already).
Then, manually define a DataGridTemplateColumn and set its CellTemplate to an appropriate DataTemplate (this would usually be defined as a resource in your DataGrid or somewhere higher in the logical tree of elements).
You can see an example of how the above is done here.
Inside the DataTemplate, use the technique described in my answer to this question to vary what is displayed in the template by matching the value of an appropriate property in the databound Product.
All of this can be done entirely in XAML, as is the preferred way of doing things in WPF.
Update (working example)
The Product:
public class Product
{
public string Name { get; set; }
public bool Exists { get; set; }
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
this.Products = new List<Product>
{
new Product { Name = "Existing product", Exists = true },
new Product { Name = "This one does not exist", Exists = false },
};
InitializeComponent();
this.DataContext = this;
}
public IEnumerable<Product> Products { get; set; }
}
MainWindow.xaml:
<Window x:Class="SandboxWPF.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">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ButtonColumnTemplate" >
<ContentControl x:Name="MyContentControl" Content="{Binding}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Exists}" Value="True">
<Setter TargetName="MyContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Your Remove product button goes here" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Exists}" Value="False">
<Setter TargetName="MyContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Your Add product button goes here" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<DataGrid ItemsSource="{Binding Products}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Product Name" Binding="{Binding Name}" />
<DataGridTemplateColumn Header="Add/Remove" CellTemplate="{StaticResource ButtonColumnTemplate}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

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