24

I have a main window with a tab control containing 2 tabItems:

Main Window

I currently have 1 ViewModel which services Tab1 & Tab2. This ViewModel is becoming a little bloated with blurred SOC. I want to split the logic into 2 viewmodels: ViewModel 1 & ViewModel2. My understanding is that you can set the Main Window DataContext to a Base ViewModel which holds a collection of ViewModels & then you can assert each TabItem to a different ViewModel.

The example's I've seen of these base ViewModels expose an ObservableCOllection like so:

private ObservableCollection<ViewModel1> _viewModelCollection
Public Observable Collection<ViewModel1> ViewModelCollection
{
   get { return _viewModelCollection; }
   set
     {
        _viewModelCollection = value;
        OnPropertyChanged("ViewModelCollection");
     }
}

public BaseViewModel()
{
  ViewModelCollection = new ObservableCollection<ViewModel1>();
  ViewModelCollection.Add(new ViewModel1(Tab1);
  ViewModelCollection.Add(new ViewModel1(Tab2);
}

But how do I assign a different ViewModel to each TabItem? I would want Tab1= ViewModel1 & Tab2=ViewModel2?

Jakob Christensen
  • 14,826
  • 2
  • 51
  • 81
Hardgraf
  • 2,566
  • 4
  • 44
  • 77
  • I'm not sure there's a definitive best practice for this. It doesn't do you much good to bind the `ItemsSource` of the `TabControl` to your `ViewModelCollection`, as there is no common 'template' (each tab presumably has its own view). If you have a fixed number of tabs, I would simply bind the `DataContext` of each tab to the corresponding view model and set each tab's content to the appropriate view. If the tabs are dynamic, things become trickier. – Mike Strobel Oct 06 '14 at 14:52
  • Did you try search? [1](http://stackoverflow.com/q/14009074/1997232), [2](http://stackoverflow.com/q/12432062/1997232), [3](http://stackoverflow.com/q/17952321/1997232) ... – Sinatr Oct 06 '14 at 14:59
  • 1
    @Sinatr yes I have read those questions. I want to bind to different ViewModels not a collection of difference instances of the same ViewModel... – Hardgraf Oct 06 '14 at 15:07

5 Answers5

39

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>
Jakob Christensen
  • 14,826
  • 2
  • 51
  • 81
8
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; } }
}

Now in XAML bind the Children to ItemsSource. It will generate each Tab for every viewmodel we have added into the observable collection

    <TabControl ItemsSource="{Binding Children}"/>
Varatharaj
  • 688
  • 6
  • 12
  • This code snippet is an exact copy of Jakob’s above. – darkstar3d Oct 28 '18 at 17:54
  • 2
    This may be a copy of Jakob's code, but this answer is more complete. It's strongly typed (not using `object` or `Children[x]`), and actually shows the OP how to do what he wanted to do. This should be the accepted answer IMO. – Yann Duran Jul 03 '19 at 06:31
4

I use a framework such as Prism, that allows you to define regions and use the RegionManager. You can then define a ContentControl as the 'ui' for the TabItem

Then you can use the RegionManager.RequestNavigate to populate a named region with a particular view (and our views import a viewmodel and set their datacontext).

Mashton
  • 6,037
  • 2
  • 25
  • 35
  • Yeah, I've had a tinker with Prism. This app is fairly simple though, don't think it's worth setting up the bootstrapper etc. for this. – Hardgraf Oct 06 '14 at 15:08
  • I suppose I could try a base model with a ObservableCollection. Add an instance of my different ViewModels to it & then bind the tab items to the respective ViewModel instance in the collection? – Hardgraf Oct 06 '14 at 15:09
  • 1
    you could bind the items source from your tab control to a list of tabViewModels that will be shown in the UI. The TabVMs could inherrit from a BaseTabVM where all common things are in and eventually, create a data template for each tab view model in the resource of your project so that they can be individually customized. The binding will go in each individual tabVM, you have a list of BaseTabVMs in the main window view model to bind to and all items can be independently used. – Olimpiu Datcu Oct 06 '14 at 15:17
1

For people finding this page with the same question, I found a video on YouTube: WPF MVVM - Working with tab controls and tab content, by a guy from DCOM Engineering, LLC.

It gives a very good explanation of "how to bind a different ViewModel to each TabItem", step by step, with downloadable code.

"Learn how to create, open, and close tabs effectively using the MVVM pattern with WPF. Facilitates unit testing."

Yann Duran
  • 3,842
  • 1
  • 24
  • 24
1

No sure why the selected answer is suggesting to use ObservableCollection and create a collection of tabs and then reference them by index.

I find it more clean to do it this way:

MainViewModel tab1= ConfigViewModel Tab2 = UserProfileViewModel

in MainViewModel:

private ConfigViewModels _configVM;
public ConfigViewModels ConfigVM { get { return _configVM; } }

private UserProfileViewModel _userProfileVM;
public UserProfileViewModel UserProfileVM { get { return _userProfileVM; } }

public MainViewModel(){

_configVM = new ConfigViewModels();
_userProfileVM = new UserProfileViewModel ();
}

In th XAML file:

<TabControl>
<TabItem DataContext="{Binding ConfigVM }" >
<TabItem DataContext="{Binding UserProfileVM }" >

I'm not sure I understand the benefit of doing this through an observable collection.

Pat M
  • 41
  • 3