2

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];
}
Community
  • 1
  • 1
Brian Var
  • 6,029
  • 25
  • 114
  • 212

2 Answers2

3

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>
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Okay that makes sense, is it possible to set the tab control binding so that if no record is selected from a data grid in the CustomerDetailsViewModel. The second CustomerOrdersViewModel tab will be disabled or no UI shown for that tab? Or does is that a good/bad idea in this instance? – Brian Var Dec 01 '15 at 15:05
  • I would create an `IsEnabled` property on `IPageViewModel`, and bind the `.IsEnabled` property of the TabItem to it using the ``, like [this](http://stackoverflow.com/a/9291899/302677). I'm assuming the tabs have some way of talking to each other to pass the `SelectedCustomer` to the `CustomerOrdersViewModel`, so you could base `IsEnabled` on if `CustomerOrdersViewModel` is null or not, or just set it directly when you want it enabled/disabled. – Rachel Dec 01 '15 at 15:14
  • I attempted implementing the above suggestion on enabling tabs. Having an issue with only one tab being enabled under the conditions. In summary I can't navigate back to the first tab once I select the second tab after it has been enabled. I posted a question on it if you have any input: http://stackoverflow.com/questions/34042541/how-to-handle-enabled-property-of-multiple-tabs-using-data-templates – Brian Var Dec 02 '15 at 12:19
  • I've since implemented the above succesfully. But I've now ran into an issue when creating a dialog seperate to the AppView. The issue is a call to ShowDialog() blocks the call to send a message to the opened VM. Which results in the message not being received until the dialog is closed.If you have any ideas on how to resolve that issue, I've posted a question on it here: http://stackoverflow.com/questions/34347091/how-to-register-message-handler-prior-to-showdialog-blocking-call – Brian Var Dec 18 '15 at 01:23
  • @BrianJ Looks like you already got an answer before I saw it :) Personally I don't like the WPF popups or dialog controls, so I use a [custom PopupUserControl](https://rachel53461.wordpress.com/2011/08/20/popup-panel-for-wpf/) for any dialog needs I have. Its just a UserControl that floats on top of everything else. – Rachel Dec 18 '15 at 14:55
  • okay thanks for the advice, might come in handy with my next project :) – Brian Var Dec 18 '15 at 14:59
0

We don't generally use a ContentPresenter outside of a ControlTemplate and certainly not like you are doing in your DataTemplate... the use of the ContentPresenter will make the WPF Framework search for a DataTemplate that matches the type of the Content, so in your case, you'll end up with an endless loop. Instead, you should put your associated view in the DataTemplate:

<DataTemplate DataType="{x:Type YourDataXamlPrefix:CurrentPageViewModel}">
    <YourUiXamlPrefix:YourView DataContext="{Binding CurrentPageViewModel}" />
</DataTemplate>

In this way, when the Framework comes across your view model class in the collection, it will find this DataTemplate and render the relevant view.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • I have to disagree with the first line, I use a `ContentPresenter` all the time when I want to insert some non-UI content into the VisualTree. A common example would be something like what the OP has (``), although typically my bindings would be correct and I wouldn't use it as the only item in a `ContentTemplate` like the code posted :) – Rachel Dec 01 '15 at 14:49
  • I thought someone might say that (as I also use them like that occasionally), so I added 'generally', but obviously that was not enough. I was trying to keep the answer succinct and not go into all the possible uses of a `ContentPresenter` and concentrate on the user's problem, but thanks for highlighting that point Rachel. – Sheridan Dec 01 '15 at 14:53