web-dev-qa-db-fra.com

WPF Naviguer dans les vues à l'aide du modèle MVVM

Je construis mon premier WPF en utilisant un modèle MVVM. Avec l'aide de cette communauté, je parviens à créer mon modèle, mon premier ViewModel et ma vue. Maintenant, je veux ajouter une certaine complexité à l'application en concevant l'interface de mise en page de l'application de base. Mon idée est d'avoir au moins 2 vues enfant et une vue principale et de les séparer sur plusieurs XAML:

  • Main.XAML
  • Produits.XAML
  • Clients.XAML

Main aura un menu et un espace pour charger les vues enfants (Produits et Clients). Maintenant, en suivant le modèle MVVM, toute la logique de navigation entre les vues doit être écrite sur un ViewModel. Donc mon idée est d'avoir 4 ViewModels:

  • MainViewModel
  • ProduitsViewModel
  • ClientsViewModel
  • NavigationViewModel

Donc NavigationViewModel devrait contenir une collection de modèles de vue enfant? et un viewmodel actif est-ce pas?

Mes questions sont donc:

1) Comment puis-je charger différentes vues (produits, clients) sur la vue principale en utilisant le modèle MVVM?

2) Comment implémenter la navigation viewModel?

3) Comment puis-je contrôler le nombre maximum de vues ouvertes ou actives?

4) Comment basculer entre les vues ouvertes?

J'ai fait beaucoup de recherches et de lectures et je n'ai trouvé aucun exemple de navigation MVVM simple avec WPF qui charge plusieurs vues dans une vue principale. Beaucoup d’alors:

1) Utilisez une boîte à outils externe, que je ne veux pas utiliser pour le moment.

2) Mettez tout le code pour créer toutes les vues dans un seul fichier XAML, ce qui ne semble pas être une bonne idée car j'ai besoin d'implémenter près de 80 vues!

Je suis sur la bonne voie ici? Toute aide, en particulier avec du code, sera appréciée.

MISE À JOUR

Donc, je construis un projet de test en suivant les conseils de @LordTakkera, mais je reste bloqué. Voici à quoi ressemble ma solution: Solution

Je crée:

  • Deux modèles (clients et produits)

  • Un MainWindow et deux contrôles utilisateur wpf (clients et produits) XAML.

  • Trois ViewModels (clients, produits et ViewModel principal)

Ensuite, j'ai défini dataContext sur chaque vue sur viewModel correspondant. Après cela, je crée MainWindow avec le ContentPresenter comme ceci et le lie à une propriété du viewmodel.

MainWindow.XAML

<Window x:Class="PruevaMVVMNavNew.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="519" Width="890">    
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="80"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="20"/>
    </Grid.RowDefinitions>        
    <Border Grid.Column="0" Grid.ColumnSpan="2" Background="AntiqueWhite" ></Border>
    <Border Grid.Row="1" Grid.RowSpan="2" Background="AliceBlue"></Border>
    <Border Grid.Row="1" Grid.Column="1" Background="CadetBlue"></Border>                
    <ContentPresenter Grid.Row="1" Grid.Column="1" x:Name="ContentArea" Content="{Binding CurrentView}"/>        
    <StackPanel Margin="5" Grid.Column="0" Grid.Row="1">            
        <Button>Clients</Button>
        <Button>Products</Button>
    </StackPanel>
</Grid>

Et voici également le modèle de vue de MainWindow:

class Main_ViewModel : BaseViewModel
    {
        public Main_ViewModel()
        {
            CurrentView = new Clients();
        }

        private UserControl _currentView;
        public UserControl CurrentView
        {
            get
            {
                return _currentView;
            }
            set
            {
                if (value != _currentView)
                {
                    _currentView = value;
                    OnPropertyChanged("CurrentView");
                }
            }
        }

    }

Donc, cette charge par défaut des clients affiche et ressemble à ceci (ce qui est juste!):

Current state

Je suppose donc que j'ai besoin d'un moyen de relier les boutons de gauche, avec un certain viemodel, puis de les lier avec la propriété CurrentView de Main viewModel. Comment puis je faire ça?

PDATE2

Selon les conseils de @LordTakkera, je modifie mon modèle principal de vue de cette façon:

class Main_ViewModel : BaseViewModel
    {
        public ICommand SwitchViewsCommand { get; private set; }

        public Main_ViewModel()
        {
            //CurrentView = new Clients();
            SwitchViewsCommand = new RelayCommand((parameter) => CurrentView = (UserControl)Activator.CreateInstance(parameter as Type));
        }

        private UserControl _currentView;
        public UserControl CurrentView
        {
            get
            {
                return _currentView;
            }
            set
            {
                if (value != _currentView)
                {
                    _currentView = value;
                    OnPropertyChanged("CurrentView");
                }
            }
        }
    }

J'utilise RelayCommand au lieu de DelegateCommand mais je pense que cela fonctionne de la même manière. La commande est exécutée lorsque j'appuie sur les boutons et que la chaîne de paramètres de type est correcte, mais j'obtiens cette erreur:

Error

Traduction: La valeur ne peut pas être nulle. Nom du paramètre: type. Suggestion utiliser un nouveau mot clé pour créer une instance d'objet Je ne sais pas où placer le nouveau mot clé. J'ai essayé CommandParameter mais cela ne fonctionnera pas. Une idée? Merci

MISE À JOUR

Après tous les conseils et l'aide reçus ici, et beaucoup de travail, voici mon menu de navigation final et la base de mon interface d'application.

Capture 1Capture 2

42
ericpap

Je ne suis pas sûr que vous ayez besoin d'un modèle de vue "navigation" distinct, vous pouvez facilement le mettre dans le principal. D'une manière ou d'une autre:

Pour séparer vos vues "enfant", j'utiliserais un simple ContentPresenter sur votre vue "principale":

<ContentPresenter Content="{Binding CurrentView}"/>

La façon la plus simple d'implémenter la propriété de support est d'en faire un UserControl, bien que certains soutiennent que cela viole MVVM (puisque le ViewModel dépend maintenant d'une classe "View"). Vous pourriez en faire un objet, mais vous perdez une certaine sécurité de type. Chaque vue serait un UserControl dans ce cas.

Pour basculer entre eux, vous allez avoir besoin d'une sorte de contrôle de sélection. J'ai déjà fait cela avec des boutons radio, vous les liez comme ceci:

<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/>

Le convertisseur est assez simple, dans "Convert" il vérifie simplement si le contrôle actuel est un type de paramètre, dans "ConvertBack" il retourne une nouvelle instance du paramètre.

public class InstanceEqualsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return (parameter as Type).IsInstanceOfType(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return (bool)value ? Activator.CreateInstance(parameter as Type) : Binding.DoNothing;
    }
}

La liaison à une zone de liste déroulante ou à un autre contrôle de sélection suivrait un modèle similaire.

Bien sûr, vous pouvez également utiliser des modèles de données (avec un sélecteur, malheureusement pas quelque chose que j'ai fait auparavant) et les charger dans vos ressources à l'aide de dictionnaires fusionnés (permettant le XAML séparé). Personnellement, je préfère l'itinéraire de contrôle utilisateur, choisissez celui qui vous convient le mieux!

Cette approche est "une vue à la fois". Il serait relativement facile de convertir en plusieurs vues (votre UserControl devient une collection de contrôles utilisateur, utilisez .Contains dans le convertisseur, etc.).

Pour ce faire avec des boutons, j'utiliserais des commandes et profiterais du CommandParameter.

Le bouton XAML ressemblerait à:

<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/>

Ensuite, vous avez une commande déléguée (tutoriel ici ) qui exécute le code d'activateur du convertisseur:

public ICommand SwitchViewsCommand {get; private set;}

public MainViewModel()
{
    SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type));
}

C'est sur le dessus de ma tête, mais devrait être assez proche. Faites-moi savoir comment ça se passe!

Faites-moi savoir si je fournis des informations supplémentaires!

Mise à jour:

Pour répondre à vos préoccupations:

  1. Oui, chaque fois que vous appuyez sur le bouton, une nouvelle instance de la vue est créée. Vous pouvez facilement résoudre ce problème en tenant un Dictionary<Type, UserControl> qui contient des vues pré-créées et un index. D'ailleurs, vous pouvez utiliser un Dictonary<String, UserControl> et utilisez des chaînes simples comme paramètres du convertisseur. L'inconvénient est que votre ViewModel est étroitement lié aux types de vues qu'il peut présenter (car il doit remplir ledit dictionnaire).

  2. La classe doit être supprimée, tant que personne d'autre n'y détient une référence (pensez aux gestionnaires d'événements auxquels elle s'est inscrite).

  3. Comme vous le faites remarquer, une seule vue est créée à la fois, vous ne devez donc pas vous soucier de la mémoire. Vous appelez, bien sûr, un constructeur, mais ce n'est pas si cher, en particulier sur les ordinateurs modernes où nous avons généralement beaucoup de temps CPU à perdre. Comme toujours, la réponse aux questions de performances est "Benchmark it" car vous seul avez accès aux cibles de déploiement prévues et à la source entière pour voir ce qui fonctionne réellement le mieux.

20
BradleyDotNET

À mon humble avis, le meilleur choix pour vous est d'utiliser le framework MVVM (PRISM, MVVM Light, Chinch, etc.) car la navigation est déjà implémentée. Si vous souhaitez créer votre propre navigation - essayez DataTemplate.

0
Alex Erygin