2010-11-30 3 views
20

Je développe une application WinForms en C#. J'ai une expérience limitée dans la programmation GUI, et je dois apprendre beaucoup à la volée. Cela étant dit, voici ce que je construis.C# WinForms Modèle-View-Presenter (Vue passive)

Voir l'aspect général de l'interface graphique à l'adresse suivante:

GUI http://img227.imageshack.us/img227/1084/program0.jpg

Maintenant, je l'ai fait beaucoup de travail déjà, mais dans le modèle de conception autonome très mauvais. Je ne savais pas que le projet atteindrait jamais une certaine taille, et, en tant que tel, il est temps de faire un refactoring majeur.

J'ai étudié beaucoup sur les modèles de conception GUI, et le modèle que je souhaite mettre en œuvre est la vue passive (voir http://martinfowler.com/eaaDev/PassiveScreen.html). Je cherche de l'aide sur la façon de tout rassembler.

Contexte:

1) En fonction de ce que l'utilisateur clique dans le « TreeView », la « liste » dans le coin inférieur gauche affiche une liste d'objets qui peuvent renseigner la zone « Editeur ». Ces objets peuvent être un TextBox ou un DataGridView. L'utilisateur bascule la liste pour choisir ce qu'il veut voir dans l'éditeur.

2) Le modèle est essentiellement un dossier avec des fichiers de données et de configuration. Il y a un programme externe qui s'exécute sur un répertoire donné, crée des fichiers/dossiers de sortie, etc. Ce programme que je développe est conçu pour gérer/configurer ces objets de manière conviviale et conviviale.

3) Le problème avec le comme je l'ai fait, c'est qu'il est presque impossible de tester, et donc le passage au modèle de conception MVP-esque Passive View

J'essaie de faire en sorte que le programme fonctionne indépendamment de la vue. Je n'ai pas réussi à trouver d'exemples où une vue interactive plus complexe est utilisée avec le pattern Passive View.

Questions:

1) Ai-je besoin de mettre en œuvre une grande interface/vue pour l'ensemble du « look » du programme, puis mettre en œuvre des sous-interfaces/sous-vues pour chacun des TreeView, éditeur, enregistreur , etc.? Ou y a-t-il une meilleure «structure» pour le faire? 2) Quand il s'agit de «transférer» des événements de la vue au Presenter/Controller (quelle que soit la terminologie que vous souhaitez utiliser, utilisez le pattern de conception Passive View), comment dois-je procéder? Parfois, j'ai des propriétés simples qui ont besoin d'être mises à jour, et parfois j'ai besoin de toute une série d'étapes à déplier.

J'aimerais des suggestions et des conseils sur ce sujet. J'ai parcouru Internet, et je n'ai pas trouvé d'exemples adéquats pour m'aider à poursuivre ce projet.

Merci d'avance!

Daniel

Répondre

18

Voici un exemple simple qui illustre le concept de vues passives en utilisant le modèle de conception MVP. Parce que nous utilisons des vues passives, la vue n'a aucune connaissance du présentateur. Le présentateur s'abonnera simplement aux événements publiés par la vue et agira en conséquence.

Pour commencer, nous devons définir un contrat pour notre vue. Ceci est généralement réalisé en utilisant une interface, essentiellement, nous voulons avoir un couplage très lâche avec notre vue.Nous souhaitons pouvoir basculer vers différentes vues ou différents événements afin de créer des vues fictives pour les tests unitaires.

Voici un contrat qui décrit une vue simple qui sera utilisé pour afficher des informations client

public interface ICustomerManagementView 
{ 
    void InitializeCustomers(ICustomer[] customers); 
    void DisplayCustomer(ICustomer customer); 
    event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged; 
} 

Il expose une méthode unique InitializeCustomers qui sera utilisé pour initialiser notre point de vue avec des objets de notre modèle.

Nous avons également un événement SelectedCustomerChanged qui sera utilisé par notre présentateur pour recevoir une notification qu'une action s'est produite dans la vue.

Une fois que nous avons notre contrat, nous pouvons commencer à gérer ces interactions dans notre présentateur.

public class CustomerManagementPresenter 
{ 
    private ICustomer _selectedCustomer; 
    private readonly ICustomerManagementView _managementView; 
    private readonly ICustomerRepository _customerRepository; 

    public CustomerManagementPresenter(ICustomerManagementView managementView, ICustomerRepository customerRepository) 
    { 
     _managementView = managementView; 
     _managementView.SelectedCustomerChanged += this.SelectedCustomerChanged; 

     _customerRepository = customerRepository; 

     _managementView.InitializeCustomers(_customerRepository.FetchCustomers()); 
    } 

    private void SelectedCustomerChanged(object sender, EventArgs<ICustomer> args) 
    { 
     // Perform some logic here to update the view 
     if(_selectedCustomer != args.Value) 
     { 
      _selectedCustomer = args.Value; 
      _managementView.DisplayCustomer(_selectedCustomer); 
     } 
    } 
} 

Dans le présentateur nous pouvons utiliser un autre modèle appelé dependency injection pour donner accès à notre point de vue et toutes les classes de modèle que nous pourrions avoir besoin. Dans cet exemple, j'ai un CustomerRepository chargé d'extraire les détails du client.

Dans le constructeur, nous avons deux lignes de code importantes, premièrement nous nous sommes abonnés à l'événement SelectedCustomerChanged dans notre vue, c'est ici que nous pouvons effectuer des actions associées. Deuxièmement, nous avons appelé InitilaizeCustomers avec les données du référentiel.

À ce stade, nous n'avons pas défini d'implémentation concrète pour notre vue, tout ce que nous devons faire est de créer un objet qui implémente ICustomerManagementView. Par exemple, dans une application Windows Forms que nous pouvons faire la

suivante
public partial class CustomerManagementView : Form, ICustomerManagementView 
{ 
    public CustomerManagementView() 
    { 
     this.InitializeComponents(); 
    } 

    public void InitializeCustomers(ICustomer[] customers) 
    { 
     // Populate the tree view with customer details 
    } 

    public void DisplayCustomer(ICustomer customer) 
    { 
     // Display the customer... 
    } 

    // Event handler that responds to node selection 
    private void CustomerTreeViewAfterSelect(object sender, TreeViewEventArgs e) 
    { 
     var customer = e.Node.Tag as ICustomer; 
     if(customer != null) 
     { 
      this.OnSelectedCustomerChanged(new EventArgs<ICustomer>(customer)); 
     } 
    } 

    // Protected method so that we can raise our event 
    protected virtual void OnSelectedCustomerChanged(EventArgs<ICustomer> args) 
    { 
     var eventHandler = this.SelectedCustomerChanged; 
     if(eventHandler != null) 
     { 
      eventHandler.Invoke(this, args); 
     } 
    } 

    // Our view will raise an event each time the selected customer changes 
    public event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged; 
} 

Si nous voulions tester notre logique de présentation, nous pourrions se moquer de notre point de vue et d'effectuer certaines affirmations.

EDIT: événement personnalisé inclus args

public class EventArgs<T> : EventArgs 
{ 
    private readonly T _value; 

    public EventArgs(T value) 
    { 
     _value = value; 
    } 

    public T Value 
    { 
     get { return _value; } 
    } 
} 
+0

J'apprécie la réponse informative, mais elle répond particulièrement à mes questions. Devrais-je utiliser une vue (avec ces sous-composants 4-5 avec lesquels l'utilisateur peut interagir), ou devrait-il y avoir des vues dans les vues (donc des interfaces dans les interfaces)? De plus, je ne suis toujours pas certain du «transfert» des événements de la vue au présentateur, et j'ai encore besoin d'éclaircissements sur la façon dont la vue observe le modèle. Je suis également préoccupé par la «structure» du programme. –

+1

Je préfère le décomposer en plus petits composants, surtout si vous voulez encourager la réutilisation.Créez des vues qui ont un seul but, mais qui peuvent être utilisées ensemble pour créer une interface utilisateur composite. –

+0

En outre, si vous regardez le constructeur du présentateur, vous pouvez voir comment gérer les événements de la vue. –

0

Je les décomposer en vues séparées avec leurs propres cadeaux, et d'utiliser un « contrôle » présentateur/vue de gérer la délégation de messages entre tous. Non seulement cela aidera la testabilité, mais il permettra également à vos contrôles de remplir SRP, aussi.

Donc, dans votre cas, vous pourriez avoir une IFormManager que la fenêtre principale mettra en œuvre, puis un IFileManager, ILoggerWindow etc., etc.

Bien qu'il puisse être un peu exagéré d'utiliser, je suggère que vous avez un regard sur Smart Client Software Factory (de l'équipe Microsoft Patterns and Practices) - il n'est plus activement développé, mais il a une bonne implémentation de MVP et fait ce genre de truc de composition de vue assez bien, donc ça pourrait vous donner de bons idées

+0

Merci pour l'entrée. Mais où pourrais-je mettre les contrôles individuels? Dans la vue principale? Mais j'essaie de garder la vue principale légère en premier lieu. –

+0

J'ai trouvé une photo qui pourrait détailler ce que je demande. Pensez-vous que ce graphique suffira à résoudre mon problème? http://www.diskordia.ch/blog/wp-content/uploads/2009/03/subview.png –

+0

Rien ne vous empêche de placer l'enfant dans la vue principale - votre vue principale est toujours légère, c'est-à-dire sa semelle la responsabilité est de maintenir la disposition des messages de délégué à travers les vues; cela ne contrôlerait rien à leur sujet. –