Search code examples
c#wpfxamlmvvmtabcontrol

How to fill each tab of the tab control's itemslist with one user control dynamically from the mainviewmodel


My MainView contains a TabControl with an ItemTemplate and a ContentTemplate. The TabControl's ItemsSource is bound to the Property ObservableCollection<TabViewModel> TabCollection in my MainViewModel.

TabViewModel:

    namespace LuxUs.ViewModels
{
    public class TabViewModel
    {
        public string Name { get; set; }
        public object VM {get; set;}      
        
        public TabViewModel(string name)
        {
            Name = name;
        }
        public TabViewModel(string name, object vm)
        {
            Name = name;
            VM = vm;          
        }        
    }
}

I want to create the tabs with their tab's header AND content dynamically from the MainViewModel like this...:

MainViewModel:

    using System.Collections.ObjectModel;

namespace LuxUs.ViewModels
{
    public class MainViewModel : ObservableObject, IPageViewModel
    {
        
        public ObservableCollection<TabViewModel> TabCollection { get; set; }

        public MainViewModel()
        {
            TabCollection = new ObservableCollection<TabViewModel>();
            TabCollection.Add(new TabViewModel("Dachdefinition", new DachdefinitionViewModel()));
            TabCollection.Add(new TabViewModel("Baukörperdefinition"));
            TabCollection.Add(new TabViewModel("Fassade"));
            TabCollection.Add(new TabViewModel("Raumdefinition"));
            TabCollection.Add(new TabViewModel("Treppenloch | Galerieöffnung"));
            
        }       
    }
}

View:

<UserControl  x:Class="LuxUs.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:LuxUs.Views"
             xmlns:models="clr-namespace:LuxUs.Models"
             xmlns:vm="clr-namespace:LuxUs.ViewModels"
             mc:Ignorable="d">
    <Grid>
        <Grid>
            <TabControl Style="{DynamicResource TabControlStyle}" ItemsSource="{Binding TabCollection}" >
              
                <TabControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}"/>
                    </DataTemplate>
                </TabControl.ItemTemplate>
                <TabControl.ContentTemplate>
                    <DataTemplate>
                        <UserControl>
                            <ContentControl Content="{Binding VM}" />
                        </UserControl>
                    </DataTemplate>
                </TabControl.ContentTemplate>
            </TabControl>
        </Grid>
    </Grid>
</UserControl>

... but the content of each tab won't show. Instead, I get this text. The ViewModel ist the correct one but it should load the view instead of showing this text, of course:

Screenshot of the View

The first tab's ViewModel DachdefinitionViewModel has only an empty constructor:

    using System.Collections.ObjectModel;

namespace LuxUs.ViewModels
{
    public sealed class DachdefinitionViewModel : ObservableObject
    {       
        public DachdefinitionViewModel()
        {
  
        }  
    }
}

And here is its view Dachdefinition.xaml:

    <UserControl x:Class="LuxUs.Views.Dachdefinition"
             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:LuxUs.Views"
             xmlns:vm="clr-namespace:LuxUs.ViewModels"
             mc:Ignorable="d">
    <UserControl.DataContext>
        <vm:DachdefinitionViewModel></vm:DachdefinitionViewModel>
    </UserControl.DataContext>
    <Grid Margin="50">
...
...
...
 </Grid>
</UserControl>

Is the binding correct here or do I need to bind differently? Why is the view not showing up inside the first tab?


Solution

  • you need to connect view model with correct view via DataTemplate.

    DataTemplate provides visual representation for data type which doesn't have it (your view model). If you don't specify DataTemplate, you will get default one: TextBlock with string representation of object (result of ToString() method, default to type name).

    <Grid>
        <Grid.Resources>
             <DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
                  <views:Dachdefinition />
             </DataTemplate>
        </Grid.Resources>
    
        <TabControl Style="{DynamicResource TabControlStyle}" 
                    ItemsSource="{Binding TabCollection}" >
          
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <UserControl>
                        <ContentControl Content="{Binding VM}" />
                    </UserControl>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </Grid>