WPF - Binding to current item from within group header style - wpf

I'm something of a WPF noob so please take it easy on me ;-)
I am trying to create a grouped DataGrid (WPF toolkit version).
I have successfully created the data source, the DataGrid itself, the required CollectionViewSource and the Style for the group header (which uses an expander).
I want to group by a property called 'Assign_To' and have the relevant value (the value that the grouped items share) show up in the header. However, I cannot work out how to bind to the current group/item in order to return its Assign_To property.
The closest I have got (shown below) is binding to the overall CollectionViewSource, which returns a fixed value for Assign_To. What would be the proper way to bind to the current item/group in order to return the correct value for 'Assign_To'?
Hope someone can help. Thanks!
Andy T.
Here's the source...
<Window DataContext="{Binding Source={StaticResource SampleDataSource}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="DataGridTest.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480" mc:Ignorable="d">
<Window.Resources>
<CollectionViewSource x:Key="CVS" Source="{Binding MyData}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Assign_To"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Source={StaticResource CVS}, Path=Assign_To}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<dg:DataGrid
ItemsSource="{Binding Source={StaticResource CVS}}"
SelectionUnit="CellOrRowHeader"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False">
<dg:DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<dg:DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</dg:DataGrid.GroupStyle>
</dg:DataGrid>
</Grid>
</Window>

Thanks for your reply. I really appreciate it and will check it out to see if it works.
Anyway, as it turns out, after some poking and prodding, I have worked it out using XAML only. What I had been missing was the fact that each item the group header is bound to is a GroupItem and that the default DataContext of a GroupItem is a CollectionViewGroup. In turn, a CollectionViewGroup has an Items property, which is a collection and I can therefore get the Assign_To value of the first item in the collection and use that in my header text. Like this:
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Items[0].Assign_To}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

binding settings depend on the type of the Assign_To property. The simplest settings which could probably work for you would be:
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Name}"/>
pls, check if an example below would work for you; also this link WPF Toolkit DataGrid Part IV: TemplateColumns and Row Grouping might be helpful for you
code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var dataProvider = (CollectionViewSource)FindResource("CVS");
dataProvider.Source = Test.GetTests();
}
}
public class Test
{
public string Assign_To { get; set; }
public string Test0 { get; set; }
public int Test1 { get; set; }
public static List<Test> GetTests()
{
List<Test> tests = new List<Test>();
tests.Add(new Test { Assign_To = "a", Test0 = "aaaa", Test1 = 1 });
tests.Add(new Test { Assign_To = "a", Test0 = "bbbb", Test1 = 1 });
tests.Add(new Test { Assign_To = "b", Test0 = "cccc", Test1 = 2 });
return tests;
}
}
xaml:
<Window.Resources>
<CollectionViewSource x:Key="CVS" >
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Assign_To"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<DataGrid
ItemsSource="{Binding Source={StaticResource CVS}}"
HorizontalScrollBarVisibility="Hidden" SelectionMode="Extended"
AutoGenerateColumns="False"
Name="dataGrid1">
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Test0" Binding="{Binding Path=Test0}" />
<DataGridTextColumn Header="Test1" Binding="{Binding Path=Test1}" />
</DataGrid.Columns>
</DataGrid>
</Grid>

Related

DataGrid Grouping in XAML with Data from SQL Table

I have an SQL table names Cities that have four fields:
City
Province
EnName
Code
I want to display its data on WPF DataGrid and set grouping by Province on it
This is my XAML design
<Window x:Class="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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:SLTADataSet x:Key="SLTADataSet"/>
<CollectionViewSource x:Key="CountriesViewSource" Source="{Binding Countries, Source={StaticResource SLTADataSet}}"/>
<CollectionViewSource x:Key="CitiesViewSource" Source="{Binding Cities, Source={StaticResource SLTADataSet}}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Province"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid >
<DataGrid x:Name="CitiesDataGrid" ItemsSource="{Binding Source={StaticResource CitiesViewSource}}" CanUserAddRows="False" >
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="false" Background="Gray" >
<Expander.Header>
<TextBlock FontWeight="Bold" Text="{Binding Source={StaticResource CitiesViewSource }, Path=Province}" Background="Yellow" />
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<!-- Style for groups under the top level. -->
<!--<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<DockPanel Background="yellow">
--><!--<TextBlock Text="{Binding Source={StaticResource CitiesViewSource}, Path=Province}" Foreground="Black" Margin="30,0,0,0" Width="100"/>--><!--
<TextBlock Text="SSSSSSSSSSSSSS" Foreground="Black" Margin="30,0,0,0" Width="100"/>
</DockPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>-->
</DataGrid.GroupStyle>
</DataGrid>
</Grid>
</Window>
here is the behind code
Class MainWindow
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs) Handles MyBase.Loaded
Dim SLTADataSet As WpfApp1.SLTADataSet = CType(Me.FindResource("SLTADataSet"), WpfApp1.SLTADataSet)
Dim SLTADataSetCitiesTableAdapter As WpfApp1.SLTADataSetTableAdapters.CitiesTableAdapter = New WpfApp1.SLTADataSetTableAdapters.CitiesTableAdapter()
Dim CitiesViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("CitiesViewSource"), System.Windows.Data.CollectionViewSource)
SLTADataSetCitiesTableAdapter.Fill(SLTADataSet.Cities)
CitiesViewSource.View.MoveCurrentToFirst()
End Sub
Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
End Sub
End Class
Grouping is done and working perfect
but I have a problem with expander in Datagrid grouping style
it always the name of first province in table for all the group item I cannot find what the problem is.
Would you please help me on it.
Since you have grouped by the Province property, you could just bind to the Name property of the GroupItem:
<TextBlock FontWeight="Bold" Text="{Binding Name}" Background="Yellow" />

WPF Listbox Grouping Header Not Binding

I have a CollectionViewSource, It is grouped by a Created proprty in an ObservableCollection. The Grouping works within the listbox with the exception to i cannot get the Header text to display the created date.
The CollectionViewSource is below:
<Window.Resources>
<CollectionViewSource x:Key="TaskListColSource" Source="{Binding Path=TaskItems}">
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription PropertyName="Created" />
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Created" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
My Listbox GroupStyle is below:
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Foreground" Value="White" />
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Foreground="White">
<Expander.HeaderTemplate>
<DataTemplate DataType="{x:Type models:TaskItem}">
<TextBlock x:Name="asdf" Text="{Binding Created}" Foreground="White"/>
</DataTemplate>
</Expander.HeaderTemplate>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
Could anyone possibly help me with displaying the created date within the relevant Textblock?
I am binding the listbox in the following way:
<ListBox x:Name="TaskListBox" ItemsSource="{Binding Source={StaticResource TaskListColSource}}">
You need to set Header for your Expander like this:
<Expander IsExpanded="True" Foreground="White"
Header="{Binding}">
</Expander>
The implicit DataContext in each GroupItem is each data item. So the Header should be set to each data item (via {Binding}). The HeaderTemplate has implicit DataContext as its Header but you has not assigned anything for it.
Edit:
If you want some StringFormat, why not set it inside TextBlock (let the Header unchanged):
<TextBlock x:Name="asdf" Text="{Binding Created, StringFormat=dd MMM yy, ConverterCulture=en-GB}" Foreground="White"/>

Need multiple styles dependent on Listboxitem

i have a Listbox, which stores two different object types, based on the same baseclass. (e.g. BaseObject = baseclass and the children of it: CustomPath and CustomImage)
The Datasource:
ObservableCollection<BattlegroundBaseObject> _baseObjectCollection;
public ObservableCollection<BattlegroundBaseObject> BaseObjectCollection
{
get { return _baseObjectCollection?? (_baseObjectCollection= new ObservableCollection<BaseObject>()); }
}
The Listbox databinding: <ListBox ItemsSource="{Binding BaseObjectCollection}"
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" x:Name="ListBoxPathLineStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem" x:Name="BattlegroundObjectControlTemplate">
<Path Stroke="{Binding ObjectColor}" StrokeThickness="{Binding StrokeThickness}" Data="{Binding PathGeometryData}" x:Name="PathLine" Opacity="{Binding Opacity}">
</Path>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Effect" TargetName="PathLine">
<Setter.Value>
<DropShadowEffect Color="CornflowerBlue" ShadowDepth="3" BlurRadius="10" />
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
I want to add to the ControlTemplate where the Path is, also a Image and to differ it by type or a property. doesnt matter.
anyone any ideas?
You can add to ListBox resources DataTemplate for each type.
In my example classes Car and Motorbike derived from Vehicle class.
<ListBox x:Name="listBox">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Car}">
<StackPanel Background="Red">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Motorbike}">
<StackPanel Background="Orange">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.Resources>
</ListBox>
EDIT:
You can add style for ListBoxItem to resources:
<ListBox x:Name="listBox">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="CornflowerBlue" ShadowDepth="3" BlurRadius="10" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type local:Car}">
<StackPanel Background="Red">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Motorbike}">
<StackPanel Background="Orange">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.Resources>
</ListBox>
You could define some DataTemplates for your different classes. They determine how the classes are displayed. I've been using them to display derived classes differently when working with a collection of the base class.
<DataTemplate DataType="{x:Type CustomPath}">
<TextBlock Text="This is a CustomPath"/>
</DataTemplate>
<DataTemplate DataType="{x:Type CustomImage}">
<TextBlock Text="This is a CustomImage"/>
</DataTemplate>
At the moment you are changing the style of the controls that WPF is using to render your bound data. A better way to do this is to provide WPF with a way of generating the correct controls. Ignore the ListBoxItem and use DataTemplates for your actual objects.
First you need to tell the Window or control how to find your types.
<Window or UserControl
...
xmlns:model="clr-namespace:yourNamespace"
>
Then you can provide WPF with a way to show your objects e.g.
<DataTemplate TargetType="{x:Type model:CustomPath}">
<Path Stroke="{Binding ObjectColor}" StrokeThickness="{Binding StrokeThickness}"
Data="{Binding PathGeometryData}" x:Name="PathLine" Opacity="{Binding Opacity}"/>
<!-- maybe use a binding from the Path.Effect back to the IsSelected and ValueConverters
to re-apply the selection effect-->
</DataTemplate>
<DataTemplate TargetType="{x:Type model:CustomImage}">
<Image Src="{Binding SomeProperty}" />
</DataTemplate>
Now all you need to do is to make these available to the ListBox in some way. Almost every element in WPF can have .Resources added to it, so you could choose to do these across the entire window
<Window ...>
<Window.Resources>
<DataTemplate .../>
<DataTemplate .../>
</Window.Resources>
...
<ListBox .../>
</Window>
or you can apply it more locally
<Window ...>
...
<ListBox>
<ListBox.Resources>
<DataTemplate .../>
<DataTemplate .../>
</ListBox.Resources>
</ListBox>
</Window>
And this way your listbox definition can become much neater too, e.g. if you are using Window.Resources
<ListBox ItemsSource="{Binding BaseObjectCollection}"/>

How to create a custom control which is a combobox with group header

I have a special requirement which need to implement a combobox which list data in groups and I could do it as below
the XAML file
<Window x:Class="GroupComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox x:Name="comboBox" Width="100">
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Horizontal" Width="150" Height="Auto" >
<!-- add scroll bar -->
</WrapPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Width" Value="50" />
</Style>
</ComboBox.Resources>
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="2">
<TextBlock Text="{Binding Name}" HorizontalAlignment="Stretch" Background="YellowGreen"/>
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Item}" Width="40"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="{Binding Available}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
</Window>
The code-behind file
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<CategoryItem<string>> items = new List<CategoryItem<string>>();
for (int i = 0; i < 18; ++i)
{
items.Add(new CategoryItem<string> { Item = string.Format("{0:D2}", i), Available = (i > 9), Category = "Group A" });
}
for (int i = 0; i < 4; ++i)
{
items.Add(new CategoryItem<string> { Item = string.Format("{0:D2}", i), Available = (i > 2), Category = "Group B" });
}
//Need the list to be ordered by the category or you might get repeating categories
ListCollectionView lcv = new ListCollectionView(items.OrderBy(w => w.Category).ToList());
//Create a group description
lcv.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
this.comboBox.ItemsSource = lcv;
}
}
public class CategoryItem<T>
{
public T Item { get; set; }
public bool Available { get; set; }
public string Category { get; set; }
}
Now I want to make the combobox to be a custom control, then it can be reused easily, but I am new for creating custom control and how should I do?
Currently I have created a WPF custom control library and change the base class of the custom control to be 'ComboBox' , but I do not know how to move the styles and templates to the resource dictionary file correctly
I can offer the following variant. Move all the styles and templates in resources:
<!-- Main style for ComboBox -->
<Style x:Key="MyComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="25" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Horizontal" Width="150" Height="Auto" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Item}" Width="40"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Style for ComboBoxItem -->
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Width" Value="50" />
</Style>
<!-- Style for ItemContainerStyle -->
<Style x:Key="ComboBoxItemContainerStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="{Binding Available}" />
</Style>
<!-- DataTemplate for HeaderTemplate -->
<DataTemplate x:Key="MyHeaderTemplate">
<Border BorderBrush="Black" BorderThickness="2">
<TextBlock Text="{Binding Name}" HorizontalAlignment="Stretch" Background="YellowGreen" />
</Border>
</DataTemplate>
Using of ComboBox with our styles:
<ComboBox x:Name="MyComboBox1" Style="{StaticResource MyComboBox}" IsSynchronizedWithCurrentItem="False" ItemContainerStyle="{StaticResource ComboBoxItemContainerStyle}">
<ComboBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource MyHeaderTemplate}" />
</ComboBox.GroupStyle>
</ComboBox>
<ComboBox x:Name="MyComboBox2" Style="{StaticResource MyComboBox}" IsSynchronizedWithCurrentItem="False" ItemContainerStyle="{StaticResource ComboBoxItemContainerStyle}">
<ComboBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource MyHeaderTemplate}" />
</ComboBox.GroupStyle>
</ComboBox>
And set the data in code:
this.MyComboBox1.ItemsSource = lcv;
this.MyComboBox2.ItemsSource = lcv;
Set styles for you control need to change Type and write your control name:
<Style x:Key="MyControlComboBox" TargetType="{x:Type local:MyControlComboBox}">
</Style>

Switch ContentPresenter of ListBoxItem Based on Selection

I am trying to switch out the ContentPresenter of a ListBoxItem when it is selected, while using multiple DataTemplates to represent different types of data.
Here is the UserControl that defines the ListBox inside:
<UserControl x:Class="Homage.View.FilePanelView"
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:vw="clr-namespace:Homage.View"
xmlns:vm="clr-namespace:Homage.ViewModel"
xmlns:ctrl="clr-namespace:Homage.Controls"
mc:Ignorable="d">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:SlugViewModel}">
<vw:SlugView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HeaderSlugViewModel}">
<vw:HeaderSlugView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ContentSlugViewModel}">
<vw:ContentSlugView />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="SlugContainer" Background="Transparent" BorderBrush="Black" BorderThickness="1" CornerRadius="2" Margin="0,5,0,0" Padding="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Content="{Binding DisplayName}" />
<ContentPresenter Grid.Row="1" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="SlugContainer" Property="BorderThickness" Value="5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Slugs}" Padding="5" />
</Grid>
</UserControl>
Based on the type of data to be shown (e.g., a "Header Slug") a certain DataTemplate is applied to the ListBoxItem. This is working great, but I am wanting to adjust the DataTemplate of the selected ListBoxItem to a different DataTemplate -- again, based on the data type being shown.
The goal is that, because each data type is different, each would have a unique look when not selected and would receive a unique set of options when selected.
If I can get the above to work, that would be great! But, I also want to complicate things...
While each data type has unique controls they also have common controls. So I would ideally like to define all the common controls once so that they appear in the same place in the ListBox.
<DataTemplate x:Key="CommonSelectedTemplate">
<!-- common controls -->
...
<DataTemplate x:Key="UniqueSelectedTemplate">
<!-- all the unique controls -->
<ContentPresenter />
</DataTemplate>
<!-- more common controls -->
...
</DataTemplate>
If I have to define all the common stuff multiple times (for now) I'll live. =)
Thanks for any help!
Let's suppose that we have only two data types: Item1ViewModel and Item2ViewModel. Therefore we need 4 data templates: 2 for the common state, 2 for the selected state.
<DataTemplate x:Key="Template1" DataType="local:Item1ViewModel">
<TextBlock Text="{Binding Property1}" Foreground="Red" />
</DataTemplate>
<DataTemplate x:Key="Template2" DataType="local:Item2ViewModel">
<TextBlock Text="{Binding Property2}" Foreground="Blue" />
</DataTemplate>
<DataTemplate x:Key="Template1Selected" DataType="local:Item1ViewModel">
<TextBlock Text="{Binding Property1}" Background="Yellow" Foreground="Red" />
</DataTemplate>
<DataTemplate x:Key="Template2Selected" DataType="local:Item2ViewModel">
<TextBlock Text="{Binding Property2}" Background="Yellow" Foreground="Blue" />
</DataTemplate>
For switching the content between two templates based on different types I use the DataTemplateSelector class:
public class SampleTemplateSelector:DataTemplateSelector
{
public DataTemplate Type1Template { get; set; }
public DataTemplate Type2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is Item1ViewModel)
return Type1Template;
else if (item is Item2ViewModel)
return Type2Template;
return null;
}
}
We need two instances of this selector: one for the common state and one for the selected state.
<local:SampleTemplateSelector x:Key="templateSelector"
Type1Template="{StaticResource Template1}"
Type2Template="{StaticResource Template2}"/>
<local:SampleTemplateSelector x:Key="selectedTemplateSelector"
Type1Template="{StaticResource Template1Selected}"
Type2Template="{StaticResource Template2Selected}"/>
And after that you should add this code which switches two selectors:
<DataTemplate x:Key="ListItemTemplate">
<ContentControl x:Name="content" Content="{Binding}" ContentTemplateSelector="{StaticResource templateSelector}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsSelected}" Value="True">
<Setter TargetName="content" Property="ContentTemplateSelector" Value="{StaticResource selectedTemplateSelector}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
That's all, apply this ItemTemplate to your ListBox without changing the ControlTemplate:
<ListBox ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ListItemTemplate}" />

Resources