Search code examples
wpfmvvmdatatemplatetabcontrolcontentpresenter

How to resolve content presenter not displaying View?


I've added a content presenter to my TabControl's data template, in order to display the correct view.

But when I load the application, the tabs display but they have no user control content.

I Googled the error and came across this solution, that suggested an error with the data context but the setup seems ok in my AppVM and AppView below.

The names of both VM's and Views are also correct that I'm referencing in the AppView.

Does anyone have an idea where the setup has gone wrong here?

This is the ApplicationView that holds both views:

<Window x:Class="MongoDBApp.Views.ApplicationView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:views="clr-namespace:MongoDBApp.Views"
        xmlns:vm="clr-namespace:MongoDBApp.ViewModels"
        Title="ApplicationView"
        Width="800"
        Height="500">

    <Window.Resources>
        <DataTemplate DataType="{x:Type vm:CustomerDetailsViewModel}">
            <views:CustomerDetailsView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:CustomerOrdersViewModel}">
            <views:CustomerOrdersView />
        </DataTemplate>
    </Window.Resources>

    <Window.DataContext>
        <vm:ApplicationViewModel />
    </Window.DataContext>


    <TabControl ItemsSource="{Binding PageViewModels}" TabStripPlacement="Top">
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ContentPresenter Content="{Binding CurrentPageViewModel}" />
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Window>

ApplicationViewModel constructor and related fields:

private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
private static ICustomerDataService customerDataService = new CustomerDataService(CustomerRepository.Instance);




#endregion

/// <summary>
/// Initializes a new instance of the <see cref="ApplicationViewModel"/> class.
/// </summary>
public ApplicationViewModel()
{
    // Add available pages
    PageViewModels.Add(new CustomerDetailsViewModel(customerDataService));
    PageViewModels.Add(new CustomerOrdersViewModel());

    // Set starting page
    CurrentPageViewModel = PageViewModels[0];
}

Solution

  • The ContentTemplate property wraps the Content object.

    For example, with your code you are setting the .Content property to a CustomerDetailsViewModel object, and trying to bind to the CurrentPageViewModel of that object, which doesn't exist.

    What is getting rendered is :

    <TabControl>
        <TabItem>
            <ContentPresenter Content=CustomerDetailsViewModel>
                <ContentPresenter Content="{Binding CurrentPageViewModel}" />
            </ContentPresenter>
        </TabItem>
        <TabItem>
            <ContentPresenter Content=CustomerOrdersViewModel>
                <ContentPresenter Content="{Binding CurrentPageViewModel}" />
            </ContentPresenter>
        </TabItem>
    </TabControl>
    

    Because the TabControl will auto-generate a ContentPresenter to wrap the .Content for each TabItem, you don't need this template at all.

    But what it sounds like what you actually want is to bind the SelectedItem property of the TabControl

    <TabControl ItemsSource="{Binding PageViewModels}" 
                SelectedItem="{Binding CurrentPageViewModel}"
                TabStripPlacement="Top">
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
    </TabControl>