Model-View-Presenter in WinForms


91

Sto cercando di implementare il metodo MVP per la prima volta, utilizzando WinForms.

Sto cercando di capire la funzione di ogni strato.

Nel mio programma ho un pulsante della GUI che quando cliccato apre una finestra di openfiledialog.

Quindi, utilizzando MVP, la GUI gestisce l'evento di clic del pulsante e quindi chiama presenter.openfile ();

All'interno di presenter.openfile (), dovrebbe quindi delegare l'apertura di quel file al livello del modello, o poiché non ci sono dati o logica da elaborare, dovrebbe semplicemente agire sulla richiesta e aprire la finestra di openfiledialog?

Aggiornamento: ho deciso di offrire una taglia in quanto ritengo di aver bisogno di ulteriore assistenza su questo, e preferibilmente adattato ai miei punti specifici di seguito, in modo da avere un contesto.

Ok, dopo aver letto su MVP, ho deciso di implementare la visualizzazione passiva. Effettivamente avrò un mucchio di controlli su un Winform che sarà gestito da un Presenter e quindi i compiti delegati al / i Modello / i. I miei punti specifici sono di seguito:

  1. Quando il winform viene caricato, deve ottenere una visualizzazione ad albero. Ho ragione nel pensare che la view dovrebbe quindi chiamare un metodo come: presenter.gettree (), questo a sua volta delegherà al modello, che otterrà i dati per la treeview, lo creerà e lo configurerà, lo restituirà al presenter, che a sua volta passerà alla visualizzazione che poi lo assegnerà semplicemente, diciamo, a un pannello?

  2. Sarebbe lo stesso per qualsiasi controllo dati su Winform, dato che ho anche un datagridview?

  3. La mia app ha un numero di classi di modelli con lo stesso assembly. Supporta anche un'architettura plug-in con plug-in che devono essere caricati all'avvio. La vista chiamerebbe semplicemente un metodo del presentatore, che a sua volta chiamerebbe un metodo che carica i plugin e mostra le informazioni nella vista? Quale livello controllerebbe quindi i riferimenti del plug-in. La visualizzazione conterrebbe riferimenti a loro o al presentatore?

  4. Ho ragione nel pensare che la vista dovrebbe gestire ogni singola cosa sulla presentazione, dal colore del nodo treeview, alla dimensione del datagrid, ecc.?

Penso che siano le mie principali preoccupazioni e se capisco come dovrebbe essere il flusso per queste penso che starò bene.


Questo collegamento lostechies.com/derekgreer/2008/11/23/… spiega alcuni degli stili di MVP. Potrebbe rivelarsi utile in aggiunta all'eccellente risposta di Johann.
ak3nat0n

Risposte:


125

Questa è la mia modesta interpretazione di MVP e dei tuoi problemi specifici.

In primo luogo , tutto ciò con cui un utente può interagire, o semplicemente essere mostrato, è una vista . Le leggi, il comportamento e le caratteristiche di tale vista sono descritti da un'interfaccia . Quell'interfaccia può essere implementata utilizzando un'interfaccia utente WinForms, un'interfaccia utente console, un'interfaccia utente web o addirittura nessuna interfaccia utente (di solito quando si testa un presentatore): l'implementazione concreta non ha importanza fintanto che obbedisce alle leggi della sua interfaccia di visualizzazione .

In secondo luogo , una visualizzazione è sempre controllata da un presentatore . Le leggi, il comportamento e le caratteristiche di un tale presentatore sono descritti anche da un'interfaccia . Quell'interfaccia non ha alcun interesse nell'implementazione della vista concreta fintanto che obbedisce alle leggi della sua interfaccia di visualizzazione.

Terzo , dal momento che un presentatore controlla la propria visualizzazione, per ridurre al minimo le dipendenze non c'è davvero alcun vantaggio nel fatto che la visualizzazione sappia qualcosa sul suo presentatore. Esiste un contratto concordato tra il presentatore e la vista e questo viene dichiarato dall'interfaccia della vista.

Le implicazioni di Third sono:

  • Il presentatore non dispone di metodi che la visualizzazione può chiamare, ma la visualizzazione ha eventi a cui il presentatore può iscriversi.
  • Il presentatore conosce il suo punto di vista. Preferisco farlo con l'iniezione del costruttore sul presentatore concreto.
  • La vista non ha idea di quale presentatore la stia controllando; non verrà mai fornito alcun presentatore.

Per il tuo problema, quanto sopra potrebbe apparire così in un codice un po 'semplificato:

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();
    }
}

Oltre a quanto sopra, di solito ho un'interfaccia di base in IViewcui ripongo la Show()vista o il titolo della vista di qualsiasi proprietario di cui generalmente beneficiano le mie visualizzazioni.

Alle tue domande:

1. Quando il winform viene caricato, deve ottenere una visualizzazione ad albero. Ho ragione nel pensare che la vista dovrebbe quindi chiamare un metodo come: presenter.gettree (), questo a sua volta delegherà al modello, che otterrà i dati per la treeview, lo creerà e lo configurerà, lo restituirà al presenter, che a sua volta passerà alla visualizzazione che poi lo assegnerà semplicemente, diciamo, a un pannello?

Chiamerei IConfigurationView.SetTreeData(...)da IConfigurationPresenter.ShowView(), subito prima della chiamata aIConfigurationView.Show()

2. Sarebbe lo stesso per qualsiasi controllo dati su Winform, dato che ho anche un datagridview?

Sì, lo chiamerei IConfigurationView.SetTableData(...). Sta alla vista formattare i dati forniti. Il presentatore obbedisce semplicemente al contratto della vista che vuole dati tabulari.

3. My App, ha un numero di classi di modelli con lo stesso assembly. Supporta anche un'architettura plug-in con plug-in che devono essere caricati all'avvio. La vista chiamerebbe semplicemente un metodo del presentatore, che a sua volta chiamerebbe un metodo che carica i plugin e mostra le informazioni nella vista? Quale livello controllerebbe quindi i riferimenti del plug-in. La visualizzazione conterrebbe riferimenti a loro o al presentatore?

Se i plugin sono relativi alla visualizzazione, le visualizzazioni dovrebbero conoscerli, ma non il presentatore. Se riguardano solo dati e modelli, la vista non dovrebbe avere nulla a che fare con essi.

4. Ho ragione nel pensare che la vista dovrebbe gestire ogni singola cosa sulla presentazione, dal colore del nodo treeview, alla dimensione del datagrid, ecc.?

Sì. Consideralo come il presentatore che fornisce XML che descrive i dati e la vista che prende i dati e applica ad essi un foglio di stile CSS. In termini concreti, il presentatore potrebbe chiamare IRoadMapView.SetRoadCondition(RoadCondition.Slippery)e la vista quindi rappresenterà la strada in colore rosso.

E i dati per i nodi cliccati?

5. Se quando clicco sui treenodes, dovrei passare attraverso il nodo specifico al presenter e quindi da quello il presenter elaborerà i dati di cui ha bisogno e poi chiederà al modello quei dati, prima di presentarli di nuovo alla vista?

Se possibile, passerei tutti i dati necessari per presentare l'albero in una vista in un colpo solo. Ma se alcuni dati sono troppo grandi per essere passati dall'inizio o se sono di natura dinamica e richiedono l '"ultima istantanea" dal modello (tramite il presentatore), aggiungerei qualcosa di simile event LoadNodeDetailsEventHandler LoadNodeDetailsall'interfaccia di visualizzazione, in modo che il il presentatore può iscriversi ad esso, recuperare i dettagli del nodo in LoadNodeDetailsEventArgs.Node(possibilmente tramite il suo ID di qualche tipo) dal modello, in modo che la vista possa aggiornare i dettagli del nodo visualizzati quando il delegato del gestore di eventi ritorna. Tieni presente che potrebbero essere necessari modelli asincroni di questo tipo se il recupero dei dati potrebbe essere troppo lento per una buona esperienza utente.


3
Non penso che tu debba necessariamente disaccoppiare la vista e il presentatore. Di solito disaccoppio il modello e il presentatore, facendo in modo che il presentatore ascolti gli eventi del modello e agisca di conseguenza (aggiorno la vista). La presenza di un presentatore nella visualizzazione facilita la comunicazione tra la visualizzazione e il presentatore.
kasperhj

11
@lejon: Dici che avere un presentatore nella vista facilita la comunicazione tra la vista e il presentatore , ma non sono assolutamente d' accordo. Il mio punto di vista è questo: quando la visualizzazione conosce il presentatore, per ogni evento di visualizzazione la visualizzazione deve decidere quale metodo del presentatore è quello appropriato da chiamare. Si tratta di "2 punti di complessità", poiché la vista non sa realmente quale evento di visualizzazione corrisponde a quale metodo del presentatore . Il contratto non lo specifica.
Johann Gerell

5
@lejon: Se, d'altra parte, la vista espone solo l'evento effettivo, il presentatore stesso (chissà cosa vuole fare quando si verifica un evento di visualizzazione) si iscrive semplicemente per fare la cosa corretta. Questo è solo "1 punto di complessità", che nel mio libro è il doppio di "2 punti di complessità". In generale, meno accoppiamento significa meno costi di manutenzione per tutta la durata del progetto.
Johann Gerell

9
Anch'io tendo ad utilizzare il presenter incapsulato come spiegato in questo link lostechies.com/derekgreer/2008/11/23/… in cui la vista è l'unico titolare del presentatore.
ak3nat0n

3
@ ak3nat0n: Rispetto ai tre stili di MVP spiegati nel link che hai fornito, credo che questa risposta di Johann potrebbe essere più strettamente allineata con il terzo stile che si chiama Stile del presentatore osservante : "Il vantaggio dello stile del presentatore osservante è che disaccoppia completamente la conoscenza del presentatore dalla vista, rendendo la vista meno suscettibile ai cambiamenti all'interno del presentatore. "
DavidRR

11

Il presentatore, che contiene tutta la logica nella vista, dovrebbe rispondere al pulsante cliccato come dice @JochemKempe . In termini pratici, il gestore dell'evento clic del pulsante chiama presenter.OpenFile(). Il presentatore è quindi in grado di determinare cosa dovrebbe essere fatto.

Se decide che l'utente deve selezionare un file, richiama nella vista (tramite un'interfaccia di visualizzazione) e lascia che la vista, che contiene tutti i tecnicismi dell'interfaccia utente, visualizzi il file OpenFileDialog. Questa è una distinzione molto importante in quanto al presentatore non dovrebbe essere consentito di eseguire operazioni legate alla tecnologia dell'interfaccia utente in uso.

Il file selezionato verrà quindi restituito al presentatore che continua la sua logica. Ciò può coinvolgere qualunque modello o servizio dovrebbe gestire l'elaborazione del file.

Il motivo principale per utilizzare un pattern MVP, imo è separare la tecnologia dell'interfaccia utente dalla logica di visualizzazione. Pertanto, il presentatore orchestra tutta la logica mentre la vista la mantiene separata dalla logica dell'interfaccia utente. Questo ha il bell'effetto collaterale di rendere il presentatore completamente testabile.

Aggiornamento: poiché il presentatore è l'incarnazione della logica che si trova in una vista specifica , la relazione vista-presentatore è IMO una relazione uno-a-uno. E per tutti gli scopi pratici, un'istanza di visualizzazione (ad esempio un modulo) interagisce con un'istanza di presentatore e un'istanza di presentatore interagisce con una sola istanza di visualizzazione.

Detto questo, nella mia implementazione di MVP con WinForms il presentatore interagisce sempre con la vista tramite un'interfaccia che rappresenta le capacità dell'interfaccia utente della vista. Non ci sono limitazioni su quale vista implementa questa interfaccia, quindi diversi "widget" possono implementare la stessa interfaccia di visualizzazione e riutilizzare la classe presenter.


Grazie. Quindi nel metodo presenter.OpenFile (), non dovrebbe avere il codice per mostrare l'openfiledialog? Invece dovrebbe tornare nella vista per far vedere quella finestra?
Darren Young,

4
Bene, non lascerei mai che il presentatore apra direttamente le finestre di dialogo, poiché ciò interromperebbe i tuoi test. O scaricalo nella vista o, come ho fatto in alcuni scenari, hai una classe "FileOpenService" separata per gestire l'effettiva interazione del dialogo. In questo modo puoi falsificare il servizio di apertura dei file durante i test. Mettere tale codice in un servizio separato può darti effetti collaterali di riutilizzabilità piacevoli :)
Peter Lillevold

2

Il presentatore dovrebbe agire alla fine della richiesta e mostrare la finestra di openfiledialog come suggerito. Poiché nessun dato è richiesto dal modello, il presentatore può e deve gestire la richiesta.

Supponiamo che tu abbia bisogno dei dati per creare alcune entità nel tuo modello. Puoi passare lo stream attraverso il livello di accesso dove hai un metodo per creare entità dallo stream, ma ti suggerisco di gestire l'analisi del file nel tuo presenter e utilizzare un costruttore o Crea metodo per entità nel tuo modello.


1
Grazie per la risposta. Inoltre, avresti un solo presentatore per la vista? E quel presentatore gestisce la richiesta o, se i dati sono richiesti, delega a un numero qualsiasi di classi modello che agiscono in base alle richieste specifiche? È questo il modo corretto? Grazie ancora.
Darren Young

3
Una vista ha un relatore ma un relatore può avere più visualizzazioni.
JochemKempe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.