WPF DataGridTemplateColumn, bind to generic CellTemplate - wpf

I'm very new to WPF so sorry if this is obvious but I can't seem to find any decent examples on the internet showing how it's done.
I have a DataGrid which is bound to a collection of DataItem called MyCollection. I want to create a generic DataTemplate that I can use for multiple columns in the grid (and elsewhere in the application should I need it).
E.g.
<DataGrid ItemsSource="{Binding MyCollection}" AutoGenerateColumns="False" SelectionUnit="Cell" EnableColumnVirtualization="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="File path" CellTemplate="{StaticResource FileSelectorEditorTemplate}" CellEditingTemplate="{StaticResource FileSelectorEditorTemplate}" />
<DataGridTemplateColumn Header="File path2" CellTemplate="{StaticResource FileSelectorEditorTemplate}" CellEditingTemplate="{StaticResource FileSelectorEditorTemplate}" />
<DataGridTemplateColumn Header="File path3" CellTemplate="{StaticResource FileSelectorEditorTemplate}" CellEditingTemplate="{StaticResource FileSelectorEditorTemplate}" />
...
My DataTemplate is defined at the moment in my Application resources as
<DataTemplate x:Key="FileSelectorEditorTemplate">
<Grid>
<TextBox Text="{Binding FilePath.PhysicalPath}" HorizontalAlignment="Stretch" Margin="0,0,35,0" />
<Button Content="..." Height="25" Width="25" Margin="0,0,5,0" HorizontalAlignment="Right" Click="FileOpen_Click" />
</Grid>
</DataTemplate>
Now the problem is that the binding is specified in the DataTemplate, whereas I need to apply a different binding for each of the properties FilePath, FilePath2, FilePath3 on the view model. I don't seem to be able to specify the Binding on the DataGridTemplateColumn though?
I'd appreciate any pointers in the right direction,
Thanks!

The binding on the DataGridTemplateColumn is specified in its CellTemplate.
If you want different bindings for the three columns, I'd say you would have to make a different DataTemplate for each of the columns. There might be some workaround, but I doubt it would be pretty.
Edit:
Having different templates you can use a DataTemplateSelector to select the right template for the current object.
Using IValueConverter (Just a quick sketch, but should work):
<DataTemplate x:Key="GenericTemplate" >
<TextBlock FontSize="14" >
<TextBlock.Text>
<Binding Converter="{StaticResource NewValue}" Path="Me" />
</TextBlock.Text>
</TextBlock>
</DataTemplate>
public class NewValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
someContainer obj = value as someContainer;
if (obj.type == MyType.First)
return (string)(obj.val1);
else
return (string)(obj.val2);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public enum MyType
{
First,
Second
}
public class someContainer
{
public someContainer Me { get; set; }
public string val1 { get; set; }
public string val2 { get; set; }
public MyType type;
public someContainer()
{
Me = this;
val1 = "string1";
val2 = "string2";
}
}
...
public ObservableCollection<someContainer> myList {get; set;}
...
<StackPanel Margin="0,10,0,0" Orientation="Vertical" Grid.Column="2">
<ItemsControl ItemsSource="{Binding MyList}" ItemTemplate="{StaticResource GenericTemplate}" />
</StackPanel>

If you can't use the option of Jesper Gaarsdal you might also be able to use a CellStyle and define the binding in the column-declaration.
see this SO for example: How to reuse WPF DataGridTemplateColumn (including binding)

Related

How can I dynamically fill a ComboBox with XAML in WPF with the values of a List?

In a CSV file I have some string values separated by ';' and some lists (values separated by ','). After reading the CSV I want to display the list as a combobox.
How can I achieve this using WPF/XAML?
I tried this:
<ComboBoxItem>
<StackPanel Orientation="Horizontal">
<TextBlock Name="DetailParams" Text="{Binding Path=DetailParams, Converter={StaticResource StringListConverter}}" />
</StackPanel>
</ComboBoxItem>
...but this generates a combobox item '(Collection)' (in German: '(Sammlung')
To be more precisely:
The ComboBox shall be displayed in a DataGrid's column for each row, whereby each row shows an entity of a class XYZ.
The values of the ComboBox do come from somewhere else but not from XYZ. Anyway: The selected values of the ComboBox shall be finally stored in a property of XYZ (and when the DataGrid/ComboBox is shown, the items shall be preselected in the ComboBox).
MainWindow.cs:
private List<string> _tagNames = new List<string>();
public IList<string> TagNames { get { return _tagNames; } }
<Window.Resources>
<CollectionViewSource x:Key="NotificationsCollectionViewSource" CollectionViewType="ListCollectionView"/>
<lb:StringListConverter x:Key="StringListConverter" />
</Window.Resources>
<DataGrid x:Name="notificationsGrid" DataContext="{StaticResource NotificationsCollectionViewSource}" ItemsSource="{Binding}"
AlternatingRowBackground="LightBlue" AutoGenerateColumns="False" CanUserAddRows="True" IsReadOnly="False"
SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn Header="Tag name" Binding="{Binding Mode=TwoWay, Path=TagName}"></DataGridTextColumn>
<DataGridTextColumn Header="NId" Binding="{Binding Mode=TwoWay, Path=Nid}"></DataGridTextColumn>
<DataGridCheckBoxColumn Header="IsActive" Binding="{Binding Mode=TwoWay, Path=IsActive}"></DataGridCheckBoxColumn>
<DataGridTemplateColumn x:Name="detailsParamColumn" Header="Edit">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=TagNames, Converter={StaticResource StringListConverter}}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
StringListConverter.cs:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Data;
namespace ConfigTool.DataBinding
{
public class StringListConverter : IValueConverter
{
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString().Split(',').ToList();
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
StringBuilder sb = new StringBuilder();
List<string> items = value as List<string>;
for( int i=0; i<items.Count; i++ )
{
sb.Append(items[i].Trim());
if( i<items.Count-1 )
{
sb.Append(",");
}
}
return sb.ToString();
}
}
}
Because I want to write back the selected values to the CSV file:
Should the Binding Mode be TwoWay?
UPDATE:
In the DataGrid for the notifications I want to have a ComboBox with all possible values for a tag name. These tag names are in a List<string> in my MainWindow.cs but this list (named _tagNames) is neither part of the NotificationViewSource nor the underlying Notification class. A notification may have one or more tag names assigned but in the ComboBox ALL possible tag names (from property _tagNames) shall be shown and able to select. ...and the list of selected items shall be written back later on.
Because _tagNames is just a List of strings I can't set something like Binding Path=whatever...
UPDATE (2018-12-14) :
Once more (because maybe my English is not so good that I have explaned good enough what ny intentiin is ;-) ): I have a class Notification
pulic class Notification
{
public List<string> _mySelectedTagNames { get; set; }
}
And in the MainWindow.cs:
public partial cass MainWindow : Window
{
private List<string> _tagNames; // initialized with maybe 100 tagNames (all possible values from which a subset (or even all) can be selected and hence stored in `Notification.mySelectedTagnames`)
}
MainWindow._tagNames shall be displayed in a ComboBox which is shown in one column of each row.
The user may select zero, on or more items and these selected items shall be stored in the rows underlying Notification class.
Set (or bind) the ItemsSource property of the ComboBox to the list of strings:
<ComboBox ItemsSource="{Binding Path=DetailParams, Converter={StaticResource StringListConverter}}" />
This should give you a ComboBox with an item per entry in the list returned by the converter.
If you want to display the items next to each other in a horizontal StackPanel, you could use an ItemsControl:
<ItemsControl ItemsSource="{Binding Path=DetailParams, Converter={StaticResource StringListConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Path=., Mode=OneWay}" />
<Run Text=" " />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Using the same DataTemplate for two different properties of the same object in two DataGridTemplateColumns

I've got two data templates in the resources and a DataTemplateSelector to choose between them:
<DataGrid.Resources>
<DataTemplate x:Key="RedTemplate">
<TextBlock Text="{Binding **Name1OrName2**}" />
</DataTemplate >
<DataTemplate x:Key="GreenTemplate">
....
</DataTemplate>
<local:MyTemplateSelector x:Key="MyTemplateSelector"
RedTemplate="{StaticResource RedTemplate}"
GreenTemplate="{StaticResource GreenTemplate}" />
</DataGrid.Resources>
Here is the code-behind of the selector:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate RedTemplate { get; set; }
public DataTemplate GreenTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is RedItem) return RedTemplate;
else if (item is GreenItem) return GreenTemplate;
else return base.SelectTemplate(item, container);
}
}
As long as I use MyTemplateSelector for one DataColumn (say, Name1), it works fine. But my DataGrid has two template columns to be bound to two string fields: Name1 and Name2
<DataGridTemplateColumn CellTemplateSelector="{StaticResource MyTemplateSelector}" > // Should be bound to Name1
<DataGridTemplateColumn CellTemplateSelector="{StaticResource MyTemplateSelector}" > // Should be bound to Name2
My question is: how can I set the proper Path (Name1 or Name2) in the Binding (instead of Name1OrName2, see above). Thank you.
It looks like my original answer was due to a misunderstanding of the question, and the requirement isn't about the data template selector, but rather how to parameterize the property a binding binds to, so you can use the same template for two different properties.
Quick answer: That's not the way XAML is designed to be used. You can't parameterize the Path property of a Binding. The conventional solution is to write one template for each case. It would be nice if you could specify which property/field a DataGridTemplateColumn is meant to display, via a Binding or DisplayMemberPath property, and then passed that value to the template -- but it doesn't work that way.
I found a likely-looking workaround here, but I'm not sure the ROI on it would stack up well relative to copying and pasting a DataTemplate and getting on with your life.
If the templates are complicated enough for maintenance to be a concern, you can work around that like so:
XAML resources:
<DataTemplate x:Key="RedBaseTemplate">
<Border BorderBrush="Green" BorderThickness="2" Margin="1">
<Label x:Name="Text" Background="Red" Content="{Binding}" />
</Border>
</DataTemplate>
<DataTemplate x:Key="GreenBaseTemplate">
<Border BorderBrush="Red" BorderThickness="2" Margin="1">
<Label x:Name="Text" Background="Green" Content="{Binding}" />
</Border>
</DataTemplate>
<DataTemplate x:Key="RedTemplateA">
<ContentControl
Content="{Binding A}"
ContentTemplate="{StaticResource RedBaseTemplate}"
/>
</DataTemplate>
<DataTemplate x:Key="RedTemplateB">
<ContentControl
Content="{Binding B}"
ContentTemplate="{StaticResource RedBaseTemplate}"
/>
</DataTemplate>
<DataTemplate x:Key="GreenTemplateA">
<ContentControl
Content="{Binding A}"
ContentTemplate="{StaticResource GreenBaseTemplate}"
/>
</DataTemplate>
<DataTemplate x:Key="GreenTemplateB">
<ContentControl
Content="{Binding B}"
ContentTemplate="{StaticResource GreenBaseTemplate}"
/>
</DataTemplate>
Original Answer
This is a common pattern: You want multiple instances of the same DataTemplateSelector (or value converter, quite often), but with different parameters. The solution is to derive from MarkupExtension, so you can instantiate the thing at the point of use with its own unique parameters, rather than creating one shared instance someplace else as a resource. In this case DataTemplateSelector is a class rather than an interface, so you can't derive your selector from MarkupExtension. Instead you write a quick MarkupExtension that returns your selector.
I wanted to pass the templates themselves to RedGreenTemplateSelectorExtension using StaticResource or DynamicResource in the XAML, but the XAML parser didn't like the idea. But this works well enough.
public class RedGreenTemplateSelectorExtension : MarkupExtension
{
public Object RedTemplateKey { get; set; }
public Object GreenTemplateKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var redTemplate = new StaticResourceExtension(RedTemplateKey)
.ProvideValue(serviceProvider) as DataTemplate;
var greenTemplate = new StaticResourceExtension(GreenTemplateKey)
.ProvideValue(serviceProvider) as DataTemplate;
return new RedGreenTemplateSelector() {
RedTemplate = redTemplate,
GreenTemplate = greenTemplate
};
}
}
public class RedGreenTemplateSelector : DataTemplateSelector
{
public DataTemplate RedTemplate { get; set; }
public DataTemplate GreenTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is RedItem)
return RedTemplate;
else if (item is GreenItem)
return GreenTemplate;
else
return base.SelectTemplate(item, container);
}
}
XAML
<StackPanel>
<ContentControl
ContentTemplateSelector="{local:RedGreenTemplateSelector RedTemplateKey=RedTemplate, GreenTemplateKey=GreenTemplate}"
>
<local:RedItem/>
</ContentControl>
<ContentControl
ContentTemplateSelector="{local:RedGreenTemplateSelector RedTemplateKey=RedTemplate, GreenTemplateKey=GreenTemplate}"
>
<local:GreenItem/>
</ContentControl>
</StackPanel>
P.S. StaticResource and Binding are two very different classes that do very different things. People misuse "binding" to mean "assignment". It's not. You aren't using any bindings at all here.

Master-detail data binding

I have two models in my application:
class Line
{
string line_id { get; set; }
string color { get; set; }
}
class Point
{
string point_id { get; set; }
string line_id { get; set; }
int weight { get; set; }
}
And I have two ObservableCollection:
lines - ObservableCollection<Line>
points - ObservableCollection<Point>
I want to display two ListBox'es : first (outer) to display lines, and second (inner) to display the points which belongs to this line.
<ListView x:Name="lvPoint" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding color, Mode=TwoWay}" />
<ListBox ItemsSource="{Binding SOMETHING, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding weight, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I set DataContext for outer ListBox in the code:
lvPoint.DataContext = lines;
How can I set DataContext for inner ListBox to display Points for each Line ?
Your Line model is not good for this scenario. It should have a property such as called Points which contains the interested points belonging to the Line. Then the Binding is just simple:
class Line {
public string line_id { get; set; }
public string color { get; set; }
ObservableCollection<Point> _points;
public ObservableCollection<Point> Points {
get {
if (_points == null) _points = new ObservableCollection<Point>();
return _points;
}
}
}
Then in XAML code you can just set the Path to Points like this:
<ListBox ItemsSource="{Binding Points, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding weight, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The model above is just an example showing the main idea. The full implementation should of course be different and more advanced depending on the current project.
Update: Without using the model above, you can try using Converter for the Binding. The Binding is set directly to the current item (Line). The Converter will convert Line to points (maybe based on line_id and your query method):
public class LineToPointsConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture){
var line = value as Line;
//convert to points by querying the points based on line.line_id here
return ...
}
public object ConvertBack(object value, Type targetType,
object parameter,
System.Globalization.CultureInfo culture){
return Binding.DoNothing;
}
}
Define a static property of LineToPointsConverter or create an instance of that converter in Resources:
<Window.Resources>
<local:LineToPointsConverter x:Key="lineToPointsConverter"/>
</Window.Resources>
Then in XAML code set that converter:
<ListBox ItemsSource="{Binding Mode=TwoWay, Path=.,
Converter={StaticResource lineToPointsConverter}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding weight, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Bind ListBox to SelectionChanged ComboBox in XAML

I want to bind the selection change of the combo box to update my list box, but my xaml code is probably wrong.
This is my Collection that takes the data from the Service.
public class WorkersCollection
{
private WorkerClient client = new WorkerClient();
public ObservableCollection<Worker> Workers { get; set; }
public WorkersCollection()
{
Workers = new ObservableCollection<Worker>();
}
public ICollection<Worker> GetAllWorkers()
{
foreach (var worker in client.GetAllWorkers())
{
Workers.Add(worker);
}
return Workers;
}
}
My DataContext is workers:
public partial class MainWindow : Window
{
WorkersCollection workers;
public MainWindow()
{
InitializeComponent();
workers = new WorkersCollection();
this.DataContext = workers;
workers.GetAllWorkers();
}
}
and in XAML:
<ComboBox Name="cbxWorkers" HorizontalContentAlignment="Right" SelectedItem="{Binding Workers}" ItemsSource="{Binding Workers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ComboBoxItem Content="{Binding LastName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox Grid.Row="3" ItemTemplate="{StaticResource WorkersTemplate}" ItemsSource="{Binding ElementName=cbxWorkers, Path=SelectedItem}" />
How can I fix it?
ItemsSource property of class ListBox has type IEnumerable (msdn).
So you can't assign to it object of type Worker.
You can create converter to do that.
Converter class:
public class WorkerToListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new List<Worker> { value as Worker };
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML code:
...
<Window.Resources>
<local:WorkerToListConverter x:Key="myCon" />
</Window.Resources>
...
<ComboBox Name="cbxWorkers" HorizontalContentAlignment="Right" ItemsSource="{Binding Workers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ComboBoxItem Content="{Binding LastName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox Grid.Row="3" ItemTemplate="{StaticResource WorkersTemplate}"
ItemsSource="{Binding ElementName=cbxWorkers, Path=SelectedItem, Converter={StaticResource myCon}}" />
...
You should also delete SelectedItem binding from ComboBox.
... SelectedItem="{Binding Workers}" ItemsSource="{Binding Workers}" ...
It's no sense to binding SelectedItem to the same thing as ItemsSource.

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