Bind a collection to a WPF TabControl with static TabItems - c#

I have a TabControl with a couple of static TabItems. I am now trying to dynamically add some tabs with a custom ItemTemplate and ContentTemplate.Something like this:
<TabControl ItemsSource="{Binding DynamicTabs}">
<TabItem Header="Static 1">Content 1</TabItem>
<TabItem Header="Static2">Content 2</TabItem>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Is there any way to achieve this?

You can use the CompositeCollection to combine your different sources:
<TabControl>
<TabControl.Resources>
<CollectionViewSource x:Key="DynamicTabsCollectionVS" Source="{Binding DynamicTabs}"/>
</TabControl.Resources>
<TabControl.ItemsSource>
<CompositeCollection>
<TabItem Header="Static 1">Content 1</TabItem>
<TabItem Header="Static 2">Content 2</TabItem>
<CollectionContainer Collection="{Binding Source={StaticResource DynamicTabsCollectionVS}}" />
</CompositeCollection>
</TabControl.ItemsSource>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

Related

How to use <TabControl.ContentTemplate> to traverse the display content in the xaml file

I'm using materialDesign open source UI library to develop, for I want to use data template to display, the code is as follows, but the display style of the interface is not correct, the display logic is also not correct
<materialDesign:Card>
<TabControl VerticalContentAlignment="Top" materialDesign:ColorZoneAssist.Mode="PrimaryMid"
ItemsSource="{Binding IndexMenus}" Style="{StaticResource MaterialDesignNavigationRailTabControl}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="SelectionChanged">
<b:InvokeCommandAction Command="{Binding NavigateCommand}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=TabControl}}" />
</b:EventTrigger>
</b:Interaction.Triggers>
<TabControl.ContentTemplate>
<DataTemplate>
<TabItem Margin="0,25,0,0">
<TabItem.Header>
<StackPanel Width="auto" Height="auto">
<materialDesign:PackIcon Width="24" Height="24" HorizontalAlignment="Center"
Kind="{Binding Icon}" />
<TextBlock HorizontalAlignment="Center" Text="{Binding Title}" />
</StackPanel>
</TabItem.Header>
</TabItem>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</materialDesign:Card>
This is what it looks like now
If the data template is not used, he will show this
You should be using ItemTemplate instead of ContentTemplate
<TabControl.ItemTemplate>
<DataTemplate>
<TabItem Margin="0,25,0,0">
<TabItem.Header>
<StackPanel Width="auto" Height="auto">
<materialDesign:PackIcon Width="24" Height="24" HorizontalAlignment="Center"
Kind="{Binding Icon}" />
<TextBlock HorizontalAlignment="Center" Text="{Binding Title}" />
</StackPanel>
</TabItem.Header>
</TabItem>
</DataTemplate>
</TabControl.ItemTemplate>

How to create dynamic Tabs with Tab Control with Dynamic Views in WPF

The software I am working is for a printing company. I have a total of 5 different Views/VMs that I could possibly display in a single TabItem inside of the TabControl. I am creating a list of them on the TabControlViewModel I have created. So for example List - FormsViewModel, PlasticsViewModel, LabelsViewModel; This list should produce 3 Dynamic Tabs containing their respective View. On top of this I was hoping to be able to actually develop it so that I have a different view from this list as the first tab, then the last tab would also have a different view from the list. Basically 2 tabs that would surround the list of dynamic tabs. Here is the code I have been messing around with so far.
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type jobDetails:JobProductionAddOnViewModel}">
<jobDetails:JobProductionAddOnView />
</DataTemplate>
<DataTemplate DataType="{x:Type forms:FormsDetailViewModel}">
<forms:FormsDetailView />
</DataTemplate>
<DataTemplate DataType="{x:Type labels:LabelsDetailViewModel}">
<labels:LabelsDetailView />
</DataTemplate>
<DataTemplate DataType="{x:Type plastics:PlasticsDetailViewModel}">
<plastics:PlasticsDetailView />
</DataTemplate>
<DataTemplate DataType="{x:Type specialtyCoatings:SpecialtyCoatingsDetailViewModel}">
<specialtyCoatings:SpecialtyCoatingsDetailView />
</DataTemplate>
<DataTemplate DataType="{x:Type digitalLabels:DigitalLabelDetailViewModel}">
<digitalLabels:DigitalLabelDetailView />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<TabControl ItemsSource="{Binding AdditionalDetailsViewModelList}"
controls:TabControlHelper.IsUnderlined="True"
controls:TabControlHelper.Transition="Left"
TabStripPlacement="Left"
SelectedIndex="{Binding SelectedIndex}"
ItemContainerStyle="{StaticResource ProductionTabItem}">
<TabItem>
<TabItem.Header>
<Label Content="Primary"
Width="100"
HorizontalContentAlignment="Center"/>
</TabItem.Header>
<AdornerDecorator>
<ContentControl Content="{Binding PrimaryDetailsViewModel}" />
</AdornerDecorator>
</TabItem>
<!--I have tried adding an ItemsControl here, didn't work-->
<!--I have also tried adding ItemsSource and binding it to the dynamic list-->
<!--But can't figure out how to change the view based on the type of viewmodel like-->
<!--you would with an itemscontrol-->
<TabItem>
<TabItem.Header>
<Label Content="+"
Width="100"
HorizontalContentAlignment="Center"/>
</TabItem.Header>
<AdornerDecorator>
<ContentControl Content="{Binding JobProductionAddOnViewModel}" />
</AdornerDecorator>
</TabItem>
</TabControl>
</Grid>
This was the solution I came to. And I got most of my idea from this post here. The ViewModel property is an interface that my ViewModels inherit called IBaseViewModel.
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type jobDetails:JobProductionAddOnViewModel}">
<jobDetails:JobProductionAddOnView />
</DataTemplate>
<DataTemplate DataType="{x:Type forms:FormsDetailViewModel}">
<forms:FormsDetailView />
</DataTemplate>
<DataTemplate DataType="{x:Type labels:LabelsDetailViewModel}">
<labels:LabelsDetailView />
</DataTemplate>
<DataTemplate DataType="{x:Type plastics:PlasticsDetailViewModel}">
<plastics:PlasticsDetailView />
</DataTemplate>
<DataTemplate DataType="{x:Type specialtyCoatings:SpecialtyCoatingsDetailViewModel}">
<specialtyCoatings:SpecialtyCoatingsDetailView />
</DataTemplate>
<DataTemplate DataType="{x:Type digitalLabels:DigitalLabelDetailViewModel}">
<digitalLabels:DigitalLabelDetailView />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<TabControl ItemsSource="{Binding TabItems}"
controls:TabControlHelper.IsUnderlined="True"
controls:TabControlHelper.Transition="Left"
TabStripPlacement="Left"
SelectedIndex="{Binding SelectedIndex}"
ItemContainerStyle="{StaticResource ProductionTabItem}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"
Width="150"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding ViewModel}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>

Use 2 ObservableCollection to fill TabControl : one for items, one for Content

In my ViewModel, I have 2 ObservableCollection of different types (ListeEquipements and ListeTypeEquipements). What I try to do is using my first collection to create Items of my Tabcontrol, and fill each tab content with properties of my second collection.
Details of my window (actually my UserControl) :
<UserControl.DataContext>
<vm:EquipementsViewModel/>
</UserControl.DataContext>
<TabControl Grid.Row="1"
TabStripPlacement="Bottom"
ItemsSource="{Binding ListeTypeEquipements}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Nom}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding ListeEquipements}">
<DataGrid.Columns>
<DataGridTextColumn Header="Tag" Binding="{Binding ListeEquipements.Tag}" />
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I've managed to make it work a bit better, doing like that :
<TabControl Grid.Row="1"
TabStripPlacement="Bottom"
ItemsSource="{Binding ListeTypeEquipements}"
>
<TabControl.Resources>
<CollectionViewSource x:Key="Equipements" Source="{Binding ListeEquipements}"/>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Nom}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Label Content="{Binding Source={StaticResource Equipements}, Path=Tag}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
My new problem is that i want my ContentTemplate to list each Equipement of ListeEquipement, by now i can only display the first member.

Bind Count of ItemsSource of an ItemsControl in a TextBlock using WPF

I want to bind a Count of ItemsSource of an ItemsControl in a TextBlock using WPF.
Have a Look into my tried Code
<Menu>
<MenuItem>
<MenuItem.Header>
<TextBlock Text="{Binding Path=(ItemsControl.ItemsSource.Item, RelativeSource={RelativeSource TemplatedParent}}" />
</MenuItem.Header>
<ItemsControl ItemsSource="{Binding PersonCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal" Margin="2" MinWidth="100">
<TextBlock Text="{Binding Value.Text}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</MenuItem>
</Menu>
Note: I need to get the count based on ItemsControl ItemsSource not by the Collection.Count Property. Kindly assist me.
This is the solution:
<Menu>
<MenuItem>
<MenuItem.Header>
<TextBox Text="{Binding ElementName=ItemsControl, Path=Items.Count, Mode=OneWay}" />
</MenuItem.Header>
<ItemsControl x:Name="ItemsControl"
ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Margin="2"
MinWidth="100">
<TextBlock Text="{Binding Value.Text}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</MenuItem>
</Menu>
Does it work for you?

Use different datatemplate by binding to different TreeView selectedItem

I have built a WPF MVVM TreeView that shows different Elements.
BaseElement
- CatA
-- SubItemA
- CatB
-- SubItemB
Based on the class I would like to use a different data template.
for each type.
So far I can connect to the selected Item, but I'm not sure how to manage the different data templates.
public class SubItem
{
public string Type { get; set; }
public string Name { get; set; }
}
<StackPanel Grid.Column="2" DataContext="{Binding ElementName=myTreeView, Path=SelectedItem}">
<TextBox Text="{Binding Parent.Name}" />
<TextBox Text="{Binding Path=Name, Mode=TwoWay}" />
</StackPanel>
[Update Nov. 15]
<HierarchicalDataTemplate x:Key="L3Template" ItemsSource="{Binding L4Collection}" ItemTemplate="{StaticResource L4Template}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="CategoryTemplate" ItemsSource="{Binding L3Collection}" ItemTemplate="{StaticResource L3Template}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="L1Template" ItemsSource="{Binding CategoryCollection}" ItemTemplate="{StaticResource CategoryTemplate}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
[/Update Nov. 15]
If the subitems are different classes, then it is rather simple: add datatemplates foreach class to the resource section.
If subitems need different templates based on the value of an enum prop, then you will need a datatemplateselector. This is a bit more cumbersome.
Assuming that you named your classes L1Class, L3Class en Category and local points to the namespace of these classes:
<TreeView ...>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:L3Class}" ItemsSource="{Binding L4Collection}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Category}" ItemsSource="{Binding L3Collection}" >
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:L1Class}" ItemsSource="{Binding CategoryCollection}" >
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Note the use of the implicit datatemplates (no keys, but DataType!) in the resource section.
Just in case it will help someone else:
<ContentPresenter Grid.Column="2" Content="{Binding ElementName=myTreeView, Path=SelectedItem}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:L1ViewModel}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:CategoryViewModel}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:L3ViewModel}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:L4ViewModel}">
<StackPanel>
<TextBox Text="{Binding Parent.Name}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>

Categories