简体   繁体   中英

Best way to bind a set of same-type ViewModels to a TabControl in MVVM / WPF

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); }
    }
}

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 DataTemplate s 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 DataTemplate s 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> .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM