I have multiple UserControls and i want to show them on a ListBox
Lets assume i have an Employee abstract UserControl and i have 2 or more UserControls than use that Employee like AdministrativeEmployee and FactoryEmployee for each of those Employees i have diffrent data but the UserControl is very similar (same size, almost same fields), on the ViewModel side i have an abstract EmployeeViewModel, the AdministrativeEmployeeViewModel and the FatoryEmployeeViewModel, on the EmployeesView i have the ListBox with the databinding and on the EmployeesViewModel i have an ICollection Employees
EmployeesView.xaml:
xmlns:local="clr-namespace:Solution.Controls"
-
<ListBox ItemsSource={Binding Employees}>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}" Name="Presenter" />
<DataTemplate.Triggers>
<DataTrigger Value="{x:Type local:AdministrativeEmployeeView}">
<Setter TargetName="Presenter" Property="Content">
<Setter.Value>
<local:AdministrativeEmployeeView />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Value="{x:Type local:FactoryEmployeeView}">
<Setter TargetName="Presenter" Property="Content">
<Setter.Value>
<local:FactoryEmployeeView/>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
EmployeesViewModel.cs:
public ICollection<EmployeeViewModel> Employees { get; set; }
But that show me System.ItemTemplate on the ListBox and not the UserControls for each type of Employee
In ListBox resources define two DataTemplates:
<ListBox ItemsSource="{Binding Employees}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:AdministrativeEmployee}">
<local:AdministrativeEmployeeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:FactoryEmployee}">
<local:FactoryEmployeeView />
</DataTemplate>
</ListBox.Resources>
</ListBox>
Model classes:
public class Employee
{
public string Name { get; set; }
}
public class AdministrativeEmployee : Employee
{
}
public class FactoryEmployee : Employee
{
}
Sample data:
List<Employee> _source = new List<Employee>();
_source.Add(new AdministrativeEmployee { Name = "A test1" });
_source.Add(new FactoryEmployee { Name = "F test1" });
_source.Add(new AdministrativeEmployee { Name = "A test2" });
_source.Add(new FactoryEmployee { Name = "F test2" });
Employees = _source;
AdministrativeEmployeeView:
<UserControl ...>
<Grid>
<TextBlock Text="{Binding Name}" Background="Red" />
</Grid>
</UserControl>
FactoryEmployeeView:
<UserControl ...>
<Grid>
<TextBlock Text="{Binding Name}" Background="Green" />
</Grid>
</UserControl>
Result:
Related
In the example below when data is bound to the tab control, it correctly has the tab headers per the data, but the tab content is always the same; and the last item bound to.
"Frank Wright"'s "Wright" is on every page.
What is wrong? One expects that there are different controls on every page, not one control shared or data repeated.
<Page.Resources>
<model:People x:Key="People">
<model:Person First="Joe"
Last="Smith"
Phone="303-555-5555" />
<model:Person First="Jenny"
Last="Johnson"
Phone="720-867-5309" />
<model:Person First="Frank"
Last="Wright"
Phone="202-555-5555" />
</model:People>
</Page.Resources>
<TabControl ItemsSource="{StaticResource People}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding First}" />
<Setter Property="Content">
<Setter.Value>
<TextBox Text="{Binding Last}" />
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
public class People : List<Person> { }
public class Person
{
public string First { get; set; }
public string Last { get; set; }
public string Phone { get; set; }
}
You want to set the ContentTemplate, not the Content:
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Last}" />
<Label Content="{Binding Phone}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
Now I have the following code:
ViewModel(s)
public class VMBase
{
public string TabID{get;set;}
public string TabHeader {get;set;}
}
public class VM1:VMBase //implements the properties in base class
{
}
public class VM2:VMBase //implements the properties in base class
{
}
And in my DataTemplate.xaml, I have the different local controls binded to the ViewModel, depending on the type of ViewModel it is, ie:
<DataTemplate DataType="{x:Type VM:VM1}">
<local: Control1 />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:VM2}">
<local: Control2 />
</DataTemplate>
Control1 and Control2 are different types of UserControl:
public class Control1:UserControl
{
}
public class Control2:UserControl
{
}
Things are still manageable when I have only two derived classes for VMBase, but what if I have ten? Or more? It's going to get ugly.
Is it possible to bind a single VM to different views ( user control), so that I don't have to manually create so many derived class for VMBase? I will just need to specify the VM properties such as TabID and TabHeader, correct views will be bind as a result.
Edit:
Here are further details: my VM is bind to a ContentControl (ie: contentcontrol.Content=VM). And each VM has two properties TabID and Header. Whether the DataTemplateSelector should be invoked depends on whether it has a specific TabID ( if it has other TabID then this DataTemplateSelector shouldn't be invoked), and which DataTemplate ( the logic inside the DataTemplateSelector ) to invoke depends further on the Header. How to implement this?
Updated answer - v2 (as per question edit)
I think simply returning a null in your DataTemplateSelector when TabID is not a match should do the trick, as WPF will then try and pick the next best match (i.e. the template that matches the DataType). In case the TabID is a match, you can return the template based on the TabHeader value.
So your custom DataTemplateSelector would look like this:
public class TabHeaderDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
if (element == null)
return null;
var viewModel = item as VMBase;
if (viewModel == null || viewModel.TabID != "02")
return null; //continue only if TabID is a match
if (viewModel != null)
{
switch(viewModel.TabHeader)
{
case "two":
return element.FindResource($"Template2") as DataTemplate;
case "three":
return element.FindResource($"Template3") as DataTemplate;
}
}
return null;
}
}
Sample XAML
<Window.Resources>
<!-- data template for VM1 -->
<DataTemplate DataType="{x:Type local:VM1}">
<Grid>
<Rectangle Stroke="Black" />
<TextBlock Margin="5" Text="{Binding TabHeader}" FontSize="18"/>
</Grid>
</DataTemplate>
<!-- data template for VM2 -->
<DataTemplate DataType="{x:Type local:VM2}">
<Grid>
<Rectangle Stroke="Red" />
<TextBlock Margin="5" Text="{Binding TabHeader}" FontSize="18"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="Template2">
<Grid>
<Ellipse Stroke="Green" StrokeThickness="4"/>
<TextBlock Margin="10" Text="{Binding TabHeader}" FontSize="24"
Foreground="Red" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="Template3">
<Grid>
<TextBlock Margin="10" Text="{Binding TabHeader}" FontSize="24"
Foreground="White" Background="Black" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplateSelector">
<Setter.Value>
<local:TabHeaderDataTemplateSelector />
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Margin="25">
<ContentControl Content="{Binding VmObj_1}" />
<ContentControl Content="{Binding VmObj_2}" />
<ContentControl Content="{Binding VmObj_3}" />
<ContentControl Content="{Binding VmObj_4}" />
</StackPanel>
and the code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new
{
VmObj_1 = new VM1 { TabID = "01", TabHeader = "one" },
VmObj_2 = new VM1 { TabID = "02", TabHeader = "two" },
VmObj_3 = new VM2 { TabID = "02", TabHeader = "three" },
VmObj_4 = new VM2 { TabID = "03", TabHeader = "four" },
};
}
}
public class VMBase
{
public string TabID { get; set; }
public string TabHeader { get; set; }
}
public class VM1 : VMBase { }
public class VM2 : VMBase { }
Updated answer - v1
You can approach this problem in two different ways. Each option has its own set of pros and cons; but my most recommended approach would be (as #jon-stødle suggested) is to use a DataTemplateSelector
Option 1 - Use DataTemplateSelector
As you are using Type based data-template(s) - then I assume you are most probably using a ContentControl (or a variant) to display the dynamic view-model driven UI. ContentControl and other templated controls such as Label, UserControl, ItemsControl, ListBox etc. usually have dependency property like ContentTemplateSelector or ItemTemplateSelector that you can bind your template-selector to.
You can refer this link for an example for using Label with DataTemplateSelector; or following example for usage with TabControl
XAML:
<Window.Resources>
<DataTemplate x:Key="Tab1Template">
<Grid>
<Rectangle Stroke="Black" />
<TextBlock Margin="5" Text="{Binding}" FontSize="18"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="Tab2Template">
<Grid>
<Ellipse Stroke="Green" StrokeThickness="4"/>
<TextBlock Margin="10" Text="{Binding}" FontSize="24"
Foreground="Red" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="Tab3Template">
<Grid>
<TextBlock Margin="10" Text="{Binding}" FontSize="24"
Foreground="White" Background="Black" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<Style TargetType="{x:Type TabControl}">
<Setter Property="ContentTemplateSelector">
<Setter.Value>
<local:TabIdDataTemplateSelector />
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<TabControl>
<TabControl.ItemsSource>
<col:ArrayList>
<sys:String>1</sys:String>
<sys:String>2</sys:String>
<sys:String>3</sys:String>
</col:ArrayList>
</TabControl.ItemsSource>
</TabControl>
DataTemplateSelector:
public class TabIdDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
if (element == null)
return null;
//var vm = item as VMBase;
//var id = vm.TabId;
string id = item as string;
if (id != null)
{
return element.FindResource($"Tab{id}Template") as DataTemplate;
}
return null;
}
}
Option 2 - Use Style based data-triggers
Another option is use data-trigger(s) on the property (i.e. TabId) of your ViewModel to update the ContentTemplate of your container-view (i.e. TabControl).
<Window.Resources>
<DataTemplate x:Key="Tab1Template">
<Grid>
<Rectangle Stroke="Black" />
<TextBlock Margin="5" Text="{Binding Key}" FontSize="18"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="Tab2Template">
<Grid>
<Ellipse Stroke="Green" StrokeThickness="4"/>
<TextBlock Margin="10" Text="{Binding Key}" FontSize="24"
Foreground="Red" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="Tab3Template">
<Grid>
<TextBlock Margin="10" Text="{Binding Key}" FontSize="24"
Foreground="White" Background="Black" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<Style TargetType="{x:Type TabControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding TabId}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=Tab1Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding TabId}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=Tab2Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding TabId}" Value="3">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=Tab3Template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<TabControl DisplayMemberPath="Value" SelectedValuePath="Key"
SelectedValue="{Binding TabId}">
<TabControl.ItemsSource>
<col:Hashtable>
<sys:String x:Key="1">one</sys:String>
<sys:String x:Key="2">two</sys:String>
<sys:String x:Key="3">three</sys:String>
</col:Hashtable>
</TabControl.ItemsSource>
</TabControl>
What you are looking for is a DataTemplateSelector. It let's you choose a DataTemplate based on different criteria.
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
var vmBase = item as VMBase;
if (element != null && vmBase != null)
{
switch(vmBase.TabID)
{
case "Tab1": return element.FindResource("Tab1Template") as DataTemplate;
case "Tab2": return element.FindResource("Tab2Template") as DataTemplate;
default: return null;
}
}
}
}
You can read more about them in the docs or have a look at this tutorial.
I have XAML related question I have tried to research an answer in vain. I have commented the relevant questions to the XAML. It looks to me this questions is a more complex because of the way I try to arrange things.
Basically I have a main view model used in the TabControl headers and then in the content area I would show a list of items from the main view model. I just don't know how to to the binding. This is the main question. However, I suspect the next and ultimate objectives I have might factor in how to think about this, so I added them too. The rest of the code is for the sake of completeness.
<StackPanel>
<TabControl x:Name="mainsTabControl"
IsSynchronizedWithCurrentItem="True"
IsEnabled="True"
Visibility="Visible"
ItemsSource="{Binding Path=Mains}">
<!-- How to select a different background for the selected header? Note that the background color is "selected tab" if MainContentViewModel.IsActive is not TRUE.
If it is, a different color is chosen. Here this fact is just emulated with IsEnabled property due to well, multi-binding to the rescue (and a converter)? -->
<!-- Is there a clean way to use ICommand binding (RelayCommand) to check if it is OK to change the tab and if necessary, present a dialogue asking for the change? -->
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsActive}"/>
</Style>
</TabControl.ItemContainerStyle>
<!-- This binds to every item in the MainViewModel.Mains collection. Note the question about background color. -->
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<!-- This binding gives reference to the selected MainContentViewModel. -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="10" DataContext="{Binding ElementName=mainsTabControl, Path=SelectedItem, Mode=OneWay}">
<ItemsControl ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
using GalaSoft.MvvmLight;
using System.Collections.ObjectModel;
using System.Linq;
namespace WpfDependencyInjection.ViewModel
{
public class MainContentViewModel: ViewModelBase
{
private ObservableCollection<CustomItemViewModel> customItems;
private mainContentDto MainContent { get; set; }
public string Name { get; }
public bool isActive;
public MainContentViewModel(Engine engine, mainContentDto mainContent)
{
MainContent = mainContent;
Name = MainContent.Name;
IsActive = true;
//The custom items belonging to this main content.
var customItems = engine.CustomItemContents.Where(i => i.MainContentId == MainContent.Id).Select(i => new CustomItemViewModel(engine, i));
CustomItems = new ObservableCollection<CustomItemViewModel>(customItems);
}
public ObservableCollection<CustomItemViewModel> CustomItems
{
get
{
return customItems;
}
set
{
customItems = value;
RaisePropertyChanged(nameof(CustomItems));
}
}
public bool IsActive
{
get
{
return isActive;
}
private set
{
isActive = value;
RaisePropertyChanged(nameof(IsActive));
}
}
}
}
public class CustomItemViewModel: ViewModelBase
{
private Engine Engine { get; }
private ItemTypeDto CustomItem { get; set; }
public string Name { get; }
public CustomItemViewModel(Engine engine, ItemTypeDto customItem)
{
Engine = engine;
CustomItem = customItem;
Name = customItem.Name;
}
}
namespace WpfDependencyInjection
{
public class Engine
{
public string Name { get; } = "EngineMan";
public List<mainContentDto> MainContents { get; set; } = new List<mainContentDto>(new[]
{
new mainContentDto { Name = "Main One", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Version = 1 },
new mainContentDto { Name = "Main Two", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Version = 1 }
});
public List<ItemTypeDto> CustomItemContents { get; set; } = new List<ItemTypeDto>(new ItemTypeDto[]
{
new ItemType1Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType1Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType2Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Name = "ItemType2Dto 2", Id = Guid.NewGuid(), Version = 1 }
});
public Engine()
{
}
}
}
<edit: The binding solved partially, though not the ICommand part.
Try this:
<TabControl x:Name="mainsTabControl"
IsEnabled="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Mains}"
SelectedItem="0"
Visibility="Visible">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter ContentSource="Header" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="HotPink" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<Button Background="{x:Null}"
Command="{Binding SomeCommand}"
Content="{Binding Name}"
FocusVisualStyle="{x:Null}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<ItemsControl Margin="10"
VerticalAlignment="Top"
ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:CustomItem}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Command:
There may be cleaner ways but here we bind IsSelected for the TabItem to the IsSelected property of the viewmodel. This enables having a command that asks if it is ok to navigate and sets IsSelected to true if it is.
Background:
We also retemplate the tabitem so that background works as we want. If you check with Snoop WPF inserts an extra border when the item is selected.
Side note 1:
Don't put the TabControl into a StackPanel like that. A StackPanel sizes to content and will kill scrolling and draw outside the control. Also it comes with a cost, a deep visual tree is not cheap. Same in the ItemTemplate and the other places. In fact StackPanel is rarely right for anything :)
Side note 2:
If you specify DataType in your DataTemplate you get intellisense and some compiletime checking.
How can i stretch the header width in a grouped listbox?
The listbox ItemSource is bound to hierarchical data structure. A group item contains a name and a list of subitems.
public class MyGroup
{
public string GroupName { get; set; }
public ObservableCollection<MyItem> SubItems { get; }
}
public class MyItem
{
public string ItemName { get; set; }
public DateTime Creation { get: set: }
}
public class MyViewModel:
{
public ObservableCollection<MyGroup> Data { get; set; }
}
The page is defined as follows
<Page>
<Page.Resources>
<CollectionViewSource x:Name="cvsListBoxGroup" IsSourceGrouped="True" Source="{Binding Data}" ItemsPath="SubItems"/>
</Page.Resources>
<Page.DataContext>
<local:MyViewModel/>
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListBox Width="500" SelectionMode="Single" ItemsSource="{Binding Source={StaticResource cvsListBoxGroup}}">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Background="Beige">
<TextBlock Text="{Binding GroupName}"></TextBlock>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Bisque">
<TextBlock Text="{Binding ItemName}" Foreground="Black" ></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Page>
Groups and items are displayed. The items don't fill the listbox full width so we need to set the HorizontalContentAlignment for ListBox.ItemContainerStyle. This trick is common - no problem
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
I tried to figure out some similiar trick for the groupheader items - but nothing helps.
<GroupStyle.HeaderContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</GroupStyle.HeaderContainerStyle>
The problem seems the TargetType. For a listview there is a ListViewHeaderItem. But nothing similar exists for the listbox.
See the answer here, it's for ListView but that's basically the same:
https://social.msdn.microsoft.com/Forums/windowsapps/en-US/e8d4f7b3-93a8-4f17-9679-8b700b8d02e6/how-to-adjust-the-width-of-group-header-in-listview-to-stretch-full-width?forum=winappswithcsharp
Imagine multiple object types in one Listbox. Both have different look and presented information. If I add any of them in ObservableCollection it displays them fine in ListBox.
<DataTemplate DataType="{x:Type local:DogData}" >
<Grid...
</DataTemplate>
<DataTemplate DataType="{x:Type local:CatData}" >
<Grid...
</DataTemplate>
Now I want users to be able to press a button and switch view to see more detailed information and there templates which provide it
<DataTemplate x:Key="TemplateDogDataWithImages" >
<Grid...
</DataTemplate>
<DataTemplate x:Key="TemplateCatDataWithImages" >
<Grid...
</DataTemplate>
but I can assign only one
AnimalsListBox.ItemTemplate = this.Resources["TemplateDogDataWithImages"] as DataTemplate;
I don't want to have one and have a bunch of triggers in it.
I have researched DataTemplateSelectors
http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector
Is there a better way? Is there a way to switch default templates for each data type so I could avoid DataTemplateSelectors?
On click of the button how do I select TemplateDogDataWithImages and TemplateCatDataWithImages as default ones and cliking other button use TemplateDogDataSimple and TemplateCatDataSimple?
I think a selector is easier (less code), but if you really want to use triggers, maybe something like this?
namespace WpfApplication65
{
public abstract class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string property)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public abstract class DataBase : NotifyBase
{
bool m_showDetailed;
public bool ShowDetailed
{
get { return m_showDetailed; }
set
{
m_showDetailed = value;
NotifyPropertyChanged("ShowDetailed");
}
}
}
public class DogData : DataBase { }
public class CatData : DataBase { }
public partial class MainWindow : Window
{
public List<DataBase> Items { get; private set; }
public MainWindow()
{
Items = new List<DataBase>() { new DogData(), new CatData(), new DogData() };
DataContext = this;
InitializeComponent();
}
}
}
<Window x:Class="WpfApplication65.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication65"
Title="MainWindow"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<DataTemplate DataType="{x:Type l:CatData}">
<DataTemplate.Resources>
<DataTemplate x:Key="basic">
<TextBlock Text="This is the basic cat view" />
</DataTemplate>
<DataTemplate x:Key="detailed">
<TextBlock Text="This is the detailed cat view" />
</DataTemplate>
</DataTemplate.Resources>
<Border BorderThickness="1"
BorderBrush="Red"
Margin="2"
Padding="2">
<StackPanel>
<ContentPresenter Name="PART_Presenter"
ContentTemplate="{StaticResource basic}" />
<CheckBox Content="Show Details"
IsChecked="{Binding ShowDetailed}" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ShowDetailed}"
Value="True">
<Setter TargetName="PART_Presenter"
Property="ContentTemplate"
Value="{StaticResource detailed}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate DataType="{x:Type l:DogData}">
<DataTemplate.Resources>
<DataTemplate x:Key="basic">
<TextBlock Text="This is the basic dog view" />
</DataTemplate>
<DataTemplate x:Key="detailed">
<TextBlock Text="This is the detailed dog view" />
</DataTemplate>
</DataTemplate.Resources>
<Border BorderThickness="1"
BorderBrush="Blue"
Margin="2"
Padding="2">
<StackPanel>
<ContentPresenter Name="PART_Presenter"
ContentTemplate="{StaticResource basic}" />
<CheckBox Content="Show Details"
IsChecked="{Binding ShowDetailed}" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ShowDetailed}"
Value="True">
<Setter TargetName="PART_Presenter"
Property="ContentTemplate"
Value="{StaticResource detailed}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding Items}" />
</Window>