WPF Item Templates - TabControl - c#

I am writing an application in which I utilize a tab control which will start with one tab open but allows the user to open multiple other tabs.
Each tab that is openned should have a treeview inside which I fill using databinding when the user loads a file.
I am new to WPF but I feel as if there is a way in which I can create a template containing each of the elements the TabItems should contain. How can I do this using templates? Right now my WPF for the tab items is the following
<TabItem Header="Survey 1">
<TreeView Height="461" Name="treeView1" VerticalAlignment="Top"
Width="625" Margin="0,0,6,0" ItemTemplateSelector="{StaticResource TreeviewDataSelector}" />
</TabItem>

I think you want something like this:
<Window x:Class="TestWpfApplication.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">
<Window.Resources>
<DataTemplate x:Key="TabItemTemplate">
<TreeView Height="461" Name="treeView1" VerticalAlignment="Top"
Width="625" Margin="0,0,6,0" ItemTemplateSelector="{StaticResource TreeviewDataSelector}" />
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding ListThatPowersTheTabs}"
ItemTemplate="{StaticResource TabItemTemplate}">
</TabControl>
</Grid>
You basically create re-usable templates as static resources which you refer to by their key name.

Usually in this sort of situation I bind my TabControl.ItemsSource to a ObservableCollect<ViewModelBase> OpenTabs, so my ViewModel is in charge of adding/removing new tabs as needed.
Then if you want something in every Tab, then overwrite the TabControl.ItemTemplate and use the following line to specify where to display the currently selected TabItem
<ContentControl Content="{Binding }" />
If you don't need to setup something in every single tab, you don't need to overwrite the TabControl.ItemTemplate - it will default to displaying the currently selected ViewModelBase in the Tab.
And I use DataTemplates to specify which View to use
<DataTemplate TargetType="{x:Type local:TabAViewModel}">
<local:TabAView />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:TabBViewModel}">
<local:TabBView />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:TabCViewModel}">
<local:TabCView />
</DataTemplate>

Related

Command- and InputBindings in a DataTemplate

I have a WPF UserControl that I'm trying to adapt to my program with kind of a MVVM pattern. Now the problem is, that the UserControl uses Command- and InputBindings which I can't simply apply to a DataTemplate. What options do I have to implement the same functionality without even having a code-behind?
Here's the XAML as of right now:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject.Settings"
xmlns:elements="clr-namespace:MyProject.Settings.Elements"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:dataPresenter="http://infragistics.com/DataPresenter"
xmlns:igEditors="http://infragistics.com/Editors"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<DataTemplate x:Key="PrioritySettingTemplate" DataType="{x:Type elements:PrioritySetting}">
<ContentPresenter>
<ContentPresenter.InputBindings>
<KeyBinding Gesture="Delete" Key="Delete"
Command="{Binding TestCommand}" />
</ContentPresenter.InputBindings>
<ContentPresenter.ContentTemplate>
<DataTemplate DataType="{x:Type elements:PrioritySetting}">
<!--
Main part of the UserControl
-->
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</DataTemplate>

Programmatically add DataTemplate to resource dictionary

<Window x:Class="App1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cust="clr-namespace:App1.Customers"
Title="Customers"
Height="525"
Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type cust:CustomerViewModel}">
<cust:CustomerView />
</DataTemplate>
</Window.Resources>
<Grid x:Name="MainContent">
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
</Window>
I've declared the datatemplate above in xaml. Over time there could be 20 viewmodels and views the main window might need to know about. I'd rather pass responsibility for adding the datatemplate(s) to the resource dictionary to somewhere else. How can I achieve the above in c#? Just the bit which adds the datatemplate to the resource dictionary

The XAML code expect my Command in different VM

On the main window xaml, I have a Frame that will host 2 pages, like this
<Window x:Class="Monitor.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" FontSize="14">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="48"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Frame Content="Frame" Grid.Row="1" Source="/Monitor;component/Views/Pages/GroupPage.xaml"/>
</Grid>
</Window>
On the first page (GroupPage.xaml) xaml code is
<Page.DataContext>
<VM:GroupPageVM />
</Page.DataContext>
<Grid>
<ItemsControl x:Name="GroupsOfItemsControl" ItemsSource="{Binding GroupsOfItems}">
<ItemsControl.Template>
<ControlTemplate>
<WrapPanel Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"
FlowDirection="LeftToRight" IsItemsHost="true">
</WrapPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Right" Margin="3"
Content="{Binding Name}"
Width="1.2in" Height=".75in" FontSize="14"
Command="{Binding Path=GroupSelectedCommand }"
CommandParameter= "{Binding}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The GroupPageVM is the VM for this page. GroupsOfItems is a collection of VM for groups. Each Group VM has a Name where I bind it for the content of a button that represent for that group. So on the screen, I will see a collection of buttons in a WrapPanel. Everything display correctly.
Now the issue come with the command that handle the click on the button, the code expect I put my Command (GroupSelectedCommand) and its execute function inside the VM of the Group, instead in the MainWindow VM.
Can someone explain why?
If it is how it suppose to work, how do I put the command in the MainWindow VM? because without that I cannot access to the Frame that host the pages or any NavigationService to go to page 2 by clicking on one of those buttons.
(My simple goal is clicking on a button that represent a group, and it will navigate to a page to display items in that group)
You have bound GroupsOfItems as the ItemsSource of the ItemsControl. Inside GroupsOfItems you have GroupVM objects. So each item inside the ItemsControl will be holding a GroupVM object. Also the DataTemplate defined will be representing the GroupVM object. Thus the Command will be expected inside GroupVM object.
To access parent DataContext, you can try giving ElementName as shown below,
Command="{Binding DataContext.GroupSelectedCommand, ElementName=GroupsOfItemsControl }"
In the above code the button will try to take the Command from the DataContext of ItemsControl which is the GroupPageVM

Set a ContextMenu UserControl for a Grid

I have created a custom context menu in a separate user Control Class CustomContextMenuUc.
The simple version of the code looks like this.
<ContextMenu x:Class="CustomContextMenu.CustomContextMenuUc"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ItemsSource="{Binding Groups}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Description.Value}" />
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.OverridesDefaultStyle>True</ContextMenu.OverridesDefaultStyle>
<ContextMenu.Placement>Custom</ContextMenu.Placement>
</ContextMenu>
I want to use this CustomContextMenuUc in multiple Windows. I am able to assign this User control in C# code like
ContextMenuGrid.ContextMenu = new CustomContextMenuUc();
Where ContextMenuGrid is a grid.
I want to do this in XAML How should I do it?
<DataGrid ContextMenu="">
You will have to define the Resource in resources of your window like
<local:CustomContextMenuUc x:Key="MyContextMenu"/>
here local is the xmlns where your contextmenu is defined.
Then you can do
<DataGrid ContextMenu="{StaticResource MyContextMenu}">
Thanks

UserControl CollectionViewSource not working

I have a user-control containing this XAML
<UserControl x:Class="QA.JobListControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:QA" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<CollectionViewSource x:Name="itemsSource" IsSourceGrouped="True" />
</UserControl.Resources>
<ListView x:Name="JobListView" Margin="-10,-10,0,0" Padding="120,0,0,60" IsSwipeEnabled="False" ItemsSource="{Binding Source=itemsSource}" SelectionChanged="JobListView_SelectionChanged" SelectionMode="Single">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border HorizontalAlignment="Stretch">
<TextBlock Text='{Binding Status}' Margin="10" />
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<TextBlock Text='{Binding TaskName}' />
<TextBlock Text='{Binding DueDate}' />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
And to set the content I am using this C#-code
itemsSource.Source = Tasks.OrderBy(Tsk => Tsk.DueDate).GroupBy(Tsk => Tsk.Status);
It is showing some of the elements (but they are shown as empty elements), and not all are shown
What could be wrong?
If I am using this C#-code it is working (but it is not grouped)
JobListView.ItemsSource = Tasks.OrderBy(Tsk => Tsk.DueDate);
UPDATE
After adding the StaticResource like below, it now shows multiple groups without items
ItemsSource="{Binding Source={StaticResource itemsSource}}"
So I think you are misunderstanding the basics behind the GroupBy method. GroupBy, as opposed to most other Linq extensions, will not return a simple list of objects, instead it will return a list of IGrouping. IGrouping interface exposes a Key property which will hold the value of the grouping discriminator you passed in the GroupBy lambda.
Therefore, to get the list to display the group name, you have to bind the group header template to Key instead of Status.
<TextBlock Text='{Binding Key}' Margin="10" />
Also if reference your CollectionViewSource as a resource, you need to define a resource key to reference it later in your XAML as a StaticResource.
<CollectionViewSource x:Name="itemsSource" x:Key="groupedTasks" IsSourceGrouped="True" />
And in the list view.
<ListView x:Name="JobListView" ItemsSource="{Binding Source={StaticResource groupedTasks}}">
This way I got your example to work as expected.
As an additional read I dearly recommend you to read this article by Sergei Barskiy which demonstrates how to use grouping in XAML lists and also provides a GroupedData class that in my opinion is much better than the default IGrouping object to expose your data and consume it in the UI.

Categories