0

I have an existing ViewModel and View in an MVVM project. Effectively this View presents a collection of items in a particular, styled way. I'll call this existing ViewModel "CollectionPresenter".

Up to now, this has been presented as as follows in XAML:

<Grid>
    <ns:CollectionPresenter />
</Grid>

Now, I want to have a dynamic collection of these "CollectionPresenter" view models made available ideally in a tab view.

My approach has been to define an observable collection of these "CollectionPresenters", creating them first on construction of the parent view model. The XAML above then changed to look something like this:

<TabControl ItemsSource="{TemplateBinding CollectionPresenters}">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding CollectionPresenterTitle}">
        </DataTemplate>
    <TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
         ... this is where things get confusing
    </TabControl.ContentTemplate>
<TabControl>

You can see above my problem is the ContentTemplate.

When I load this up, I get a tab control and it has as many tabs as my observable collection of "CollectionPresenter" objects.

However, the content of the tab control is always empty.

Is this approach correct - and is there a better way regardless?

EDIT: ADDING SOME EXTRA THINGS TO MAKE IT CLEARER

I've tried the below, but it doesn't work. The XAML with the Tab Control (the binding to "Things" works fine):

<TabControl ItemsSource="{TemplateBinding Things}">
    <TabControl.ItemTemplate>
        <DataTemplate DataType="{x:Type viewModels:Thing}">
                <TextBlock Text="{Binding ThingName}" Width="200" Background="Blue" Foreground="White"/>
        </DataTemplate>
    </TabControl.ItemTemplate>    
    <TabControl.ContentTemplate>
        <DataTemplate DataType="{x:Type viewModels:Thing}">
                <TextBlock Text="{Binding ThingName}" Width="500" Height="500" Background="Blue" Foreground="White"/>
        </DataTemplate>
    </TabControl.ContentTemplate>  
</TabControl>

The definition for the "Things" observable collection (which is inside the templated parent (ParentObject) of the XAML with the tab control):

public static readonly DependencyProperty ThingsProperty =
DependencyProperty.Register("Things", typeof(ObservableCollection<Thing>), typeof(ParentObject), new PropertyMetadata(null));

public ObservableCollection<Thing> Things
{
    get { return (ObservableCollection<Thing>)GetValue(ThingsProperty); }
    set { SetValue(ThingsProperty, value); }
}   

Stripped down version of the "Thing" view model:

public class Thing : ViewModelBase
{       
    public Thing()
    {
    }

    public void Initialise(ObservableCollection<Thing> things, string thingName)
    {            
        Things = things;
        ThingName = thingName;
    }

    public static readonly DependencyProperty ThingNameProperty =
    DependencyProperty.Register("ThingName", typeof(string), typeof(Thing), new PropertyMetadata(null));

    public string ThingName
    {
        get { return (string)GetValue(ThingNameProperty); }
        set { SetValue(ThingNameProperty, value); }
    }
}
James Harcourt
  • 6,017
  • 4
  • 22
  • 42
  • You might like to see my answer to the [WPF MVVM navigate views](http://stackoverflow.com/questions/19654295/wpf-mvvm-navigate-views/19654812#19654812) question. – Sheridan Mar 18 '15 at 08:57
  • Thanks @Sheridan but it's not helped. The issue I have is that the TabItem's ItemsSource contains a collection of view models - but when I define the TabControl.ItemTemplate and refer to a property of the view model using {Binding ....} it doesn't work - and the visual tree doesn't show the view models ... it just shows tab items, with the visual items in the ItemTemplate, but no reference to the actual item from the ItemsSource collection ... – James Harcourt Mar 18 '15 at 12:26
  • Your method is flawed, which is why I provided you with a link on how to do it properly. With your method, you have to load all view models at once, which is a huge and unnecessary waste of RAM. – Sheridan Mar 18 '15 at 12:44
  • The view models need to be loaded up front since the tabs are purely to allow what *should* be on the screen all at once to be spread over 2-5 tabs (since there isn't physical room for them on a single tab). Basically we don't want dynamic loading of view models, we want them all to be pre-loaded - but just tab-accessed on the screen. – James Harcourt Mar 18 '15 at 12:48
  • The linked answer also shows you a solution to your problem too... I'll add an answer. – Sheridan Mar 18 '15 at 12:50
  • Ok, I did edit the question a few minutes ago anyway. I've boiled it down to a problem whereby the objects in the "ItemsSource" collection are not presenting themselves to the data template I have defined for each tab item - and I guess I'm trying to (regardless of 'correctness' of approach) work out why this is happening ... – James Harcourt Mar 18 '15 at 12:54

1 Answers1

1

Looking at my answer to the WPF MVVM navigate views question, you can see this:

<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
    <Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
    <Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
    <Views:CompanyView />
</DataTemplate>

Now, wherever we use an instance from one of these types in our application, these DataTemplates will tell the framework to display the related view instead.

Therefore, your solution is to simply not hard-code one single DataTemplate to the TabControl.ItemTemplate property, but to leave that blank instead. If you use multiple DataTemplates without providing x:Key values, then they will implicitly be applied when each data object is to be rendered in the TabControl.


UPDATE >>>

Using these DataTemplates should leave your TabControl looking like this:

<TabControl ItemsSource="{TemplateBinding Things}" />

I'm not sure why you're using a TemplateBinding there though as you don't need to define any new templates to get this working... therefore, you should be using a plain old Binding instead.

One other thing that you need to do is to use different data types for each item in the collection that you want to display differently. You could derive custom classes from your Thing class and so the collection could still be of type ObservableCollection<Thing>.

Community
  • 1
  • 1
Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • Our equiv. of your view (e.g. Views:CompanyView) consists of a ResourceDictionary which defines a style and control template for target type "Thing" ... does this approach still hold? – James Harcourt Mar 18 '15 at 13:00
  • You only have a `TextBlock` in the `DataTemplate` in your question, so I'm not really sure what you mean, but if you can put it into a `DataTemplate`, then yes, otherwise no... just try it out. – Sheridan Mar 18 '15 at 13:02
  • Yes I had simplified it for the sake of not writing a massive amount of code into the post. My last comment on the original post holds though - the objects in the "ItemsSource" collection are not presenting themselves to the data template I have defined for each tab item, even if that tempate is the basic textbox ... – James Harcourt Mar 18 '15 at 13:05
  • Although it actually solve my problem, I think that's because I didn't explain it in full. I am going to upvote your answer as I think it is good nonetheless... in the end I overrode the tab control and achieved this. – James Harcourt Mar 18 '15 at 17:30