web-dev-qa-db-fra.com

La meilleure approche pour créer une nouvelle fenêtre dans WPF en utilisant MVVM

Dans le message voisin: Comment le ViewModel doit-il fermer le formulaire? J'ai publié ma vision de la fermeture de fenêtres avec l'utilisation de MVVM. Et maintenant, j'ai une question: comment les ouvrir.

J'ai une fenêtre principale (vue principale). Si l'utilisateur clique sur le bouton "Afficher", la fenêtre "Démo" (boîte de dialogue modale) devrait s'afficher. Quelle est la meilleure façon de créer et d'ouvrir des fenêtres à l'aide du modèle MVVM? Je vois deux approches générales:

Le 1er (probablement le plus simple). Le gestionnaire d'événements "ShowButton_Click" doit être implémenté dans le code derrière la fenêtre principale de la manière suivante:

        private void ModifyButton_Click(object sender, RoutedEventArgs e)
        {
            ShowWindow wnd = new ShowWindow(anyKindOfData);
            bool? res = wnd.ShowDialog();
            if (res != null && res.Value)
            {
                //  ... store changes if neecssary
            }
        }
  1. Si nous "Afficher" l'état du bouton doit être modifié (activé/désactivé), nous devrons ajouter une logique qui gérera l'état du bouton;
  2. Le code source est très similaire aux sources WinForms et MFC "à l'ancienne" - je ne sais pas si c'est bon ou mauvais, veuillez le conseiller.
  3. Quelque chose d'autre que j'ai manqué?

Une autre approche:

Dans MainWindowViewModel, nous implémenterons la propriété "ShowCommand" qui renverra l'interface ICommand de la commande. Comman à son tour:

  • soulèvera "ShowDialogEvent";
  • va gérer l'état du bouton.

Cette approche sera plus adaptée à la MVVM mais nécessitera un codage supplémentaire: la classe ViewModel ne peut pas "afficher la boîte de dialogue", donc MainWindowViewModel ne soulèvera que "ShowDialogEvent", MainWindowView nous aurons besoin d'ajouter un gestionnaire d'événements dans sa méthode MainWindow_Loaded, quelque chose comme ceci :

((MainWindowViewModel)DataContext).ShowDialogEvent += ShowDialog;

(ShowDialog - similaire à la méthode 'ModifyButton_Click'.)

Mes questions sont donc les suivantes: 1. Voyez-vous une autre approche? 2. Pensez-vous que l'un des éléments énumérés est bon ou mauvais? (Pourquoi?)

Toute autre pensée est la bienvenue.

Merci.

55
Budda

Je pensais aussi à cette question récemment. Voici une idée que j'avais si vous utilisez nity dans votre projet en tant que "conteneur" ou autre pour l'injection de dépendances. Je suppose que normalement, vous surchargez App.OnStartup() et créez votre modèle, affichez le modèle, affichez-le et donnez à chacun les références appropriées. Avec Unity, vous donnez au conteneur une référence au modèle, puis utilisez le conteneur pour "résoudre" la vue. Le conteneur Unity injecte votre modèle de vue, de sorte que vous ne l'instanciez jamais directement. Une fois votre vue résolue, vous appelez Show() dessus.

Dans un exemple de vidéo que j'ai regardé, le conteneur Unity a été créé en tant que variable locale dans OnStartup. Que se passe-t-il si vous la créez en tant que propriété publique statique en lecture seule dans votre classe App? Vous pouvez ensuite l'utiliser dans votre modèle de vue principale pour créer vos nouvelles fenêtres, en injectant automatiquement toutes les ressources dont la nouvelle vue a besoin. Quelque chose comme App.Container.Resolve<MyChildView>().ShowDialog();.

Je suppose que vous pourriez en quelque sorte se moquer du résultat de cet appel au conteneur Unity dans vos tests. Alternativement, vous pourriez peut-être écrire des méthodes comme ShowMyChildView() dans la classe App, qui fait essentiellement ce que j'ai décrit ci-dessus. Il pourrait être facile de se moquer d'un appel à App.ShowMyChildView() car il retournerait juste un bool?, Hein?

Eh bien, ce n'est peut-être pas mieux que d'utiliser simplement new MyChildView(), mais c'est une petite idée que j'avais. Je pensais que je le partagerais. =)

16
Benny Jobigan

Certains frameworks MVVM (par exemple MVVM Light ) utilisent le modèle de médiateur . Ainsi, pour ouvrir une nouvelle fenêtre (ou créer une vue), un code spécifique à la vue s'abonnera aux messages du médiateur et le ViewModel enverra ces messages.

Comme ça:

Abonnement

Messenger.Default.Register<DialogMessage>(this, ProcessDialogMessage);
...
private void ProcessDialogMessage(DialogMessage message)
{
     // Instantiate new view depending on the message details
}

Dans ViewModel

Messenger.Default.Send(new DialogMessage(...));

Je préfère faire l'abonnement dans une classe singleton, qui "vit" aussi longtemps que la partie UI de l'application le fait. Pour résumer: ViewModel transmet des messages comme "J'ai besoin de créer une vue" et l'interface utilisateur écoute ces messages et agit sur eux.

Il n'y a cependant pas d'approche "idéale".

17
arconaut

Je suis un peu en retard, mais je trouve les réponses existantes insuffisantes. Je vais vous expliquer pourquoi. En général:

  • vous pouvez accéder à ViewModels à partir de View,
  • il est faux d'accéder aux vues depuis les ViewModels, car cela introduit une dépendance circulaire et rend les ViewModels difficiles à tester.

Réponse de Benny Jobigan:

App.Container.Resolve<MyChildView>().ShowDialog();

cela ne résout rien du tout. Vous accédez à votre vue à partir de ViewModel de manière couplée. La seule différence avec new MyChildView().ShowDialog() est que vous avez parcouru une couche d'indirection. Je ne vois aucun avantage à appeler directement le ctor MyChildView.

Ce serait plus propre si vous utilisiez l'interface pour la vue:

App.Container.Resolve<IMyChildView>().ShowDialog();`

À présent, le ViewModel n'est pas étroitement couplé à la vue. Cependant, je trouve assez peu pratique de créer une interface pour chaque vue.

réponse de l'arconaut:

Messenger.Default.Send(new DialogMessage(...));

c'est mieux. Il semble que Messenger ou EventAggregator ou un autre modèle pub/sub soit une solution universelle pour tout dans MVVM :) L'inconvénient est qu'il est plus difficile de déboguer ou de naviguer vers DialogMessageHandler. C'est trop humble indirect. Par exemple, comment liriez-vous la sortie du dialogue? en modifiant DialogMessage?

Ma solution:

vous pouvez ouvrir la fenêtre de MainWindowViewModel comme ceci:

var childWindowViewModel = new MyChildWindowViewModel(); //you can set parameters here if necessary
var dialogResult = DialogService.ShowModal(childWindowViewModel);
if (dialogResult == true) {
   //you can read user input from childWindowViewModel
}

DialogService ne prend que le ViewModel de la boîte de dialogue, de sorte que vos modèles de vue sont totalement indépendants des vues. Au moment de l'exécution, DialogService peut trouver la vue appropriée (en utilisant la convention de dénomination par exemple) et l'afficher, ou il peut être facilement simulé dans les tests unitaires.

dans mon cas, j'utilise ces interfaces:

interface IDialogService
{
   void Show(IDialogViewModel dialog);
   void Close(IDialogViewModel dialog); 
   bool? ShowModal(IDialogViewModel dialog);
   MessageBoxResult ShowMessageBox(string message, string caption = null, MessageBoxImage icon = MessageBoxImage.No...);
}

interface IDialogViewModel 
{
    string Caption {get;}
    IEnumerable<DialogButton> Buttons {get;}
}

où DialogButton spécifie DialogResult ou ICommand ou les deux.

5
Liero

Jetez un œil à ma solution MVVM actuelle pour afficher les boîtes de dialogue modales dans Silverlight. Il résout la plupart des problèmes que vous avez mentionnés, mais il est complètement abstrait des choses spécifiques à la plate-forme et peut être réutilisé. De plus, je n'ai utilisé aucune liaison de code uniquement avec des délégués qui implémentent ICommand. La boîte de dialogue est essentiellement une vue - un contrôle distinct qui a son propre ViewModel et il est affiché à partir du ViewModel de l'écran principal mais déclenché à partir de l'interface utilisateur via la liaison DelagateCommand.

Voir la solution complète de Silverlight 4 ici Dialogues modaux avec MVVM et Silverlight 4

2
Roboblob

J'utilise un contrôleur qui gère toutes les informations passant entre les vues. Tous les modèles de vue utilisent des méthodes dans le contrôleur pour demander plus d'informations qui peuvent être implémentées sous forme de dialogues, d'autres vues, etc.

Cela ressemble à ceci:

class MainViewModel {
    public MainViewModel(IView view, IModel model, IController controller) {
       mModel = model;
       mController = controller;
       mView = view;
       view.DataContext = this;
    }

    public ICommand ShowCommand = new DelegateCommand(o=> {
                  mResult = controller.GetSomeData(mSomeData);
                                                      });
}

class Controller : IController {
    public void OpenMainView() {
        IView view = new MainView();
        new MainViewModel(view, somemodel, this);
    }

    public int GetSomeData(object anyKindOfData) {
      ShowWindow wnd = new ShowWindow(anyKindOfData);
      bool? res = wnd.ShowDialog();
      ...
    }
}
1
adrianm

Mon approche est similaire à celle d'Adrianm. Cependant, dans mon cas, le contrôleur ne fonctionne jamais avec les types de vues concrets. Le contrôleur est complètement découplé de la vue - de la même manière que le ViewModel.

Comment cela fonctionne peut être vu dans l'exemple ViewModel de WPF Application Framework (WAF) .

.

Meilleures salutations,

jbe

0
jbe