0

I'm doing an WPF application using MVVM pattern which has 2 models (Month, Year), 1 Viewmodel (YearViewModel) and 1 View (YearView) rendered in MainWindow as UserControl. The viewmodel has an observable collection of type object to add the data.

I want to achieve a tabcontrol where the first tab shows info of year model and the other tabs info of the month model.

I've used 2 DataTemplate declared in UserControl.Resources of the YearView and in the tab control I assign the templates to the tabs.

Year Model

namespace multi_tabs.Models
{
    public class Year
    {
        public float TotalIncome { get; set; }
        public float TotalExpenses { get; set; }
        public float AverageMonthlyIncome { get; set; }
        public float AverageMonthlyExpenses { get; set; }
    }
}

Month Model

namespace multi_tabs.Models
{
    public class Month
    {
        public string Name { get; set; }
        public float Income { get; set; }
        public float Expenses { get; set; }
    }
}

YearViewModel

namespace multi_tabs.ViewModels
{
    public sealed class YearViewModel
    {
        public ObservableCollection<object> Tabs { get; set; }

        public YearViewModel ()
        {
            Tabs = new ObservableCollection<object>();
            LoadMonths();
            CalculateAnnualSummary();
        }

        private void LoadMonths ()
        {
            Tabs.Add(new Month { Name = "January", Income = 100.5f, Expenses = 87.4f });
            Tabs.Add(new Month { Name = "February", Income = 100.5f, Expenses = 87.4f });
            Tabs.Add(new Month { Name = "March", Income = 100.5f, Expenses = 87.4f });
            Tabs.Add(new Month { Name = "April", Income = 145600.5f, Expenses = 87.4f });
            Tabs.Add(new Month { Name = "May", Income = 100.5f, Expenses = 8457.4f });
            Tabs.Add(new Month { Name = "June", Income = 100.5f, Expenses = 87.4f });
            Tabs.Add(new Month { Name = "July", Income = 104560.5f, Expenses = 87.4f });
            Tabs.Add(new Month { Name = "August", Income = 100.5f, Expenses = 87.4f });
            Tabs.Add(new Month { Name = "September", Income = 100.5f, Expenses = 87.4f });
            Tabs.Add(new Month { Name = "October", Income = 100.5f, Expenses = 87.4f });
            Tabs.Add(new Month { Name = "November", Income = 1786700.5f, Expenses = 84567.4f });
            Tabs.Add(new Month { Name = "December", Income = 100.5f, Expenses = 87.4f });
        }

        private void CalculateAnnualSummary ()
        {
            float _totalIncome = 0;
            float _totalExpenses = 0;
            float _averageIncome = 0;
            float _averageExpenses = 0;

            foreach (var month in Tabs)
            {
                _totalIncome += ((Month)month).Income;
                _totalExpenses += ((Month)month).Expenses;
            }

            _averageIncome = _totalIncome / Tabs.Count;
            _averageExpenses = _totalExpenses / Tabs.Count;

            Tabs.Insert(0, new Year { TotalIncome = _totalIncome,
                                      TotalExpenses = _totalExpenses,
                                      AverageMonthlyIncome = _averageIncome,
                                      AverageMonthlyExpenses = _averageExpenses });
        }
    }
}

YearView

<UserControl x:Class="multi_tabs.Views.YearView"
             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:multi_tabs.Views"
             xmlns:viewmodels="clr-namespace:multi_tabs.ViewModels"
             xmlns:view="clr-namespace:multi_tabs.Views"
             xmlns:model="clr-namespace:multi_tabs.Models"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>

        <DataTemplate x:Key="YearTemplate" DataType="{x:Type viewmodels:YearViewModel}">
            <StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Income:"/>
                    <TextBlock Text="{Binding Income}" Margin="5,0,0,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Expenses:"/>
                    <TextBlock Text="{Binding Expenses}" Margin="5,0,0,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Average Monthly Income:"/>
                    <TextBlock Text="{Binding AverageMonthlyIncome}" Margin="5,0,0,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Average Monthly Expenses:"/>
                    <TextBlock Text="{Binding AverageMonthlyExpenses}" Margin="5,0,0,0"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="MonthTemplate" DataType="{x:Type viewmodels:YearViewModel}">
            <StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Income:"/>
                    <TextBlock Text="{Binding Income}" Margin="5,0,0,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Expenses:"/>
                    <TextBlock Text="{Binding Expenses}" Margin="5,0,0,0"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
        
    </UserControl.Resources>
    <Grid>
        <TabControl ContentTemplate="{StaticResource MonthTemplate}" ItemsSource="{Binding Tabs}">
            <TabItem Header="Annual Summary" ContentTemplate="{StaticResource YearTemplate}"/>
            <TabItem Header="January"/>
            <TabItem Header="February"/>
            <TabItem Header="March"/>
            <TabItem Header="April"/>
            <TabItem Header="May"/>
            <TabItem Header="June"/>
            <TabItem Header="July"/>
            <TabItem Header="August"/>
            <TabItem Header="September"/>
            <TabItem Header="October"/>
            <TabItem Header="November"/>
            <TabItem Header="December"/>
        </TabControl>
    </Grid>
</UserControl>

The data templates are found & show correctly but the binding doesn't show the data contained in Tabs ObservableCollection.

What I'm doing wrong?

Thank you

EDIT:

I'm using Visual Studio Community 2017 and the output window appears clean to my view, but just in case I paste the lines it shows:

'multi_tabs.exe' (CLR v4.0.30319: DefaultDomain): 'C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: DefaultDomain): 'C:\Users\n60pc\OneDrive\Escritorio\JAF\multi_tabs\bin\Debug\multi_tabs.exe' cargado. Símbolos cargados.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_32\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xaml\v4.0_4.0.0.0__b77a5c561934e089\System.Xaml.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\PrivateAssemblies\Runtime\Microsoft.VisualStudio.Debugger.Runtime.dll' cargado. 
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\mscorlib.resources\v4.0_4.0.0.0_es_b77a5c561934e089\mscorlib.resources.dll' cargado. El módulo se compiló sin símbolos.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework.Aero2\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.Aero2.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework.resources\v4.0_4.0.0.0_es_31bf3856ad364e35\PresentationFramework.resources.dll' cargado. El módulo se compiló sin símbolos.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework-SystemXml\v4.0_4.0.0.0__b77a5c561934e089\PresentationFramework-SystemXml.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\UIAutomationTypes\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationTypes.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationCore.resources\v4.0_4.0.0.0_es_31bf3856ad364e35\PresentationCore.resources.dll' cargado. El módulo se compiló sin símbolos.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Users\n60pc\AppData\Local\Temp\VisualStudio.XamlDiagnostics.8336\Microsoft.VisualStudio.DesignTools.WpfTap.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Serialization.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\SMDiagnostics\v4.0_4.0.0.0__b77a5c561934e089\SMDiagnostics.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.ServiceModel.Internals\v4.0_4.0.0.0__31bf3856ad364e35\System.ServiceModel.Internals.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization.resources\v4.0_4.0.0.0_es_b77a5c561934e089\System.Runtime.Serialization.resources.dll' cargado. El módulo se compiló sin símbolos.
'multi_tabs.exe' (CLR v4.0.30319: multi_tabs.exe): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\UIAutomationProvider\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationProvider.dll' cargado. No se encuentra el archivo PDB o no se puede abrir.

EDIT 2:

MainWindow

<Window x:Class="multi_tabs.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:multi_tabs"
        xmlns:views="clr-namespace:multi_tabs.Views"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <views:YearView/>
    </Grid>
</Window>
Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Juan FS
  • 33
  • 6
  • Have you checked the `Output` tab in visual studio (or whatever IDE you use)? – X39 May 15 '19 at 13:39
  • Are you sure you can mix `ItemsSource` and explicit `TabItem` instances? My initial suspicion is that the explicit `TabItem` list is overriding the `ItemsSource`, and since those explicit items have no bindings, you get no data. – Bradley Uffner May 15 '19 at 13:41
  • I've checked if I remove all TabItem lines then nothing shows. – Juan FS May 15 '19 at 13:45
  • If you want implicit template selection based on the item type, your `DataTemplate` resources can't have an `x:Key` set on them. The `x:Key` means they are explicit templates. – Bradley Uffner May 15 '19 at 13:48
  • Bradley is correct, if you want to mix generated items from a bound collection with explicit TabItems, you're going to need to [use a CompositeCollection](https://stackoverflow.com/a/28179316/424129). When you say "nothing shows", do you mean no tabs appear, or do you mean empty tabs appear? – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 13:49
  • 1
    I think you may want `ItemTemplate="{StaticResource MonthTemplate}"` rather than `ContentTemplate`, on the TabControl itself. – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 13:51
  • If I remove `x:Key` from `DataTemplate` and `ContentTemplate="{StaticResource ...}"` from `TabControl` & `TabItem` then I get and exception in the first line of XAML ` – Juan FS May 15 '19 at 13:54
  • @EdPlunkett When I say "nothing shows" I mean no tabs and no content shows, neither the hard coded strings in the data templates. – Juan FS May 15 '19 at 13:56
  • 1
    Can you translate that to English please? – Bradley Uffner May 15 '19 at 13:56
  • 1
    If you remove x:Key from the datatemplates, you also need to remove the `StaticResource` attributes explicitly assigning the datatemplates to attributes. Since the templates have the DataType attribute, they will be automatically associated with the correct viewmodels by the framework. – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 13:57
  • Where do you create your `YearViewModel`? – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 13:57
  • YearViewModel is instantiated when I add `` inside the default `Grid` control on MainWindow. – Juan FS May 15 '19 at 14:02
  • Currently `YearViewModel` has (and creates) the instace of `Tabs`, how can I do it in reverse? – Juan FS May 15 '19 at 14:03
  • @JuanFS: Did you see my answer? It should work if the `DataContext` of the `UserControl` is set to an instance of a `YearViewModel`. – mm8 May 15 '19 at 14:03
  • @JuanFS Ahh, I missed part of your structure. Please ignore what I said about the `YearViewModel`. – Bradley Uffner May 15 '19 at 14:04
  • @JuanFS OK. You should accept mm8's answer. It's correct and complete as far as I can tell. You don't need any explicit TabItems; the ItemsSource thing will cause the TabControl to generate them all for you, so forget the CompositeCollection I mentioned above. Note that he's changing the DataType of the DataTemplates from what you had -- that's critically important. Just let him help you figure out the DataContext business. – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 14:05
  • Ok, I will try @mm8 's solution again. I will try to do the DataContext bussiness again – Juan FS May 15 '19 at 14:17

2 Answers2

2

Get rid of the TabItems from the XAML markup and use implicit DataTemplates for Month and Year:

<TabControl ItemsSource="{Binding Tabs}">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name, FallbackValue='Annual Summary'}" />
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.Resources>
        <DataTemplate DataType="{x:Type local:Year}">
            <StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Income:"/>
                    <TextBlock Text="{Binding TotalIncome}" Margin="5,0,0,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Expenses:"/>
                    <TextBlock Text="{Binding TotalExpenses}" Margin="5,0,0,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Average Monthly Income:"/>
                    <TextBlock Text="{Binding AverageMonthlyIncome}" Margin="5,0,0,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Average Monthly Expenses:"/>
                    <TextBlock Text="{Binding AverageMonthlyExpenses}" Margin="5,0,0,0"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:Month}">
            <StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Income:"/>
                    <TextBlock Text="{Binding Income}" Margin="5,0,0,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Expenses:"/>
                    <TextBlock Text="{Binding Expenses}" Margin="5,0,0,0"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </TabControl.Resources>
</TabControl>
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Sorry, but this (adapted namespaces) doesn't work, it causes to not show any tab nor content – Juan FS May 15 '19 at 14:07
  • Then the binding to `Tabs` probably fails because it works for me. The XAML you posted is invalid though. You cannot add `TabItems` to the `TabControl` when you set or bind the `ItemsSource` property. What's the `DataContex`t of your `UserControl`? – mm8 May 15 '19 at 14:08
  • @JuanFS Can you show the MainWindow XAML where you instantiate YearViewModel? Is there an implicit datatemplate in MainWindow for YearViewModel? – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 14:11
  • @EdPlunkett Added in EDIT 2 part – Juan FS May 15 '19 at 14:13
  • 1
    @JuanFS: And where are you creating an instance of `YearViewModel`? – mm8 May 15 '19 at 14:14
  • @mm8 I think the instantiation is "automatic" when I do the MainWindow thing See EDIT 2 part in the question – Juan FS May 15 '19 at 14:19
  • @JuanFS No, it's not automatic. See my answer. – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 14:21
  • @JuanFS: I don't understand. How and where is the `YearViewModel` instantied in your example? You create a YearView in the XAML markup but you never seem to set the `DataContext` of it. – mm8 May 15 '19 at 14:22
  • This solution worked using the @EdPlunkett's answer – Juan FS May 15 '19 at 14:25
  • @mm8 That's true, I probably was missing the instantiation (I have no complete understanding on MVVM pattern) and with the Ed Plunkett's answer it was solved – Juan FS May 15 '19 at 14:28
  • @JuanFS: You weren't only missing the instantiation of the view model. But I am glad that your issue has been solved. Please feel free to ask a new question if you have another issue. – mm8 May 15 '19 at 14:31
  • @JuanFS In each case, with Year, Month, and YearViewModel, 1) something has to create the viewmodel explicitly, 2) something has to make the viewmodel the Content of some control (the TabControl does that job for you when it creates TabItems to display the ItemsSource), and 3) Something has to say what DataTemplate the control uses to display it. Those three steps happen differently in different places here, but you can now see how they always happen. – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 14:32
1

Further to mm8's excellent answer, I suggest the following in MainWindow (this is a complete replacement for what's in MainWindow, but obviously you can toss in all kinds of other stuff around that UserControl, if desired):

<Window.Resources>
    <DataTemplate DataType="{x:Type viewmodels:YearViewModel}">
        <!-- 
          The YearViewModel will be the DataContext here, and the YearView
          inherits that from here. This is similar to the viewmodels:Year and Month
          templates now found in mm8's answer. 
        -->
        <local:YearView />
    </DataTemplate>
</Window.Resources>
<Grid>
    <ContentControl>
        <ContentControl.Content>
            <!-- 
            This creates a YearViewModel. The framework uses the implicit datatemplate above
            to figure out how the ContntControl will display this Content. 
            -->
            <viewmodels:YearViewModel />
        </ContentControl.Content>
    </ContentControl>
</Grid>

Note that this isn't the only way in the world to set the DataContext of a UserControl instance. This also works. In this example, the implicit datatemplate in Window.Resources is no longer defined.

    <ContentControl>
        <ContentControl.Content>
            <viewmodels:YearViewModel />
        </ContentControl.Content>
        <ContentControl.ContentTemplate>
            <DataTemplate>
                <local:YearView />
            </DataTemplate>
        </ContentControl.ContentTemplate>
    </ContentControl>

And so does this. Setting the DataContext explicitly is considered unwise because it will break any other Binding on the YearView. But as-is, this does work.

    <local:YearView>
        <local:YearView.DataContext>
            <viewmodels:YearViewModel />
        </local:YearView.DataContext>
    </local:YearView>