web-dev-qa-db-fra.com

Comment lier un TabControl à une collection de ViewModels?

Fondamentalement, j'ai dans mon MainViewModel.cs:

ObservableCollection<TabItem> MyTabs { get; private set; }

Cependant, je dois en quelque sorte être en mesure non seulement de créer les onglets, mais aussi de charger le contenu des onglets et de le lier à leurs modèles de vue appropriés tout en maintenant MVVM.

Fondamentalement, comment puis-je obtenir un contrôle utilisateur à charger en tant que contenu d'un tabitem ET que ce contrôle utilisateur soit connecté à un modèle de vue approprié. La partie qui rend cela difficile est que le ViewModel n'est pas censé construire les éléments de vue réels, non? Ou est-ce possible?

Fondamentalement, est-ce que MVVM serait approprié:

UserControl address = new AddressControl();
NotificationObject vm = new AddressViewModel();
address.DataContext = vm;
MyTabs[0] = new TabItem()
{
    Content = address;
}

Je demande seulement parce que bien, je construis une vue (AddressControl) à partir d'un ViewModel, qui pour moi ressemble à un MVVM non-non.

64
michael

Ce n'est pas MVVM. Vous ne devez pas créer d'éléments d'interface utilisateur dans votre modèle de vue.

Vous devez lier la ItemsSource de l'onglet à votre ObservableCollection, et qui doit contenir des modèles avec des informations sur les onglets qui doivent être créés.

Voici le VM et le modèle qui représente une page à onglet:

public sealed class ViewModel
{
    public ObservableCollection<TabItem> Tabs {get;set;}
    public ViewModel()
    {
        Tabs = new ObservableCollection<TabItem>();
        Tabs.Add(new TabItem { Header = "One", Content = "One's content" });
        Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
    }
}
public sealed class TabItem
{
    public string Header { get; set; }
    public string Content { get; set; }
}

Et voici à quoi ressemblent les liaisons dans la fenêtre:

<Window x:Class="WpfApplication12.MainWindow"
        xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <ViewModel
            xmlns="clr-namespace:WpfApplication12" />
    </Window.DataContext>
    <TabControl
        ItemsSource="{Binding Tabs}">
        <TabControl.ItemTemplate>
            <!-- this is the header template-->
            <DataTemplate>
                <TextBlock
                    Text="{Binding Header}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <!-- this is the body of the TabItem template-->
            <DataTemplate>
                <TextBlock
                    Text="{Binding Content}" />
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Window>

(Remarque: si vous voulez des éléments différents dans différents onglets, utilisez DataTemplates. Soit le modèle d'affichage de chaque onglet doit être sa propre classe, soit créez un DataTemplateSelector personnalisé pour choisir le modèle correct.)

Oh regardez, un UserControl à l'intérieur du modèle de données:

<TabControl
    ItemsSource="{Binding Tabs}">
    <TabControl.ItemTemplate>
        <!-- this is the header template-->
        <DataTemplate>
            <TextBlock
                Text="{Binding Header}" />
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <!-- this is the body of the TabItem template-->
        <DataTemplate>
            <MyUserControl xmlns="clr-namespace:WpfApplication12" />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>
126
Will

Dans Prism, vous faites généralement en sorte que le contrôle d'onglet soit une région afin que vous n'ayez pas à contrôler la collection de pages d'onglets liées.

<TabControl 
    x:Name="MainRegionHost"
    Regions:RegionManager.RegionName="MainRegion" 
    />

Maintenant, les vues peuvent être ajoutées en s'inscrivant dans la région MainRegion:

RegionManager.RegisterViewWithRegion( "MainRegion", 
    ( ) => Container.Resolve<IMyViewModel>( ).View );

Et ici, vous pouvez voir une spécialité de Prism. La vue est instanciée par le ViewModel. Dans mon cas, je résous le ViewModel via un conteneur d'inversion de contrôle (par exemple Unity ou MEF). Le ViewModel obtient la vue injectée via l'injection de constructeur et se définit comme contexte de données de la vue.

L'alternative consiste à enregistrer le type de vue dans le contrôleur de région:

RegionManager.RegisterViewWithRegion( "MainRegion", typeof( MyView ) );

Cette approche vous permet de créer les vues ultérieurement lors de l'exécution, par exemple par un contrôleur:

IRegion region = this._regionManager.Regions["MainRegion"];

object mainView = region.GetView( MainViewName );
if ( mainView == null )
{
    var view = _container.ResolveSessionRelatedView<MainView>( );
    region.Add( view, MainViewName );
}

Parce que vous avez enregistré le type de la vue, la vue est placée dans la bonne région.

21
PVitt

J'ai un convertisseur pour découpler l'interface utilisateur et ViewModel, c'est le point ci-dessous:

<TabControl.ContentTemplate>
    <DataTemplate>
        <ContentPresenter Content="{Binding Tab,Converter={StaticResource TabItemConverter}"/>
    </DataTemplate>
</TabControl.ContentTemplate>

L'onglet est une énumération dans mon TabItemViewModel et le TabItemConverter le convertit en interface utilisateur réelle.

Dans TabItemConverter, obtenez simplement la valeur et renvoyez le contrôle utilisateur dont vous avez besoin.

1
acai

Peut-être:

<UserControl x:Class="Test_002.Views.MainView"
         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:Test_002.Views"
         xmlns:generalView="clr-namespace:Test_002.Views.General"
         xmlns:secVIew="clr-namespace:Test_002.Views.Security"
         xmlns:detailsView="clr-namespace:Test_002.Views.Details"
         mc:Ignorable="d" 
         d:DesignHeight="400" d:DesignWidth="650">
<Grid>
    <DockPanel>
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Margin="2,5">
            <Button Command="{Binding btnPrev}" Content="Prev"/>
            <Button Command="{Binding btnNext}" Content="Next"/>
            <Button Command="{Binding btnSelected}" Content="Selected"/>
        </StackPanel>
        <TabControl>
            <TabItem Header="General">
                <generalView:GeneralView></generalView:GeneralView>
            </TabItem>
            <TabItem Header="Security">
                <secVIew:SecurityView></secVIew:SecurityView>
            </TabItem>
            <TabItem Header="Details">
                <detailsView:DetailsView></detailsView:DetailsView>
            </TabItem>
        </TabControl>
    </DockPanel>
</Grid>

Pensez que c'est la façon la plus simple. Le MVVM est-il compatible?

0
BigShady