I've got a collection of ViewModels and want to bind a ListBox to them. Doing some investigation I found this.
So my ViewModel look like this (Pseudo Code)
interface IItemViewModel
{
string DisplayName { get; }
}
class AViewModel : IItemViewModel
{
string DisplayName { return "foo"; }
}
class BViewModel : IItemViewModel
{
string DisplayName { return "foo"; }
}
class ParentViewModel
{
IEnumerable<IItemViewModel> Items
{
get
{
return new IItemViewModel[] {
new AItemViewModel(),
new BItemViewModel()
}
}
}
}
class GroupViewModel
{
static readonly GroupViewModel GroupA = new GroupViewModel(0);
static readonly GroupViewModel GroupB = new GroupViewModel(1);
int GroupIndex;
GroupViewModel(int groupIndex)
{
this.GroupIndex = groupIndex;
}
string DisplayName
{
get { return "This is group " + GroupIndex.ToString(); }
}
}
class ItemGroupTypeConverter : IValueConverter
{
object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is AItemViewModel)
return GroupViewModel.GroupA;
else
return GroupViewModel.GroupB;
}
}
And this XAML
<UserControl.Resources>
<vm:ItemsGroupTypeConverter x:Key="ItemsGroupTypeConverter "/>
<CollectionViewSource x:Key="GroupedItems" Source="{Binding Items}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription Converter="{StaticResource ItemsGroupTypeConverter }"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource GroupedItems}}">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" FontWeight="bold" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This works somehow, exept of the fact that the binding of the HeaderTemplate does not work. Anyhow I'd prefer omitting the TypeConverter and the CollectionViewSource. Isn't there a way to use a property of the ViewModel for Grouping?
I know that in this sample scenario it would be easy to replace the GroupViewModel with a string an have it working, but that's not an option. So how can I bind the HeaderTemplate to the GroupViewModel?
Ok, I finally solved it myself.
First of all the TypeConverter is unnecessary, because the PropertyGroupDescription can be bound on a PropertyName. So I added a Property 'Group' to IItemViewModel and modified XAML as follows:
<UserControl.Resources>
<CollectionViewSource x:Key="GroupedItems" Source="{Binding Items}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Group"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
Furthermore the HeaderTemplate's DataContext is a CollectionViewGroupInternal and the binding has to look like this:
<DataTemplate>
<TextBlock Text="{Binding Name.DisplayName}" FontWeight="bold" />
</DataTemplate>
Here CollectionViewGroupInternal.Name resolves the actual GroupViewModel.
Related
I need to add a default value 'select' in the combobox.I cant add this value to the database.This location value is dynamic.It appears based upon the userrole. I tried different ways nothing worked.Please help.
<ComboBox Width="140" ItemsSource="{Binding SecurityContexts, Mode=OneWay}"
SelectedItem="{Binding ActiveSecurityContext, Mode=TwoWay}"
ToolTip="Working Location">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Location}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The code behind is
SecurityContexts = new ObservableCollection(_currentUser.ApplicationSecurityContexts);
public interface IApplicationSecurityContext
{
IRole Role { get; }
string Location { get; }
IEnumerable<string> Budgets { get; }
}
public IApplicationSecurityContext ActiveSecurityContext
{
get { return this._currentUser.ActiveSecurityContext; }
set
{
if (this._currentUser.ActiveSecurityContext != value)
{
this._currentUser.ChangeActiveSecurityContext(value);
RaisePropertyChanged("CurrentUser");
LoadData();
}
}
}
You can achieve your goal using CompositeCollection
you can do this. Define resource in your grid/usercontrol/combobox:
<Grid.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding Binding SecurityContexts, Mode=OneWay}" />
<DataTemplate DataType="{x:Type c:SecurityContexts}">
<TextBlock Text="{Binding Location}"/>
</DataTemplate>
</Grid.Resources>
then your combobox itemsource will be:
<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem>
<TextBlock Text="select"/>
</ComboBoxItem>
<CollectionContainer Collection="{Binding Source= {StaticResource cvs}}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
It should work. You need to define your datatemplate for your collection in the resource too, to define how your item will be displayed
Note that c in c:SecurityContexts is the path where you defined your custom object
A ListView displays a collection of the following class:
public class Employee
{
private string _department;
private string _manager;
private string _name;
private string _address;
public string Department
{
get { return _department; }
}
public string Manager
{
get { return _manager; }
}
public string Name
{
get { return _name; }
}
public string Address
{
get { return _address; }
}
}
There is a 1-to-1 relation between Department and Manager, so any 2 rows with the same department will also have the same manager.
I want to group by Department/Manager, with the group header showing "Department (Manager)".
My CollectionViewSource looks like
<CollectionViewSource x:Key="cvsEmployees" Source="{Binding Employees}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Department" />
<PropertyGroupDescription PropertyName="Manager" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
The plan is to not display the first level header (Department) and to somehow bind to both the Department (1st level) and the Manager (2nd level) from the 2nd level header.
3 questions:
To avoid displaying the 1st level header, I have an empty data template in the groupstyle:
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
This seems very clunky. Is there a more elegant way to skip a group header?
How do I bind to the 1st grouping level property (Department) from the 2nd level header (Manager) to achieve the required "Department (Manager)" ?
Is there a better way to do this than creating 2 grouping level?
Thanks
Solved the main stumbling block, question 2 above: how to bind from the group header to a property that is not the grouping property.
The solution is to change the data context to:{Binding Items}. The ItemSource properties are then available
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,10,0,3" DataContext="{Binding Items}" >
<TextBlock Text="{Binding Path=Department}" FontWeight="Bold" Margin="3"/>
<TextBlock Text="{Binding Path=Manager, StringFormat='({0})'}" Margin="3"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
I would create another model part, which represents the dual grouping that you need to have happen:
Model Classes:
public class EmployeeModel {
private readonly Employee _Employee;
public DepartmentManager ManagementInfo { get; private set; }
public string Name {
get { return _Employee.Name; }
}
public string Address {
get { return _Employee.Address; }
}
public EmployeeModel(Employee employee) {
this._Employee = employee;
this.ManagementInfo = new DepartmentManager(employee.Department, employee.Manager);
}
}
public class DepartmentManager {
public string Department { get; private set; }
public string Manager { get; private set; }
public DepartmentManager(string dept, string manager) {
this.Department = dept;
this.Manager= manager;
}
public override bool Equals(object obj) {
var model = obj as DepartmentManager;
if(null == model)
return false;
return Department.Equals(model.Department, StringComparison.InvariantCultureIgnoreCase) &&
Manager.Equals(model.Manager, StringComparison.InvariantCultureIgnoreCase);
}
}
XAML:
<CollectionViewSource x:Key="cvsEmpsModel" Source="{Binding EmployeesModel}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ManagementInfo" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate DataType="{x:Type models:EmployeeModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
...
<ListView ItemsSource="{Binding Source={StaticResource cvsEmpsModel}}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,10,0,3" DataContext="{Binding Items}">
<TextBlock Text="{Binding Path=ManagementInfo.Manager}" FontWeight="Bold" Margin="3" />
<TextBlock Text="{Binding Path=ManagementInfo.Department, StringFormat='({0})'}" Margin="3" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
Then in your Window/ViewModel:
this.EmployeesModel = new ObservableCollection<EmployeeModel>(MyListOfEmployersFromDB.Select(e => new EmployeeModel(e)));
Note, I've overriden Equals in the DepartmentManager class, but not GetHashCode, ideally you should do a custom implementation of that. I had to override equals so the grouping view source would correctly group the same entries. You could get rid of this need, buy constructing the DepartmentManager for the same Employees outside of the collection, and pass them into the EmployeeModel ctr.
I need to display Hierarchy in the treeview. But Details should be displayed in the datagrid.
How do I have to write my template to achieve this? I misunderstand smth in templates for now.
<TreeView ItemsSource="{Binding Path=Categories}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type stackProjects:Category}" ItemsSource="{Binding Path=SubCategories}">
<TextBlock Margin="3" Text="{Binding Path=CategoryName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type stackProjects:SubCategory}" ItemsSource="{Binding Path=Details}">
<TextBlock Text="{Binding Path=SubCategoryName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type stackProjects:Detail}" >
<StackPanel Orientation="Horizontal">
<TextBlock Margin="3" Text="{Binding Path=Name}"/>
<TextBlock Margin="3" Text=" - "/>
<TextBlock Margin="3" Text="{Binding Path=Info}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
I've found a workaround. I had to understand that Details should be presented as a collection within a single element which has IEnumerable property. May be it's not the best solution but it works.
I needed to create a Converter to wrap my collection into single one.
public class BoxingItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var values = value as IEnumerable;
var type = parameter as Type;
if (values == null || type == null)
return null;
if (type.GetInterfaces().Any(x => x == typeof (IItemsWrapper)))
{
var instance = (IItemsWrapper) type.Assembly.CreateInstance(type.FullName);
instance.Items = (IEnumerable) value;
//returned value should be IEnumerable with one element.
//Otherwise we will not see children nodes
return new List<IItemsWrapper> {instance};
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Example for wrappers:
internal interface IItemsWrapper
{
IEnumerable Items { get; set; }
}
public class ItemsWrapper : IItemsWrapper
{
public IEnumerable Items { get; set; }
}
public class DetailItemsWrapper : ItemsWrapper{}
public class InvoiceItemsWrapper:ItemsWrapper{}
And the xaml. It will not require a lot of changes. You just need to use Boxing converter and set the Type to return in the converter parameter.
<TreeView ItemsSource="{Binding Path=Categories}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type wpfProj:Category}" ItemsSource="{Binding Path=SubCategories}">
<TextBlock Margin="4" Text="{Binding Path=CategoryName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type wpfProj:SubCategory}"
ItemsSource="{Binding Path=Details, Converter={StaticResource boxingItems}, ConverterParameter={x:Type wpfProj:DetailItemsWrapper}}" >
<StackPanel>
<TextBlock Margin="4" Text="{Binding Path=SubCategoryName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type wpfProj:DetailItemsWrapper}" >
<DataGrid ItemsSource="{Binding Path=Items}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
I've uploaded sample to dropbox.
Here is how it does look like:
I have a WPF user control that contains a DataGrid. This DG contains several columns including a ComboBox for states. The list of states is populated and stored as a property in my ViewModel.
I am trying to bind the StateList Property to the ItemsSource of my Combobox but when I run the form and try to edit the DG, the combobox does not contain any values, the combobox is empty.
Here is the XAML for the usercontrol.
<UserControl x:Class="myproject.View.ucContactView"
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="475" d:DesignWidth="977">
<UserControl.Resources>
<ResourceDictionary Source="/Templates/MyResourceDictionary.xaml"/>
</UserControl.Resources>
<Grid DataContext="{Binding ViewModel}">
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding AddressCollectionViewSource.View}">
<DataGridTemplateColumn Header="State" Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding StateDescription}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox Name="cboState"
SelectedValuePath="StateKey"
ItemTemplate="{StaticResource dtStateTemplate}"
ItemsSource="{Binding StateList}"
SelectedItem="{Binding StateKey, Mode=TwoWay}"
Width="100" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid>
</Grid>
</UserControl>
The odd thing is that if I create another combobox on this usercontrol with the exact same combobox, this combobox works as expected.
<!-- this works as long as it's not in the DG -->
<StackPanel Height="126" HorizontalAlignment="Left" Margin="766,275,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="200" >
<ComboBox Name="cboState2"
SelectedValuePath="StateKey"
ItemTemplate="{StaticResource dtStateTemplate}"
ItemsSource="{Binding StateList}"
SelectedItem="{Binding StateKey, Mode=TwoWay}"
Width="100" />
</StackPanel>
Why won't the combobox in the DG display the values from the StateList property? Any why does the separate combobox work properly?
It's not working because your ComboBox is looking for StateList as a property of the DataContext of the DataGrid. That is, it's trying to bind to ViewModel.AddressCollectionViewSource.View.StateList when it needs to be binding to ViewModel.StateList. Check your output window while debugging and I bet you'll see a binding error to the effect of Could not find property StateList on object AddressCollectionViewSource (or maybe ICollection).
Try this instead:
<ComboBox Name="cboState2"
SelectedValuePath="StateKey"
ItemTemplate="{StaticResource dtStateTemplate}"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.StateList}"
SelectedItem="{Binding StateKey, Mode=TwoWay}"
Width="100" />
if your viewmodel is a property at the window you can do this
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=ViewModel.StateList, Mode=OneWay}"
<Window x:Class="WpfStackOverflowSpielWiese.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2"
Height="300"
Width="300"
x:Name="window">
<Grid DataContext="{Binding ElementName=window, Path=ViewModel}">
<DataGrid x:Name="grid"
AutoGenerateColumns="False"
ItemsSource="{Binding AddressCollectionViewSource, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="State"
Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding StateKey}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox Name="cboState"
SelectedValuePath="StateKey"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=ViewModel.StateList, Mode=OneWay}"
SelectedItem="{Binding StateKey, Mode=TwoWay}"
Width="100" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfStackOverflowSpielWiese
{
/// <summary>
/// Interaction logic for Window2.xaml
/// </summary>
public partial class Window2 : Window
{
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(ViewModelClass), typeof(Window2), new PropertyMetadata(default(ViewModelClass)));
public ViewModelClass ViewModel {
get { return (ViewModelClass)this.GetValue(ViewModelProperty); }
set { this.SetValue(ViewModelProperty, value); }
}
public Window2() {
this.InitializeComponent();
this.grid.Items.Clear();
this.ViewModel = new ViewModelClass();
}
}
public class StateClass : DependencyObject
{
public static readonly DependencyProperty StateKeyProperty =
DependencyProperty.Register("StateKey", typeof(string), typeof(ViewModelClass), new PropertyMetadata(default(string)));
public string StateKey {
get { return (string)this.GetValue(StateKeyProperty); }
set { this.SetValue(StateKeyProperty, value); }
}
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(string), typeof(StateClass), new PropertyMetadata(default(string)));
public string State {
get { return (string)this.GetValue(StateProperty); }
set { this.SetValue(StateProperty, value); }
}
}
public class ViewModelClass : DependencyObject
{
public static readonly DependencyProperty StateListProperty =
DependencyProperty.Register("StateList", typeof(ObservableCollection<string>), typeof(ViewModelClass), new PropertyMetadata(default(ObservableCollection<string>)));
public static readonly DependencyProperty AddressCollectionViewSourceProperty =
DependencyProperty.Register("AddressCollectionViewSource", typeof(ObservableCollection<StateClass>), typeof(ViewModelClass), new PropertyMetadata(default(ObservableCollection<StateClass>)));
public ObservableCollection<StateClass> AddressCollectionViewSource {
get { return (ObservableCollection<StateClass>)this.GetValue(AddressCollectionViewSourceProperty); }
set { this.SetValue(AddressCollectionViewSourceProperty, value); }
}
public ObservableCollection<string> StateList {
get { return (ObservableCollection<string>)this.GetValue(StateListProperty); }
set { this.SetValue(StateListProperty, value); }
}
public ViewModelClass() {
this.StateList = new ObservableCollection<string>(new[] {"one", "two"});
this.AddressCollectionViewSource = new ObservableCollection<StateClass>(new[] {new StateClass {State = "state", StateKey = "one"}});
}
}
}
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.