13
votes

I have a Usercontrol(TabUserControl) which contains a TabControl. The Viewmodel of that UserControl loads ab Observable collection of TabItems. One od those items is another user control. When I just load text in the tabcontrol there is no problem, but how can I load the other user control into the tabitem of the TabUserControl. I'm using MVVM.

Here's my code:

public class TabItem
{
    public string Header { get; set; }
    public object Content { get; set; } // object to allow all sort of items??
}

The Viewmodel of the TabUserControl

public class TabViewModel
{
    public ObservableCollection<TabItem> Tabs {get;set;}

    public TabViewModel()
    {
        Tabs = new ObservableCollection<TabItem>();
        //Tabs.Add(new TabItem { Header = "Overview", Content = new OverviewViewModel() }); How to load a usercontrol here if it's in the ItemCollection?
        Tabs.Add(new TabItem { Header = "Overview", Content = "Bla bla bla" });
        Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
    }
}

And then the TabControl XAML:

<TabControl x:Name="_tabControl"
            ItemsSource="{Binding Tabs}">
  <TabControl.ItemContainerStyle>
    <Style TargetType="TabItem">
      <Setter Property="Header"
              Value="{Binding Header}" />
      <Setter Property="Content"
              Value="{Binding Content}" />
    </Style>
  </TabControl.ItemContainerStyle>
</TabControl>

It works as long as I dont load the viewmodel of the usercontrol in the tabItems collection. how can I make the UserTabControl load on to the TabItem? The intention is that every tabitem will contain a usercontrol. Each usercontrol then does it's own thing.

Hope someone can help me as I am a WPF beginner. Thx!

2

2 Answers

37
votes

Ideally, the TabControl.ItemsSource should be set to a collection of ViewModels, and DataTemplates should be used to tell the WPF to draw each ViewModel with a specific UserControl.

This keeps between your business logic (ViewModels) completely separate from your UI (Views)

For example,

<TabControl x:Name="MyTabControl"
            ItemsSource="{Binding TabViewModels}"
            SelectedItem="{Binding SelectedTabViewModel}">

    <TabControl.Resources>
        <DataTemplate DataType="{x:Type my:ViewModelA}">
            <my:ViewAUserControl />
        </DataTemplate>
        <DataTemplate DataType="{x:Type my:ViewModelB}">
            <my:ViewBUserControl />
        </DataTemplate>
        <DataTemplate DataType="{x:Type my:ViewModelC}">
            <my:ViewCUserControl />
        </DataTemplate>
    </TabControl.Resources>

    <TabControl.ItemContainerStyle>
        <Style TargetType="TabItem">
            <Setter Property="Header" Value="{Binding Header}" />
        </Style>
    </TabControl.ItemContainerStyle>

</TabControl>

ViewModel containing TabControl's DataContext:

TabViewModels = new ObservableCollection<ITabViewModel>();
TabViewModels.Add(new ViewModelA { Header = "Tab A" });
TabViewModels.Add(new ViewModelB { Header = "Tab B" });
TabViewModels.Add(new ViewModelC { Header = "Tab C" });

SelectedTabViewModel = TabViewModels[0];
5
votes

Thanks Rachel for your answer. But it enforces declaring the DataContext during compile time itself. Like you did, relating each of the Views to their respective ViewModels in the DataTemplate of TabControl. We can achieve dynamic View-ViewModel linking when move this out to ViewModel. Here's how:

XAML:

<TabControl.ItemContainerStyle>
      <Style TargetType="TabItem">
          <Setter Property="Header" Value="{Binding Header}" />
          <Setter Property="Content" Value="{Binding Content}" />
      </Style>
</TabControl.ItemContainerStyle>

VM:

 public ObservableCollection<TabItem> TabItems { get; set; }
 public MainWindowViewModel()
        {
            TabItems = new ObservableCollection<TabItem>
            {
                new TabItem{Content = new TabAView() {DataContext = new TabAViewModel()}, Header = "Tab A"},
                new TabItem{Content = new TabBView(), Header = "Tab B"}
            };
        }

We can even make use of Action delegates to delay and invoke initialization of the TabItems only upon Tab SelectionChangedEvent. This achieves lot of memory saving if the UserControl Views have many UI elements.