How to Bind a Tabcontrol to a Collection of Viewmodels

How do I bind a TabControl to a collection of ViewModels?

This isn't MVVM. You should not be creating UI elements in your view model.

You should be binding the ItemsSource of the Tab to your ObservableCollection, and that should hold models with information about the tabs that should be created.

Here are the VM and the model which represents a tab page:

public sealed class ViewModel
{
public ObservableCollection<TabItem> Tabs {get;set;}
public ViewModel()
{
Tabs = new ObservableCollection<TabItem>();
Tabs.Add(new TabItem { Header = "One", Content = "One's content" });
Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
}
}
public sealed class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
}

And here is how the bindings look in the window:

<Window x:Class="WpfApplication12.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">
<Window.DataContext>
<ViewModel
xmlns="clr-namespace:WpfApplication12" />
</Window.DataContext>
<TabControl
ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock
Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>

(Note, if you want different stuff in different tabs, use DataTemplates. Either each tab's view model should be its own class, or create a custom DataTemplateSelector to pick the correct template.)

A UserControl inside the data template:

<TabControl
ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock
Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<MyUserControl xmlns="clr-namespace:WpfApplication12" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

Binding collection of viewmodels to WPF MVVM TabControl

How can I bind a collection of ViewModels to populate the headers and content of a tab control without depending on the TabItem object?

Try this:

View:

<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

View Model:

public class TabViewModel
{
public TabViewModel()
{
Items = new ObservableCollection<Item>()
{
new Item { Header = "a", Content = "..." },
new Item { Header = "b", Content = "..." },
new Item { Header = "c", Content = "..." },
};
}

public ObservableCollection<Item> Items { get; set; }
}

Item is a POCO class.

WPF MVVM: Binding a different ViewModel to each TabItem?

You can indeed add the view models for your tabs to a main view model. You can then bind to the child view models in the XAML for your tabs.

Say that you have three viewmodels: MainViewModel, Tab1ViewModel, and Tab2ViewModel. On your MainViewModel you keep a collection of your tab viewmodels:

class MainViewModel
{
ObservableCollection<object> _children;

public MainViewModel()
{
_children = new ObservableCollection<object>();
_children.Add(new Tab1ViewModel());
_children.Add(new Tab2ViewModel());
}

public ObservableCollection<object> Children { get { return _children; } }
}

After setting the DataContext of your main window to your MainViewModel you can bind the DataContext of your tabs by referencing the Children property:

<TabControl>
<TabItem DataContext="{Binding Children[0]}" x:Name="Tab1" Header="Tab1" >
<!-- Tab content -->
</TabItem>
<TabItem DataContext="{Binding Children[1]}" x:Name="Tab2" Header="Tab2" >
<!-- Tab content -->
</TabItem>
</TabControl>

Binding TabControl ItemsSource to Collection of ViewModels

I ended up using a ContentControl with a TabControl data template (like the original tutorial project). Here is the xaml code I ended up with. I did not change the code-behind from the original sample project to make this work. The ContentControl is in my MainWindow.xaml and the other two pieces of code were in a ResourceDictionary.

<!-- Workspaces Tab Control -->
<ContentControl Grid.Row="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"/>

<!-- Workspaces Template -->
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl Style="{StaticResource ClosableTabControl}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource WorkspaceTabItemTemplate}"
Margin="1"/>
</DataTemplate>


<!-- Workspace Tab Item Template -->
<DataTemplate x:Key="WorkspaceTabItemTemplate">
<Grid Width="Auto" Height="Auto">
<ContentPresenter ContentSource="Header" Margin="3"
Content="{Binding Path=DisplayName}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource Foreground}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</DataTemplate>

How to bind a TabControl with a collection of TabItems

You would want to bind the ItemSource for the TabControl to you collection to generate the tabs. Something like this:

    <TabControl ItemsSource="{Binding TableAreaCollection}">
<TabControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding TableCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:TableViewModel}">
<Button Uid="{Binding TableName}" ContentStringFormat="{Binding TableGuestCount}" Style="{StaticResource ResourceKey=BtnTableEmpty}" Width="84" Height="90" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=TablePosX}" />
<Setter Property="Canvas.Top" Value="{Binding Path=TablePosY}" />
<Setter Property="Tag" Value="{Binding Path=TableID}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>

Tabcontrol MVVM light viewmodel binding

Don't use static fields in your MainViewModel, it's a bad design decision and makes your code not testable.

Instead, use the powerful data templating mechanism of WPF.

class MainViewModel : INotifyPropertyChanged
{
// Note: this is just a sample.
// You might want to inject the instances via DI
public IEnumerable<INotifyPropertyChanged> TabItems { get; } =
new[] { new PricesViewModel(), new LicensesViewModel() };
}

Use the data templates for your view models:

<TabControl ItemsSource="{Binding TabItems}" DisplayMemberPath="PropertyNameForTabHeader">
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewModels:PricesViewModel}">
<local:PricesControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:LicensesViewModel}">
<local:LicenseControl/>
</DataTemplate>
</TabControl.Resources>
</TabControl>

The DisplayMemberPath property specifies the name of the tab item's view model property to use as the tab's header.

With this approach, your design is dynamic and scalable.

How to bind WPF TabControl ContentTemplate to an observable collection of different ViewModels

Ok, i will modify the sample code in the answer you linked:

<Window.Resources>
<DataTemplate x:Key="templateForTheHeader" DataType="{x:Type vm:BaseViewModel}">
<TextBlock Text="{Binding CommonPropertyToDisplayInTheHeader}"/>
</DataTemplate>

<DataTemplate DataType="{x:Type vm:ViewModel1}">
<TextBlock Text="{Binding PropertyInVM1}"/>
</DataTemplate>

<DataTemplate DataType="{x:Type vm:ViewModel2}">
<TextBlock Text="{Binding PropertyInVM2}"/>
</DataTemplate>
</Window.Resources>

...

<TabControl ItemsSource="{Binding YourCollection}"
ItemTemplate="{StaticResource templateForTheHeader}">
</TabControl>

The header displays some property in the base VM class.
And the important thing, i removed the x:key of the other DataTemplates, this will make it being applied to every instance of the defined DataType found in the Window (ContentTemplate in the TabControl not needed).

YourCollection is a mix of objects, each of it will get its template applied based on its type if a DataTemplate with matching DataType exists. Simple uh?

Binding TabControl to collection of ViewModels

Header is specified by ItemTemplate, body is specified by ContentTemplate.

e.g.

<TabControl ItemsSource="{Binding DpData}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Occupation}" />
<TextBlock Text="{Binding Status}" />
<TextBlock Text="{Binding IsActive}" />
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

If you have trouble with bindings and datatemplates read the overviews first:

Data Binding Overview

Data Templating Overview

c# WPF MVVM TabControl with Multiple ViewModels and changing tabs

So I solved this with an EventAggregator.

public static class IntAggregator
{
public static void Transmit(int data)
{
if (OnDataTransmitted != null)
{
OnDataTransmitted(data);
}
}

public static Action<int> OnDataTransmitted;
}

First ViewModel sends data.

public class ModifyUsersViewModel : INotifyPropertyChanged
{
private void change_tab(int data)
{
IntAggregator.Transmit(data);
}
}

Second ViewModel receives data and then does something with it.

public class MainWindowViewModel : INotifyPropertyChanged
{
private int _Tab_SelectedIndex = 0;
public int Tab_SelectedIndex
{
get
{
return _Tab_SelectedIndex;
}
set
{
_Tab_SelectedIndex = value;
OnPropertyChanged(new PropertyChangedEventArgs("Tab_SelectedIndex"));
}
}

public MainWindowViewModel()
{
IntAggregator.OnDataTransmitted += OnDataReceived;
}

private void OnDataReceived(int data)
{
Tab_SelectedIndex = data;
}
}


Related Topics



Leave a reply



Submit