web-dev-qa-db-fra.com

Model-View-Presenter dans WinForms

J'essaie d'implémenter la méthode MVP pour la première fois, en utilisant WinForms.

J'essaie de comprendre la fonction de chaque couche.

Dans mon programme, j'ai un bouton GUI qui, lorsqu'il est cliqué, ouvre une fenêtre openfiledialog.

Donc, en utilisant MVP, l'interface graphique gère l'événement click click et appelle ensuite presenter.openfile ();

Dans presenter.openfile (), cela devrait-il alors déléguer l'ouverture de ce fichier à la couche modèle, ou comme il n'y a pas de données ou de logique à traiter, devrait-il simplement agir sur la demande et ouvrir la fenêtre openfiledialog?

Mise à jour: J'ai décidé d'offrir une prime car je sens que j'ai besoin d'une assistance supplémentaire à ce sujet, et de préférence adaptée à mes points spécifiques ci-dessous, afin d'avoir du contexte.

D'accord, après avoir lu sur MVP, j'ai décidé d'implémenter la vue passive. En effet, j'aurai un tas de contrôles sur un Winform qui sera géré par un présentateur, puis les tâches déléguées aux modèles. Mes points spécifiques sont ci-dessous:

  1. Lorsque le winform se charge, il doit obtenir un treeview. Ai-je raison de penser que la vue devrait donc appeler une méthode telle que: presenter.gettree (), ceci à son tour déléguera au modèle, qui obtiendra les données de l'arborescence, les créera et les configurera, les renverra à la présentateur, qui à son tour passera à la vue qui l'affectera ensuite simplement, disons, à un panneau?

  2. Serait-ce la même chose pour tout contrôle de données sur le Winform, car j'ai également un datagridview?

  3. Mon application possède plusieurs classes de modèles avec le même assemblage. Il prend également en charge une architecture de plug-in avec des plug-ins qui doivent être chargés au démarrage. La vue appellerait-elle simplement une méthode de présentation, qui à son tour appellerait une méthode qui charge les plugins et affiche les informations dans la vue? Quel niveau contrôlerait alors les références du plugin. La vue contiendrait-elle des références à eux ou au présentateur?

  4. Ai-je raison de penser que la vue doit gérer tout ce qui concerne la présentation, de la couleur du nœud de l'arborescence à la taille de la grille de données, etc.?

Je pense que ce sont mes principales préoccupations et si je comprends comment le flux devrait être pour celles-ci, je pense que je vais bien.

87
Darren Young

Ceci est mon humble avis sur MVP et vos problèmes spécifiques.

First, tout ce avec quoi un utilisateur peut interagir ou simplement être affiché est une vue . Les lois, le comportement et les caractéristiques d'une telle vue sont décrits par une interface . Cette interface peut être implémentée à l'aide d'une interface utilisateur WinForms, d'une interface utilisateur de console, d'une interface utilisateur Web ou même d'aucune interface utilisateur (généralement lors du test d'un présentateur) - l'implémentation concrète n'a pas d'importance tant qu'elle obéit aux lois de son interface d'affichage .

Second, une vue est toujours contrôlée par un présentateur . Les lois, le comportement et les caractéristiques d'un tel présentateur sont également décrits par une interface . Cette interface n'a aucun intérêt dans l'implémentation de la vue concrète tant qu'elle obéit aux lois de son interface de vue.

Troisième, car un présentateur contrôle sa vue, pour minimiser les dépendances, il n'y a vraiment aucun avantage à ce que la vue sache quelque chose sur son présentateur. Il y a un contrat convenu entre le présentateur et la vue et cela est indiqué par l'interface de vue.

Les implications de Third sont:

  • Le présentateur ne dispose d'aucune méthode que la vue peut appeler, mais la vue contient des événements auxquels le présentateur peut s'abonner.
  • Le présentateur connaît son point de vue. Je préfère accomplir cela avec une injection de constructeur sur le présentateur de béton.
  • La vue n'a aucune idée du présentateur qui la contrôle; il ne sera tout simplement jamais fourni de présentateur.

Pour votre problème, ce qui précède pourrait ressembler à ceci dans un code quelque peu simplifié:

interface IConfigurationView
{
    event EventHandler SelectConfigurationFile;

    void SetConfigurationFile(string fullPath);
    void Show();
}

class ConfigurationView : IConfigurationView
{
    Form form;
    Button selectConfigurationFileButton;
    Label fullPathLabel;

    public event EventHandler SelectConfigurationFile;

    public ConfigurationView()
    {
        // UI initialization.

        this.selectConfigurationFileButton.Click += delegate
        {
            var Handler = this.SelectConfigurationFile;

            if (Handler != null)
            {
                Handler(this, EventArgs.Empty);
            }
        };
    }

    public void SetConfigurationFile(string fullPath)
    {
        this.fullPathLabel.Text = fullPath;
    }

    public void Show()
    {
        this.form.ShowDialog();        
    }
}

interface IConfigurationPresenter
{
    void ShowView();
}

class ConfigurationPresenter : IConfigurationPresenter
{
    Configuration configuration = new Configuration();
    IConfigurationView view;

    public ConfigurationPresenter(IConfigurationView view)
    {
        this.view = view;            
        this.view.SelectConfigurationFile += delegate
        {
            // The ISelectFilePresenter and ISelectFileView behaviors
            // are implicit here, but in a WinForms case, a call to
            // OpenFileDialog wouldn't be too far fetched...
            var selectFilePresenter = Gimme.The<ISelectFilePresenter>();
            selectFilePresenter.ShowView();
            this.configuration.FullPath = selectFilePresenter.FullPath;
            this.view.SetConfigurationFile(this.configuration.FullPath);
        };
    }

    public void ShowView()
    {
        this.view.SetConfigurationFile(this.configuration.FullPath);
        this.view.Show();
    }
}

En plus de ce qui précède, j'ai généralement une interface de base IView où je stocke la Show() et toute vue propriétaire ou titre de vue dont mes vues bénéficient généralement.

A vos questions:

1. Lorsque le winform se charge, il doit obtenir un treeview. Ai-je raison de penser que la vue devrait donc appeler une méthode telle que: presenter.gettree (), ceci à son tour déléguera au modèle, qui obtiendra les données de l'arborescence, les créera et les configurera, les renverra à la présentateur, qui à son tour passera à la vue qui l'affectera ensuite simplement, disons, à un panneau?

J'appellerais IConfigurationView.SetTreeData(...) de IConfigurationPresenter.ShowView(), juste avant l'appel à IConfigurationView.Show()

2. Serait-ce la même chose pour tout contrôle de données sur le Winform, car j'ai également un datagridview?

Oui, j'appellerais IConfigurationView.SetTableData(...) pour cela. C'est à la vue de formater les données qui lui sont données. Le présentateur obéit simplement au contrat de vue qu'il veut des données tabulaires.

. Mon application possède un certain nombre de classes de modèles avec le même assembly. Il prend également en charge une architecture de plug-in avec des plug-ins qui doivent être chargés au démarrage. La vue appellerait-elle simplement une méthode de présentation, qui à son tour appellerait une méthode qui charge les plugins et affiche les informations dans la vue? Quel niveau contrôlerait alors les références du plugin. La vue contiendrait-elle des références à eux ou au présentateur?

Si les plugins sont liés à la vue, les vues doivent les connaître, mais pas le présentateur. S'ils concernent tous les données et le modèle, la vue ne devrait rien avoir à voir avec eux.

4. Ai-je raison de penser que la vue doit gérer tout ce qui concerne la présentation, de la couleur du nœud de l'arborescence à la taille de la grille de données, etc.?

Oui. Considérez-le comme le présentateur fournissant du XML qui décrit les données et la vue qui prend les données et leur applique une feuille de style CSS. Concrètement, le présentateur peut appeler IRoadMapView.SetRoadCondition(RoadCondition.Slippery) et la vue rend ensuite la route en rouge.

Qu'en est-il des données pour les nœuds cliqués?

5. Si, lorsque je clique sur les treenodes, dois-je passer par le nœud spécifique au présentateur, puis à partir de là, le présentateur déterminerait les données dont il a besoin et demande ensuite au modèle ces données, avant de les présenter à la vue?

Si possible, je transmettrais toutes les données nécessaires pour présenter l'arbre dans une vue en un seul coup. Mais si certaines données sont trop volumineuses pour être transmises depuis le début ou si elles sont dynamiques dans leur nature et ont besoin du "dernier instantané" du modèle (via le présentateur), alors j'ajouterais quelque chose comme event LoadNodeDetailsEventHandler LoadNodeDetails À la interface de vue, afin que le présentateur puisse s'y abonner, récupérer les détails du nœud dans LoadNodeDetailsEventArgs.Node (éventuellement via son ID d'une sorte) du modèle, afin que la vue puisse mettre à jour les détails de son nœud affichés lorsque le le délégué du gestionnaire d'événements retourne. Notez que des modèles asynchrones de cela peuvent être nécessaires si la récupération des données peut être trop lente pour une bonne expérience utilisateur.

119
Johann Gerell

Le présentateur, qui contient tout logique dans la vue, devrait répondre au clic sur le bouton en tant que @JochemKempe dit . En termes pratiques, le gestionnaire d'événements Click Click appelle presenter.OpenFile(). Le présentateur est alors en mesure de déterminer ce qui doit être fait.

S'il décide que l'utilisateur doit sélectionner un fichier, il rappelle dans la vue (via une interface de vue) et laisse la vue, qui contient toute l'interface utilisateur les détails techniques, afficher le OpenFileDialog. Il s'agit d'une distinction très importante dans la mesure où le présentateur ne doit pas être autorisé à effectuer des opérations liées à la technologie d'interface utilisateur utilisée.

Le fichier sélectionné sera ensuite renvoyé au présentateur qui poursuit sa logique. Cela peut impliquer le modèle ou le service qui doit gérer le traitement du fichier.

La principale raison d'utiliser un modèle MVP, imo est de séparer la technologie d'interface utilisateur de la logique d'affichage. Ainsi, le présentateur orchestre toute la logique tandis que la vue la garde séparée de la logique de l'interface utilisateur. Cela a pour effet secondaire très agréable de rendre le présentateur entièrement testable.

Mise à jour: puisque le présentateur est l'incarnation de la logique trouvée dans une vue spécifique, la relation vue-présentateur est OMI une relation un-à-un. Et à toutes fins pratiques, une instance de vue (par exemple un formulaire) interagit avec une instance de présentateur et une instance de présentateur interagit avec une seule instance de vue.

Cela dit, dans mon implémentation de MVP avec WinForms, le présentateur interagit toujours avec la vue via une interface représentant les capacités d'interface utilisateur de la vue. Il n'y a pas de limitation sur la vue qui implémente cette interface, donc différents "widgets" peuvent implémenter la même interface de vue et réutiliser la classe du présentateur.

10
Peter Lillevold

Le présentateur doit agir sur la demande et afficher la fenêtre openfiledialog comme vous l'avez suggéré. Étant donné qu'aucune donnée n'est requise du modèle, le présentateur peut et doit traiter la demande.

Supposons que vous ayez besoin des données pour créer des entités dans votre modèle. Vous pouvez soit passer le flux à la couche d'accès où vous avez une méthode pour créer des entités à partir du flux, mais je vous suggère de gérer l'analyse du fichier dans votre présentateur et d'utiliser un constructeur ou une méthode Create par entité dans votre modèle.

2
JochemKempe