Set ContentPresenter ContentTemplate on value change in generated field - wpf

I have am attempting to build a tree view where:
1. The TreeViewItems are generated by a list in my model.
2. Each TreeViewItem contains a ComboBox, and a dynamic element whose template I want to change based on the value selected in the ComboBox.
Here is my current xaml code.
<Window x:Class="MyTestWPF.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:MyTestWPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:NodeTypeToTemplateConverter x:Key="NodeTypeToTemplateConverter"/>
<DataTemplate x:Key="Template1">
<TextBlock Text="Template 1" />
</DataTemplate>
<DataTemplate x:Key="Template2">
<TextBlock Text="Template 2" />
</DataTemplate>
<Style x:Key="MyNodeTemplate" TargetType="ContentPresenter">
<Setter Property="ContentTemplate" Value="{StaticResource Template1}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}">
<DataTrigger.Value>
<local:NodeTypesEnum>Type1</local:NodeTypesEnum>
</DataTrigger.Value>
<Setter Property="ContentTemplate" Value="{Binding Converter={StaticResource NodeTypeToTemplateConverter}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:MyTreeNode}"
ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=GetAvailableNodeType}"
SelectedItem="{Binding Path=NodeType}" />
<ContentPresenter Style="{StaticResource MyNodeTemplate}" Content="{Binding}" />
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView x:Name="MyTree" ItemsSource="{Binding MyTreeModel}" />
</Window>
And its code-behind:
using System.Windows;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new
{
MyTreeModel = new MyTreeNode[] {
new MyTreeNode() { Name = "1", Nodes = new MyTreeNode[] { new MyTreeNode() { Name= "2" } } }
}
};
}
}
The tree node type:
namespace MyTestWPF
{
public class MyTreeNode
{
public string Name { get; set; }
public NodeTypesEnum NodeType { get; set; }
public MyTreeNode[] Nodes { get; set; }
public NodeTypesEnum[] GetAvailableNodeType()
{
return new NodeTypesEnum[] { NodeTypesEnum.Type1, NodeTypesEnum.Type2 };
}
}
public enum NodeTypesEnum
{
Type1 = 0,
Type2 = 1
}
}
The Converter (NodeTypeToTemplateConverter) receives the whole ViewModel, and returns the name of the relevant template based on values in the model.
using System;
using System.Globalization;
using System.Windows.Data;
namespace MyTestWPF
{
public class NodeTypeToTemplateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if((value as MyTreeNode).NodeType == NodeTypesEnum.Type1)
{
return "Template1";
} else
{
return "Template2";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The problem is that the above code causes a stack overflow exception. The first item in the TreeView endlessly calls NodeTypeToTemplateConverter's Convert method.
I figured it had to do with the DataTrigger.Value. Setting that to a value different from the default NodeType allows the page to load without overflow, but the moment any ComboBox is set to NodeType1, stack overflow.
I attempted to simply remove the DataTrigger.Value element, but that causes the Converter to never be called at all...
How can I dynamically build the template name based on the value selected by its neighboring ComboBox?

You probably want to use a DataTemplateSelector rather than a converter.
public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
public DataTemplate Template1 { get; set; }
public DataTemplate Template2 { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//Logic to select template based on 'item' value.
if (item == <template1Value>) return Template1; //TODO: replace <template1Value>
else if (item == <template2Value>) return Template2; //TODO: replace <template2Value>
else return new DataTemplate();
}
}
<local:ComboBoxItemTemplateSelector x:Key="ComboBoxItemTemplateSelector">
<local:ComboBoxItemTemplateSelector.Template1>
<DataTemplate>
<TextBlock Text="" />
</DataTemplate>
</local:ComboBoxItemTemplateSelector.Template1>
<local:ComboBoxItemTemplateSelector.Template2>
<DataTemplate>
<TextBlock Text="" />
</DataTemplate>
</local:ComboBoxItemTemplateSelector.Template2>
</local:ComboBoxItemTemplateSelector>
<ContentPresenter Content="{Binding NodeType}" ContentTemplateSelector="{StaticResource ComboBoxItemTemplateSelector}"/>
I have not fully tested this code, so let me know if you have any issues.
EDIT:
The template selector is only executed when the content changes so this won't work if you use {Binding}. A workaround for this would be to have the DataTemplate content bind to the parent's DataContext.
<DataTemplate>
<TextBlock Text="" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContentPresenter}}"/>
</DataTemplate>
If this workaround is not acceptable, there are other ways to do this as well.

Related

WPF, How to change the properties (visibility.. ) of dynamically created UI components on load

I want to change the properties of the dynamically populated UI components.
Here is my sample UI. The label and three textboxes created dynamically.
I want to change the visibility of the third textbox if the label content is R11.
and add a combo box with the static resource if the label is R12
Here is my sample code
my main screen XAML
<StackPanel>
<control:MethodControl></control:MethodControl>
<ContentControl Content="{Binding ChildViewModel}" />
</StackPanel>
MainViewModel
class MethodViewModel : ViewModelBase
{
#region Properties
private Method _method;
private PropertyViewModel _childViewModel;
#endregion
#region Getter & Setters
public PropertyViewModel ChildViewModel
{
get { return this._childViewModel; }
set
{
if (this._childViewModel != value)
{
this._childViewModel = value;
RaisePropertyChanged(() => ChildViewModel);
}
}
}
public Method Method
{
get { return _method; }
}
public ICommand UpdateCommand
{
get; private set;
}
#endregion
#region Constructor
/// <summary>
/// Initialize a new interface of the MEthodViewModel class
/// </summary>
public MethodViewModel()
{
//test
_method = new Method();
PropertyViewModel pwm = new PropertyViewModel();
pwm.CollectProperties(_method.Name, _method.Helper);
ChildViewModel = pwm;
UpdateCommand = new UpdateCommand(SaveChanges, () => string.IsNullOrEmpty(_method.Error));
}
#endregion
#region Functions
public void SaveChanges()
{
PropertyViewModel pwm = new PropertyViewModel();
pwm.CollectProperties(_method.Name, _method.Helper);
ChildViewModel = pwm;
}
Child View Model
class PropertyViewModel : ViewModelBase
{
private ObservableCollection<Property> _properties;
public ObservableCollection<Property> Properties
{
get { return _properties; }
}
public PropertyViewModel(string method, string reflection)
{
_properties = new ObservableCollection<Property>();
CollectProperties(method, reflection);
}
public void CollectProperties(string method, string reflection)
{
_properties.Clear();
int methodindex = Array.IndexOf((String[])Application.Current.Resources["MethodNames"], method);
switch (methodindex)
{
case 0:
foreach (String prop in (String[])Application.Current.Resources["Result1"])
{
PopulateProperty(prop, true);
}
break;
default:
foreach (String prop in (String[])Application.Current.Resources["Result2"])
{
PopulateProperty(prop, true);
}
break;
}
}
public PropertyViewModel()
{
_properties = new ObservableCollection<Property>();
}
private void PopulateProperty(string prop, bool p1)
{
Property temp = new Property(prop, "", 0, "");
_properties.Add(temp);
}
}
ChildViewModel XAML
<StackPanel >
<ItemsControl ItemsSource = "{Binding Properties}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<Label Content="{Binding Name}"></Label>
<TextBox Text = "{Binding Path, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding StdDev, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Unit, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
and my resources
<x:Array x:Key="MethodNames" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>MM1</sys:String>
<sys:String>MM2</sys:String>
<sys:String>MM3</sys:String>
</x:Array>
<x:Array x:Key="HelperMethods" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>HM1</sys:String>
<sys:String>HM2</sys:String>
<sys:String>HM3</sys:String>
</x:Array>
<x:Array x:Key="Result1" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>R11</sys:String>
<sys:String>R12</sys:String>
<sys:String>R13</sys:String>
</x:Array>
<x:Array x:Key="Result2" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>R21</sys:String>
<sys:String>R22</sys:String>
<sys:String>R23</sys:String>
</x:Array>
<DataTemplate DataType="{x:Type modelViews:PropertyViewModel}">
<control:PropertyControl />
</DataTemplate>
part of the UI is changing with the selections of the combo boxes.
I need whole 3 text boxes for some options, only 1 for some and 1 textbox and 1 combo box for some options depends on the label name.
How can I add this property to dynamically populated user control?
You could add one or several triggers to the DataTemplate, e.g.:
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<Label Content="{Binding Name}"></Label>
<TextBox Text = "{Binding Path, Mode=TwoWay}" Width = "100" Margin="3 5 3 5"/>
<TextBox Text="{Binding StdDev, Mode=TwoWay}" Width="100" Margin="3 5 3 5"/>
<TextBox x:Name="third" Text="{Binding Unit, Mode=TwoWay}" Width="100" Margin="3 5 3 5"/>
<ComboBox x:Name="combo" Visibility="Collapsed" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Name}" Value="R11">
<Setter TargetName="third" Property="Visibility" Value="Collapsed" />
<Setter TargetName="combo" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
I find an easy solution to my question, so let me add it.
I add visibility in the properties and bind the textbox visibility to it.
here is the bool the visibility converter
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
 
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the conversion from visibility to bool
}
}
Resource
<converter:BoolToVisibilityConverter x:Key="converter"></converter:BoolToVisibilityConverter>
text box
<TextBox Visibility="{Binding HasUnit,
Converter={StaticResource converter}}" />
 

How to search for a wpf list box item value and highlight the index based on the value?

In my application I would like you highlight an index based on the value. for example:
ArrayList itemsList = new ArrayList();
private void button_Click(object sender, RoutedEventArgs e)
{
itemsList.Add("Coffie");
itemsList.Add("Tea");
itemsList.Add("Orange Juice");
itemsList.Add("Milk");
itemsList.Add("Mango Shake");
itemsList.Add("Iced Tea");
itemsList.Add("Soda");
itemsList.Add("Water");
listBox.ItemsSource = itemsList;
ApplyDataBinding();
}
private void ApplyDataBinding()
{
listBox.ItemsSource = null;
listBox.ItemsSource = itemsList;
}
It does not matter where in the listbox "Orange Juice" is I would like to highlight it based on its value. If the Position change it should be still highlighted. (Not based on the selected index)
If you want to highlight an item based on it's value, then you need to define your own datatemplate for an item and use a converter to provide appropriate brush for the background. Something like that:
<Window.Resources>
<local:TextToBrushConverter x:Key="TextToBrushConverter" />
</Window.Resources>
<Grid>
<ListBox Name="listBox" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Background="{Binding ., Converter={StaticResource TextToBrushConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Converter
class TextToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value as String) == "Orange Juice")
{
return Brushes.Orange;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I would recommend that you place the items into a class, think Object Oriented design and then work off of properties (as flags) to give different states.
Then by using Xaml styles to key off those different properties to achieve the affect you are looking for.
Say for example we have an Order class with these properties
public class Order
{
public string CustomerName { get; set; }
public int OrderId { get; set; }
public bool InProgress { get; set; }
}
When an order is marked as in progress (InProgress = true) we want to show red in our list box say for "Alpha" and "Omega" which are in progress:
ListBox Xaml
Here is the Xaml which binds to our data (how you bind is up to you) and shows how to work with Style(s), DataTemplate, and DataTrigger(s) to achieve that:
<ListBox ItemsSource="{StaticResource Orders}"
x:Name="lbOrders">
<ListBox.Resources>
<DataTemplate DataType="{x:Type model:Order}">
<TextBlock Text="{Binding Path=CustomerName}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=InProgress}"
Value="True">
<Setter Property="Foreground"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
Here is the data setup in the pages's resource Xaml to do that, but it could be created in code behind:
<Window.Resources>
<model:Orders x:Key="Orders">
<model:Order CustomerName="Alpha"
OrderId="997"
InProgress="True" />
<model:Order CustomerName="Beta"
OrderId="998"
InProgress="False" />
<model:Order CustomerName="Omega"
OrderId="999"
InProgress="True" />
<model:Order CustomerName="Zeta"
OrderId="1000"
InProgress="False" />
</model:Orders>
</Window.Resources>
This should give you enough to start on and create a full featured UI.

System.Windows.Data Error: 40 : BindingExpression path error

I have 2 object types which display on tabs within the same tab. I am using a DataTrigger and a DataTypeConverter to display the content. The content displays correctly but I receive the following errors in the output window:
System.Windows.Data Error: 40 : BindingExpression path error: 'Taste' property not found on 'object' ''MySmellObject' (HashCode=30266853)'. BindingExpression:Path=Taste; DataItem='MySmellObject' (HashCode=30266853); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'Smell' property not found on 'object' ''MyTasteObject' (HashCode=36404074)'. BindingExpression:Path=Smell; DataItem='MyTasteObject' (HashCode=36404074); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
Here's the XAML:
<Window x:Class="ControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ControlTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:DataTypeConverter x:Key="DataTypeConverter" />
</Window.Resources>
<StackPanel Orientation="Vertical" Width="150">
<TabControl Name="tab" Height="200px"
ItemsSource="{Binding MyObjects}"
SelectedValuePath="Id"
SelectedItem="{Binding MyObject}"
>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=tab,Path=SelectedItem, Converter={StaticResource DataTypeConverter}}" Value="{x:Type local:MyTasteObject}">
<Setter Property="Content">
<Setter.Value>
<TextBox Text="{Binding Taste}" ></TextBox>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=tab,Path=SelectedItem, Converter={StaticResource DataTypeConverter}}" Value="{x:Type local:MySmellObject}">
<Setter Property="Content">
<Setter.Value>
<TextBox Text="{Binding Smell}" ></TextBox>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
</Window>
Here's the code behind:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace ControlTest
{
public partial class MainWindow : Window
{
public List<MyObject> MyObjects { get; set; }
public int MyObjectId { get; set; }
public MyObject MyObject { get; set; }
public MainWindow()
{
MyObject obj0 = new MySmellObject() { Id = 0, Smell = "Pleasent" };
MyObject obj1 = new MyTasteObject() { Id = 1, Taste = "Mild" };
MyObjects = new List<MyObject> { obj0, obj1 };
MyObjectId = 0;
MyObject = obj0;
DataContext = this;
InitializeComponent();
}
}
public class MyObject
{
public int Id { get; set; }
}
public class MyTasteObject : MyObject
{
public string Taste { get; set; }
public override string ToString()
{
return Taste;
}
}
public class MySmellObject : MyObject
{
public String Smell { get; set; }
public override string ToString()
{
return Smell;
}
}
public class DataTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
return value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Does anyone know why I'm getting these errors and how to fix the code?
The problem here is the ContentTemplate is applied against all items in the TabControl. Initially it's applied only on the SelectedItem. But after switching to the new item, it's then applied to all loaded items.
When the SelectedItem is changed, the ContentTemplate with a ContentControl having new Content updated, this is then applied on all items. But because the underlying items are of different concrete types (deriving from the same base type). So once the template is applied on item of mismatched type, the Binding error will be reported silently.
In fact we have a better way to achieve what you want without using Triggers at all. That's what the so-called DataTemplate provides you. Just set the DataType to the item type, then it will be automatically replaced with the corresponding template, something like this:
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{TemplateBinding Content}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:MySmellObject}">
<TextBox Text="{Binding Smell}" ></TextBox>
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyTasteObject}">
<TextBox Text="{Binding Taste}" ></TextBox>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>

WPF Combobox, bind a collection<items> where item has a bool property isSelected?

I have a List< Versions> where Version, amongst others has the properties VersionUUID, Label, SKU and IsSelected. I would like to bind this to a Combobox and have the selected item just select the IsSelected flag (unselected any previous set flag).
Note:The combobox is in a template, used inside a datagrid cell, so I can not just bind a SelectedItem to the model!
What I have so far is working, the datagrid updates the DB as expected, however the initial value is not set onLoad. If one version already has a IsSelected=true, I would like to have that showing int the Combobox, but it is always empty unless i Select one from the list.
<DataTemplate x:Key="dtDatagridVersionSelector">
<ComboBox Margin="0" Width="90" Style="{StaticResource DatagridComboBox}"
ItemsSource="{Binding Path=Versions, Mode=OneTime}">
<ComboBox.ItemTemplate >
<DataTemplate >
<RadioButton Focusable="false" IsEnabled="true"
GroupName="{Binding VersionUUID}"
IsChecked="{Binding IsSelected, Mode=TwoWay}">
<StackPanel Orientation="Horizontal" >
<TextBlock Margin="3,0,0,0" Text="{Binding Label}"/>
<TextBlock Foreground="Red" Margin="3,0,0,0"
Text="{Binding SKU}"/>
</StackPanel>
</RadioButton>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=OneWay}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
Also, the use of the Radiobox is not written in stone, if there is a better solution to achieve this so only one item isSelected, I'm all open for it
Thanx for any pointers
Andreas
<Window x:Class="WpfApplication1.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.Resources>
<local:SelectedItemConverter x:Key="selectedItemConverter"/>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Students}" SelectedItem="{Binding Students, Converter={StaticResource selectedItemConverter}}" DisplayMemberPath="Name"/>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Students = new ObservableCollection<Student>();
Students.Add(new Student() { Name = "HArish", RollNo = 1, IsSelected = false });
Students.Add(new Student() { Name = "Arev", RollNo = 2, IsSelected = false });
Students.Add(new Student() { Name = "Pankaj", RollNo = 3, IsSelected = true });
Students.Add(new Student() { Name = "Deepak", RollNo = 4, IsSelected = false });
DataContext = this;
}
public ObservableCollection<Student> Students { get; set; }
}
public class Student
{
public string Name { get; set; }
public int RollNo { get; set; }
public bool IsSelected { get; set; }
}
public class SelectedItemConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value is IEnumerable<Student>)
return ((IEnumerable<Student>)value).Where(s => s.IsSelected).FirstOrDefault();
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
I hope this will help.

How can a property be bound to a type

I have two HeaderedContentControls like those below that each have their content property bound to one of two view model properties of the same base type (one control is on the left side of the window and one on the right, thus the view model property names).
However, either view model property can be one of four different derived types. So the left could be an Airplane and the right can be a Car. Then later, the left could be a Boat and right could be an Airplane. I would like the Style property of the header controls to be dynamic based on the derived type. What's the best way to do this declaratively?
<Window...>
<StackPanel
Grid.Row="2"
Orientation="Horizontal" VerticalAlignment="Top">
<Border
Height="380"
Width="330"
Margin="0,0,4,0"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=LeftChild}"
Header="{Binding LeftChild.DisplayName}"
Style="{StaticResource StandardHeaderStyle}"
/>
</Border>
<Border
Height="380"
Width="330"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=RightChild}"
Header="{Binding RightChild.DisplayName}"
Style="{StaticResource StandardHeaderStyle}"
/>
</Border>
</StackPanel>
</Window>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:myViewModelNamespace;assembly=myViewModelAssembly"
xmlns:vw="clr-namespace:myViewNamespace" >
<!--***** Item Data Templates ****-->
<DataTemplate DataType="{x:Type vm:CarViewModel}">
<vw:CarView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:BoatViewModel}">
<vw:BoatView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:AirplaneViewModel}">
<vw:AirplaneView />
</DataTemplate>
<!--*****
Other stuff including the StandardHeaderStyle and the MainBorderStyle
****-->
</ResourceDictionary>
Are you sure you need to vary HeaderedContentControl's Style, not the ContentTemplate basing on Content's dynamic type? In other words: do you need to vary the control's style or you just need to vary the item's data-template?
Because there is very handy property ContentTemplateSelector and if you'll write very simple class you'll be able to select the DataTemplate basing on content's dynamic type.
If that's not the case and you are sure you need to vary the Style, then could you please elaborate a little which parts of the style you'd like to vary - maybe there a workaround through the same ContentTemplateSelector is available.
In case you insist on varying the styles, think a little about using data trigger inside your style - using a very simple converter you'll be able to vary certain properties (or all of them if you prefer) of your style.
I'll be glad to provide you further assistance as soon as you'll elaborate the specifics of your problem.
UPD: OK, author insists that he need to vary the Style. Here are two possible ways of how you can do that.
First and simple solution, but severely limited one: since your Header content can be specified through Content content you can do this:
<DataTemplate x:Key="DefaultTemplate">
<HeaderedContentControl Content="{Binding}"
Header="{Binding DisplayName}"
Style="{StaticResource DefaultStyle}" />
</DataTemplate>
<DataTemplate x:Key="CarTemplate"
DataType="dm:Car">
<HeaderedContentControl Content="{Binding}"
Header="{Binding DisplayName}"
Style="{StaticResource CarStyle}" />
</DataTemplate>
<DataTemplate x:Key="BoatTemplate"
DataType="dm:Boat">
<HeaderedContentControl Content="{Binding}"
Header="{Binding DisplayName}"
Style="{StaticResource BoatStyle}" />
</DataTemplate>
<u:TypeBasedDataTemplateSelector x:Key="MySelector"
DefaultTemplate="{StaticResource DefaultTemplate}"
NullTemplate="{StaticResource DefaultTemplate}">
<u:TypeMapping Type="dm:Car" Template="{StaticResource CarTemplate}" />
<u:TypeMapping Type="dm:Boat" Template="{StaticResource BoatTemplate}" />
</u:TypeBasedDataTemplateSelector>
<ContentPresenter Content="{Binding LeftChild}"
ContentTemplateSelector="{StaticResource MySelector}" />
The only code you'll need to back this purely declarative solution is a very simple template selector implementation. Here it goes:
public class TypeMapping
{
public Type Type { get; set; }
public DataTemplate Template { get; set; }
}
public class TypeBasedDataTemplateSelector : DataTemplateSelector, IAddChild
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate NullTemplate { get; set; }
private readonly Dictionary<Type, DataTemplate> Mapping = new Dictionary<Type, DataTemplate>();
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
return NullTemplate;
DataTemplate template;
if (!Mapping.TryGetValue(item.GetType(), out template))
template = DefaultTemplate;
return template;
}
#region IAddChild Members
public void AddChild(object value)
{
if (!(value is TypeMapping))
throw new Exception("...");
var tm = (TypeMapping)value;
Mapping.Add(tm.Type, tm.Template);
}
public void AddText(string text)
{
throw new NotImplementedException();
}
#endregion
}
The second solution is more generic and can be applied to the cases where Header content has nothing to do with Content content. It bases on the Binding's converter capabilities.
<Style x:Key="StandardHeaderedStyle">
<!--...-->
</Style>
<Style x:Key="CarHeaderedStyle"
BasedOn="{StaticResource StandardHeaderedStyle}">
<!--...-->
</Style>
<Style x:Key="BoatHeaderedStyle"
BasedOn="{StaticResource StandardHeaderedStyle}">
<!--...-->
</Style>
<Style x:Key="UnknownHeaderedStyle"
BasedOn="{StaticResource StandardHeaderedStyle}">
<!--...-->
</Style>
<u:StylesMap x:Key="MyStylesMap"
FallbackStyle="{StaticResource UnknownHeaderedStyle}">
<u:StyleMapping Type="Car" Style="{StaticResource CarHeaderedStyle}" />
<u:StyleMapping Type="Boat" Style="{StaticResource BoatHeaderedStyle}" />
</u:StylesMap>
<u:StyleSelectorConverter x:Key="StyleSelectorConverter" />
<HeaderedContentControl Content="{Binding LeftChild}"
Header="{Binding LeftChild.DisplayName}">
<HeaderedContentControl.Style>
<Binding Path="LeftChild"
Converter="{StaticResource StyleSelectorConverter}"
ConverterParameter="{StaticResource MyStylesMap}" />
</HeaderedContentControl.Style>
</HeaderedContentControl>
It also requires some of backing code:
public class StyleMapping
{
public Type Type { get; set; }
public Style Style { get; set; }
}
public class StylesMap : Dictionary<Type, Style>, IAddChild
{
public Style FallbackStyle { get; set; }
#region IAddChild Members
public void AddChild(object value)
{
if (!(value is StyleMapping))
throw new InvalidOperationException("...");
var m = (StyleMapping)value;
this.Add(m.Type, m.Style);
}
public void AddText(string text)
{
throw new NotImplementedException();
}
#endregion
}
public class StyleSelectorConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var m = (StylesMap)parameter;
if (value == null)
return m.FallbackStyle;
Style style;
if (!m.TryGetValue(value.GetType(), out style))
style = m.FallbackStyle;
return style;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
HTH
My answer is an elaboration on Archimed's. Don't hesitate to ask further!
<Window x:Class="Datatemplate_selector.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:Datatemplate_selector">
<Window.Resources>
<DataTemplate DataType="{x:Type local:CarDetail}">
<Border BorderBrush="Yellow" BorderThickness="2">
<HeaderedContentControl Margin="4" Foreground="Red">
<HeaderedContentControl.Header>
<Border BorderBrush="Aquamarine" BorderThickness="3">
<TextBlock Text="{Binding Name}"/>
</Border>
</HeaderedContentControl.Header>
<HeaderedContentControl.Content>
<Border BorderBrush="CadetBlue" BorderThickness="1">
<TextBlock TextWrapping="Wrap" Text="{Binding Description}"/>
</Border>
</HeaderedContentControl.Content>
</HeaderedContentControl>
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type local:HouseDetail}">
<HeaderedContentControl Margin="4" Foreground="Yellow" FontSize="20"
Header="{Binding Name}">
<HeaderedContentControl.Content>
<TextBlock Foreground="BurlyWood" TextWrapping="Wrap"
Text="{Binding Description}"/>
</HeaderedContentControl.Content>
</HeaderedContentControl>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ItemDetail}">
<HeaderedContentControl Margin="4" Foreground="Green" FontStyle="Italic"
Content="{Binding Description}"
Header="{Binding Name}">
</HeaderedContentControl>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding ItemDetails}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
using System.Collections.ObjectModel;
using System.Windows;
namespace Datatemplate_selector
{
public partial class Window1 : Window
{
public ObservableCollection<ItemDetail> ItemDetails { get; set; }
public Window1()
{
ItemDetails = new ObservableCollection<ItemDetail>
{
new CarDetail{Name="Trabant"},
new HouseDetail{Name="Taj Mahal"}
};
DataContext = this;
InitializeComponent();
}
}
public class ItemDetail:DependencyObject
{
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name",
typeof(string),
typeof(ItemDetail),
new UIPropertyMetadata(string.Empty));
public virtual string Description
{
get { return Name + " has a lot of details"; }
}
}
public class CarDetail:ItemDetail
{
public override string Description
{
get { return string.Format("The car {0} has two doors and a max speed of 90 kms/hr", Name); }
}
}
public class HouseDetail:ItemDetail
{
public override string Description
{
get { return string.Format("The house {0} has two doors and a backyard", Name); }
}
}
}
PS: I thought that this use of inheritance in a generic collection was not supported in .Net 3. I am pleasurably surprised that this code works!
try using the Style Selector class:
http://msdn.microsoft.com/en-us/library/system.windows.controls.styleselector.aspx
I haven't used it myself specifically, so i don't have any sample code for you to check out, but the MSDN link has some.

Resources