Model-View-Presenter in WinForms

89

Ich versuche zum ersten Mal, die MVP-Methode mit WinForms zu implementieren.

Ich versuche die Funktion jeder Schicht zu verstehen.

In meinem Programm habe ich eine GUI-Schaltfläche, die beim Klicken ein offenes Dialogfenster öffnet.

Bei Verwendung von MVP verarbeitet die GUI das Schaltflächenklickereignis und ruft dann presenter.openfile () auf.

Sollte das in presenter.openfile () dann das Öffnen dieser Datei an die Modellebene delegieren oder, da keine Daten oder Logik zu verarbeiten sind, einfach auf die Anforderung reagieren und das Fenster openfiledialog öffnen?

Update: Ich habe beschlossen, ein Kopfgeld anzubieten, da ich das Gefühl habe, weitere Unterstützung zu benötigen, und vorzugsweise auf meine spezifischen Punkte unten zugeschnitten, damit ich Kontext habe.

Okay, nachdem ich mich über MVP informiert habe, habe ich beschlossen, die passive Ansicht zu implementieren. Tatsächlich habe ich eine Reihe von Steuerelementen auf einer Winform, die von einem Präsentator verwaltet werden, und dann die Aufgaben, die an die Modelle delegiert wurden. Meine spezifischen Punkte sind unten:

  1. Wenn die Winform geladen wird, muss sie eine Baumansicht erhalten. Habe ich Recht, wenn ich denke, dass die Ansicht daher eine Methode wie: presenter.gettree () aufrufen sollte, die wiederum an das Modell delegiert, das die Daten für die Baumansicht abruft, erstellt und konfiguriert und an die zurückgibt Moderator, der wiederum an die Ansicht übergeht, die sie dann beispielsweise beispielsweise einem Panel zuweist?

  2. Wäre dies für jede Datensteuerung auf der Winform dasselbe, da ich auch eine Datagrid-Ansicht habe?

  3. Meine App verfügt über eine Reihe von Modellklassen mit derselben Assembly. Es unterstützt auch eine Plugin-Architektur mit Plugins, die beim Start geladen werden müssen. Würde die Ansicht einfach eine Präsentationsmethode aufrufen, die wiederum eine Methode aufruft, die die Plugins lädt und die Informationen in der Ansicht anzeigt? Welche Ebene würde dann die Plugin-Referenzen steuern? Würde die Ansicht Verweise auf sie oder den Moderator enthalten?

  4. Bin ich zu Recht der Meinung, dass die Ansicht alle Aspekte der Präsentation behandeln sollte, von der Farbe des Baumansichtsknotens bis zur Größe des Datagrids usw.?

Ich denke, dass sie meine Hauptanliegen sind und wenn ich verstehe, wie der Fluss für diese sein sollte, denke ich, dass es mir gut gehen wird.

Darren Young
quelle
Dieser Link lostechies.com/derekgreer/2008/11/23/… erklärt einige der Stile von MVP. Es könnte sich zusätzlich zu Johanns hervorragender Antwort als hilfreich erweisen.
ak3nat0n

Antworten:

123

Dies ist meine bescheidene Einstellung zu MVP und Ihren spezifischen Problemen.

Erstens ist alles, mit dem ein Benutzer interagieren oder nur angezeigt werden kann, eine Ansicht . Die Gesetze, Verhaltensweisen und Eigenschaften einer solchen Ansicht werden durch eine Schnittstelle beschrieben . Diese Schnittstelle kann mithilfe einer WinForms-Benutzeroberfläche, einer Konsolen-Benutzeroberfläche, einer Web-Benutzeroberfläche oder gar keiner Benutzeroberfläche implementiert werden (normalerweise beim Testen eines Präsentators). Die konkrete Implementierung spielt keine Rolle, solange sie den Gesetzen der Ansichtsoberfläche entspricht .

Zweitens wird eine Ansicht immer von einem Präsentator gesteuert . Die Gesetze, Verhaltensweisen und Eigenschaften eines solchen Präsentators werden auch durch eine Schnittstelle beschrieben . Diese Schnittstelle hat kein Interesse an der konkreten Ansichtsimplementierung, solange sie den Gesetzen ihrer Ansichtsschnittstelle entspricht.

Drittens , da ein Präsentator seine Ansicht kontrolliert, um Abhängigkeiten zu minimieren, ist es wirklich kein Vorteil, wenn die Ansicht überhaupt etwas über seinen Präsentator weiß. Es gibt einen vereinbarten Vertrag zwischen dem Präsentator und der Ansicht, der von der Ansichtsoberfläche angegeben wird.

Die Implikationen von Third sind:

  • Der Präsentator verfügt über keine Methoden, die die Ansicht aufrufen kann, aber die Ansicht enthält Ereignisse, die der Präsentator abonnieren kann.
  • Der Moderator kennt seine Sichtweise. Ich ziehe es vor, dies mit einer Konstruktorinjektion auf dem Betonpräsentator zu erreichen.
  • Die Ansicht hat keine Ahnung, welcher Moderator sie steuert. Es wird einfach nie ein Moderator zur Verfügung gestellt.

Für Ihr Problem könnte das Obige in etwas vereinfachtem Code so aussehen:

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

Darüber hinaus habe ich normalerweise eine Basisschnittstelle IView, in der ich die Show()Eigentümeransicht oder den Ansichtstitel, von dem meine Ansichten normalerweise profitieren, aufbewahre.

Auf Ihre Fragen:

1. Wenn die Winform geladen wird, muss sie eine Baumansicht erhalten. Habe ich Recht, wenn ich denke, dass die Ansicht daher eine Methode wie: presenter.gettree () aufrufen sollte, die wiederum an das Modell delegiert, das die Daten für die Baumansicht abruft, sie erstellt und konfiguriert und an die zurückgibt Moderator, der wiederum an die Ansicht übergeht, die sie dann beispielsweise beispielsweise einem Panel zuweist?

Ich würde rufen IConfigurationView.SetTreeData(...)aus IConfigurationPresenter.ShowView(), direkt vor dem AufrufIConfigurationView.Show()

2. Wäre dies für jede Datensteuerung auf der Winform gleich, da ich auch eine Datagrid-Ansicht habe?

Ja, das würde ich fordern IConfigurationView.SetTableData(...). Es liegt an der Ansicht, die ihm gegebenen Daten zu formatieren. Der Präsentator befolgt einfach den Vertrag der Ansicht, wonach Tabellendaten gewünscht werden.

3. Meine App verfügt über eine Reihe von Modellklassen mit derselben Assembly. Es unterstützt auch eine Plugin-Architektur mit Plugins, die beim Start geladen werden müssen. Würde die Ansicht einfach eine Präsentationsmethode aufrufen, die wiederum eine Methode aufruft, die die Plugins lädt und die Informationen in der Ansicht anzeigt? Welche Ebene würde dann die Plugin-Referenzen steuern? Würde die Ansicht Verweise auf sie oder den Moderator enthalten?

Wenn die Plugins auf Ansichten bezogen sind, sollten die Ansichten über sie Bescheid wissen, nicht jedoch der Präsentator. Wenn es um Daten und Modelle geht, sollte die Ansicht nichts mit ihnen zu tun haben.

4. Habe ich Recht, wenn ich denke, dass die Ansicht alle Aspekte der Präsentation behandeln sollte, von der Farbe des Baumansichtsknotens bis zur Größe des Datagrids usw.?

Ja. Stellen Sie sich das als Präsentator vor, der XML bereitstellt, das Daten beschreibt, und die Ansicht, die die Daten aufnimmt und ein CSS-Stylesheet darauf anwendet. Konkret könnte der Moderator anrufen IRoadMapView.SetRoadCondition(RoadCondition.Slippery)und die Ansicht zeigt die Straße dann in roter Farbe.

Was ist mit Daten für angeklickte Knoten?

5. Wenn ich beim Klicken auf die Treenodes den spezifischen Knoten an den Präsentator weiterleiten sollte, würde der Präsentator dann herausfinden, welche Daten er benötigt, und dann das Modell nach diesen Daten fragen, bevor er sie wieder der Ansicht präsentiert?

Wenn möglich, würde ich alle Daten, die erforderlich sind, um den Baum in einer Ansicht zu präsentieren, auf einmal übergeben. Aber wenn einige Daten zu groß sind, um von Anfang an weitergegeben zu werden, oder wenn sie von Natur aus dynamisch sind und den "neuesten Schnappschuss" des Modells (über den Präsentator) benötigen, würde ich event LoadNodeDetailsEventHandler LoadNodeDetailsder Ansichtsoberfläche so etwas hinzufügen , dass die Der Präsentator kann es abonnieren und die Details des Knotens LoadNodeDetailsEventArgs.Node(möglicherweise über seine ID) aus dem Modell abrufen, damit die Ansicht die angezeigten Knotendetails aktualisieren kann, wenn der Event-Handler-Delegat zurückkehrt. Beachten Sie, dass möglicherweise asynchrone Muster erforderlich sind, wenn das Abrufen der Daten für eine gute Benutzererfahrung zu langsam ist.

Johann Gerell
quelle
3
Ich denke nicht, dass Sie die Ansicht und den Moderator unbedingt entkoppeln müssen. Normalerweise entkopple ich das Modell und den Präsentator, wobei der Präsentator Modellereignisse abhört und entsprechend handelt (Ansicht aktualisieren). Ein Präsentator in der Ansicht erleichtert die Kommunikation zwischen Ansicht und Präsentator.
Kasperhj
11
@lejon: Sie sagen, dass ein Moderator in der Ansicht die Kommunikation zwischen Ansicht und Moderator erleichtert , aber ich bin absolut anderer Meinung. Mein Standpunkt ist folgender: Wenn die Ansicht den Präsentator kennt, muss die Ansicht für jedes Ansichtsereignis entscheiden, welche Präsentationsmethode die richtige ist, die aufgerufen werden soll. Das sind "2 Punkte Komplexität", da die Ansicht nicht wirklich weiß, welches Ansichtsereignis welcher Präsentationsmethode entspricht . Der Vertrag sieht das nicht vor.
Johann Gerell
5
@lejon: Wenn andererseits die Ansicht nur das tatsächliche Ereignis verfügbar macht, abonniert der Präsentator selbst (der weiß, was er tun möchte, wenn ein Ansichtsereignis auftritt) es nur, um das Richtige zu tun. Das ist nur "1 Punkt Komplexität", was in meinem Buch doppelt so gut ist wie "2 Punkte Komplexität". Im Allgemeinen bedeutet weniger Kopplung weniger Wartungskosten während der Laufzeit eines Projekts.
Johann Gerell
9
Ich neige auch dazu, den gekapselten Präsentator zu verwenden, wie in diesem Link lostechies.com/derekgreer/2008/11/23/… erläutert, in dem die Ansicht der alleinige Inhaber des Präsentators ist.
ak3nat0n
3
@ ak3nat0n: In Bezug auf die drei MVP-Stile, die in dem von Ihnen angegebenen Link erläutert wurden, glaube ich, dass diese Antwort von Johann am ehesten mit dem dritten Stil übereinstimmt, der als Observing Presenter Style bezeichnet wird : "Der Vorteil des Observing Presenter-Stils besteht darin, dass Dadurch wird das Wissen über den Präsentator vollständig von der Ansicht entkoppelt, wodurch die Ansicht weniger anfällig für Änderungen innerhalb des Präsentators wird. "
DavidRR
11

Der Präsentator, der die gesamte Logik in der Ansicht enthält, sollte auf die Schaltfläche reagieren, auf die geklickt wird, wie @JochemKempe sagt . In der Praxis ruft der Ereignishandler für Schaltflächenklicks auf presenter.OpenFile(). Der Präsentator kann dann bestimmen, was zu tun ist.

Wenn der Benutzer eine Datei auswählen muss, ruft er die Ansicht (über eine Ansichtsoberfläche) zurück und lässt die Ansicht, die alle technischen Details der Benutzeroberfläche enthält, die anzeigen OpenFileDialog. Dies ist eine sehr wichtige Unterscheidung, da der Präsentator keine Vorgänge ausführen darf, die an die verwendete UI-Technologie gebunden sind.

Die ausgewählte Datei wird dann an den Präsentator zurückgegeben, der seine Logik fortsetzt. Dies kann jedes Modell oder jeden Dienst betreffen, der die Verarbeitung der Datei übernehmen soll.

Der Hauptgrund für die Verwendung eines MVP-Musters imo besteht darin, die UI-Technologie von der Ansichtslogik zu trennen. Somit orchestriert der Präsentator die gesamte Logik, während die Ansicht sie von der UI-Logik trennt. Dies hat den sehr schönen Nebeneffekt, dass der Präsentator vollständig testbar ist.

Update: Da der Präsentator die Verkörperung der Logik ist, die in einer bestimmten Ansicht gefunden wird , ist die Ansicht-Präsentator-Beziehung IMO eine Eins-zu-Eins-Beziehung. Für alle praktischen Zwecke interagiert eine Ansichtsinstanz (z. B. ein Formular) mit einer Präsentatorinstanz, und eine Präsentatorinstanz interagiert nur mit einer Ansichtsinstanz.

In meiner Implementierung von MVP mit WinForms interagiert der Präsentator jedoch immer mit der Ansicht über eine Schnittstelle, die die UI-Fähigkeiten der Ansicht darstellt. Es gibt keine Einschränkung, welche Ansicht diese Schnittstelle implementiert. Daher können verschiedene "Widgets" dieselbe Ansichtsschnittstelle implementieren und die Präsentatorklasse wiederverwenden.

Peter Lillevold
quelle
Vielen Dank. In der presenter.OpenFile () -Methode sollte es also nicht den Code zum Anzeigen des openfiledialogs geben? Stattdessen sollte es zurück in die Ansicht gehen, damit das Fenster angezeigt wird?
Darren Young
4
Richtig, ich würde den Moderator niemals direkt Dialogfelder öffnen lassen, da dies Ihre Tests unterbrechen würde. Verlagern Sie dies entweder in die Ansicht oder lassen Sie, wie ich es in einigen Szenarien getan habe, eine separate "FileOpenService" -Klasse die eigentliche Dialoginteraktion übernehmen. Auf diese Weise können Sie den Dateiöffnungsdienst während der Tests fälschen. Das Einfügen eines solchen Codes in einen separaten Dienst kann zu netten Nebenwirkungen bei der Wiederverwendbarkeit führen :)
Peter Lillevold
2

Der Präsentator sollte auf Anfrage reagieren und das von Ihnen vorgeschlagene OpenFiledialog-Fenster anzeigen. Da für das Modell keine Daten erforderlich sind, kann und sollte der Präsentator die Anforderung bearbeiten.

Angenommen, Sie benötigen die Daten, um einige Entitäten in Ihrem Modell zu erstellen. Sie können den Stream entweder an die Zugriffsebene übergeben, auf der Sie eine Methode zum Erstellen von Entitäten aus dem Stream haben. Ich empfehle jedoch, das Parsen der Datei in Ihrem Präsentator durchzuführen und einen Konstruktor oder eine Methode zum Erstellen pro Entität in Ihrem Modell zu verwenden.

JochemKempe
quelle
1
Danke für die Antwort. Hätten Sie auch einen einzigen Moderator für die Ansicht? Und dieser Präsentator bearbeitet entweder die Anforderung, oder wenn Daten erforderlich sind, delegiert er an eine beliebige Anzahl von Modellklassen, die auf die spezifischen Anforderungen reagieren? Ist das der richtige Weg? Danke noch einmal.
Darren Young
3
Eine Ansicht hat einen Präsentator, aber ein Präsentator kann mehrere Ansichten haben.
JochemKempe