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>

In Prism you usually make the tab control a region so that you don't have to take control over the bound tab page collection.

<TabControl 
    x:Name="MainRegionHost"
    Regions:RegionManager.RegionName="MainRegion" 
    />

Now the views can be added via registering itself into the region MainRegion:

RegionManager.RegisterViewWithRegion( "MainRegion", 
    ( ) => Container.Resolve<IMyViewModel>( ).View );

And here you can see a speciality of Prism. The View is instanciated by the ViewModel. In my case I resolve the ViewModel throught a Inversion of Control container (e.g. Unity or MEF). The ViewModel gets the View injected via constructor injection and sets itself as the View's data context.

The alternative is to register the view's type into the region controller:

RegionManager.RegisterViewWithRegion( "MainRegion", typeof( MyView ) );

Using this approach allows you to create the views later during runtime, e.g. by a controller:

IRegion region = this._regionManager.Regions["MainRegion"];

object mainView = region.GetView( MainViewName );
if ( mainView == null )
{
    var view = _container.ResolveSessionRelatedView<MainView>( );
    region.Add( view, MainViewName );
}

Because you have registered the View's type, the view is placed into the correct region.


I have a Converter to decouple the UI and ViewModel,thats the point below:

<TabControl.ContentTemplate>
    <DataTemplate>
        <ContentPresenter Content="{Binding Tab,Converter={StaticResource TabItemConverter}"/>
    </DataTemplate>
</TabControl.ContentTemplate>

The Tab is a enum in my TabItemViewModel and the TabItemConverter convert it to the real UI.

In the TabItemConverter,just get the value and Return a usercontrol you need.


Possibly so :

<UserControl x:Class="Test_002.Views.MainView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Test_002.Views"
         xmlns:generalView="clr-namespace:Test_002.Views.General"
         xmlns:secVIew="clr-namespace:Test_002.Views.Security"
         xmlns:detailsView="clr-namespace:Test_002.Views.Details"
         mc:Ignorable="d" 
         d:DesignHeight="400" d:DesignWidth="650">
<Grid>
    <DockPanel>
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Margin="2,5">
            <Button Command="{Binding btnPrev}" Content="Prev"/>
            <Button Command="{Binding btnNext}" Content="Next"/>
            <Button Command="{Binding btnSelected}" Content="Selected"/>
        </StackPanel>
        <TabControl>
            <TabItem Header="General">
                <generalView:GeneralView></generalView:GeneralView>
            </TabItem>
            <TabItem Header="Security">
                <secVIew:SecurityView></secVIew:SecurityView>
            </TabItem>
            <TabItem Header="Details">
                <detailsView:DetailsView></detailsView:DetailsView>
            </TabItem>
        </TabControl>
    </DockPanel>
</Grid>

Think this is the easiest way. Is the MVVM Compatible ?


Comments

  1. Bertrand

    • 2020/11/20

    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 

  2. Maurice

    • 2020/3/10

    In Prism you usually make the tab control a region so that you don't have to take control over the bound tab page collection. <TabControl x:Name="MainRegionHost" Regions:RegionManager.RegionName="MainRegion" />. Now the views can be added via registering itself into the region MainRegion:

  3. Blaze

    • 2019/2/19

    Free source code and tutorials for Software developers and Architects.; Updated: 23 Dec 2012.

  4. Kellan

    • 2015/10/9

    The viewmodels can be of different types, derived from the same base type, of course. When a viewmodel is added to the list I want the tabcontrol adds a new tabpage based on the type of the view model. I do not understand how to set up the ContentTemplate of the TabControl to pick the right view based on the type of the view model.

  5. Adrien

    • 2015/4/14

    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, 

  6. Ariel

    • 2018/9/5

    Most importantly, I'm binding a TabControl to an ObservableCollection of ViewModels. This means that am using a tabbed MDI interface that displays a UserControl as the content of a TabItem. The issue I'm seeing in my application is that when I have several tabs and I flip back and forth between tabs, the content is being refersh each time I

  7. Kristian

    • 2016/4/11

    As you can see the ViewModel class has a reference to an observable collection of Person objects. In fact this will be set to the ItemsSource property of the 

  8. Zaire

    • 2017/3/3

    Copy Code. System.Windows.Controls.TabItem i = new System.Windows.Controls.TabItem (); i.Content = new Views.ViewStartPage (); i.Header = "A Tab Item" ; this .xTabControl.Items.Add (i); The C# code above is what I want to accomplish through binding the view models.

  9. Wood

    • 2018/4/28

    public sealed class ViewModel { public ObservableCollection<TabItem> Tabs {get;set;} <CollectionContainer Collection="{Binding DataContext.

  10. Cayden

    • 2015/12/2

    class ViewModelMain : ViewModelBase { #region Private properties private ObservableCollection<ViewModelBase> _tabItems = new ObservableCollection<ViewModelBase>(); private ViewModelBase _selectedTab; #endregion public ViewModelMain() { //fill tab collection with views TabItems.Add(new ViewModelUserControlGmcMain("GMC")); TabItems.Add(new ViewModelUserControlScuMain("SCU")); this.SelectedTab = TabItems[1]; //select the second tab by default } #region Public properties public

  11. Turner

    • 2020/3/24

    As you can see, we create a collection of the view models which will be used to populate the ItemsSource on the TabControl. Let's take a look at 

  12. Lombardo

    • 2017/5/5

    Since the ViewModels are drastically different I had to be as generic as possible for the root collection and then use a ContentTemplateSelector to show the appropriate content. The issue I have is occasionally one of the tabs is duplicated, even though the underlying source doesn't reflect this.

  13. Jayson

    • 2017/11/20

    Hi,. I am using a TabControl with MVVM pattern to create tabs by binding the ItemsSource property to a collection of viewmodels.

  14. Wade

    • 2017/4/17

    On your MainViewModel you keep a collection of your tab viewmodels: <TabControl> <TabItem DataContext="{Binding Children[0]}" x:Name="Tab1" 

  15. Maison

    • 2017/3/5

    To bind ItemsSource to TabControl , you need to have collection with data Here, Model class defined with Header and Content properties and ViewModel 

  16. Aryan

    • 2015/9/25

    However, the default WPF TabControl doesn't support adding and Resources> tags are the DataTemplate's associating a ViewModel type with 

Comments are closed.

Recent Posts