Frage

Ich versuche, meinen Kopf um das MVP-Muster in einer C # / WinForms-Anwendung verwendet zu wickeln. Also habe ich ein einfaches „Notizblock“ wie Anwendung zu versuchen, alle Details auszuarbeiten. Mein Ziel ist es, etwas zu schaffen, das die klassischen Fenster Verhalten geöffnet wird, speichern, sowohl neue als auch was den Namen der gespeicherten Datei in der Titelleiste. Auch wenn Änderungen noch nicht bestätigt, soll die Titelleiste eines * enthalten.

Also habe ich einen Blick und einen Moderator, der die Anwendung des Dauerhaftigkeitsstatus verwalten. Eine Verbesserung, die ich in Betracht gezogen habe bricht aus dem Textverarbeitungscode so die Ansicht / Moderator ist wirklich eine einzige Zweckgesellschaft.

Hier ist ein Screenshot als Referenz ...

alt text

Ich bin alle relevanten Dateien unten darunter. Ich bin in der Meinung interessiert, ob ich es richtig gemacht habe oder ob es Möglichkeiten zur Verbesserung.

NoteModel.cs:

public class NoteModel : INotifyPropertyChanged 
{
    public string Filename { get; set; }
    public bool IsDirty { get; set; }
    string _sText;
    public readonly string DefaultName = "Untitled.txt";

    public string TheText
    {
        get { return _sText; }
        set
        {
            _sText = value;
            PropertyHasChanged("TheText");
        }
    }

    public NoteModel()
    {
        Filename = DefaultName;
    }

    public void Save(string sFilename)
    {
        FileInfo fi = new FileInfo(sFilename);

        TextWriter tw = new StreamWriter(fi.FullName);
        tw.Write(TheText);
        tw.Close();

        Filename = fi.FullName;
        IsDirty = false;
    }

    public void Open(string sFilename)
    {
        FileInfo fi = new FileInfo(sFilename);

        TextReader tr = new StreamReader(fi.FullName);
        TheText = tr.ReadToEnd();
        tr.Close();

        Filename = fi.FullName;
        IsDirty = false;
    }

    private void PropertyHasChanged(string sPropName)
    {
        IsDirty = true;
        PropertyChanged.Invoke(this, new PropertyChangedEventArgs(sPropName));
    }


    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

Form2.cs:

public partial class Form2 : Form, IPersistenceStateView
{
    PersistenceStatePresenter _peristencePresenter;

    public Form2()
    {
        InitializeComponent();
    }

    #region IPersistenceStateView Members

    public string TheText
    {
        get { return this.textBox1.Text; }
        set { textBox1.Text = value; }
    }

    public void UpdateFormTitle(string sTitle)
    {
        this.Text = sTitle;
    }

    public string AskUserForSaveFilename()
    {
        SaveFileDialog dlg = new SaveFileDialog();
        DialogResult result = dlg.ShowDialog();
        if (result == DialogResult.Cancel)
            return null;
        else 
            return dlg.FileName;
    }

    public string AskUserForOpenFilename()
    {
        OpenFileDialog dlg = new OpenFileDialog();
        DialogResult result = dlg.ShowDialog();
        if (result == DialogResult.Cancel)
            return null;
        else
            return dlg.FileName;
    }

    public bool AskUserOkDiscardChanges()
    {
        DialogResult result = MessageBox.Show("You have unsaved changes. Do you want to continue without saving your changes?", "Disregard changes?", MessageBoxButtons.YesNo);

        if (result == DialogResult.Yes)
            return true;
        else
            return false;
    }

    public void NotifyUser(string sMessage)
    {
        MessageBox.Show(sMessage);
    }

    public void CloseView()
    {
        this.Dispose();
    }

    public void ClearView()
    {
        this.textBox1.Text = String.Empty;
    }

    #endregion

    private void btnSave_Click(object sender, EventArgs e)
    {
        _peristencePresenter.Save();
    }

    private void btnOpen_Click(object sender, EventArgs e)
    {
        _peristencePresenter.Open();
    }

    private void btnNew_Click(object sender, EventArgs e)
    {
        _peristencePresenter.CleanSlate();
    }

    private void Form2_Load(object sender, EventArgs e)
    {
        _peristencePresenter = new PersistenceStatePresenter(this);
    }

    private void Form2_FormClosing(object sender, FormClosingEventArgs e)
    {
        _peristencePresenter.Close();
        e.Cancel = true; // let the presenter handle the decision
    }

    private void textBox1_TextChanged(object sender, EventArgs e)
    {
        _peristencePresenter.TextModified();
    }
}

IPersistenceStateView.cs

public interface IPersistenceStateView
{
    string TheText { get; set; }

    void UpdateFormTitle(string sTitle);
    string AskUserForSaveFilename();
    string AskUserForOpenFilename();
    bool AskUserOkDiscardChanges();
    void NotifyUser(string sMessage);
    void CloseView();
    void ClearView();
}

PersistenceStatePresenter.cs

public class PersistenceStatePresenter
{
    IPersistenceStateView _view;
    NoteModel _model;

    public PersistenceStatePresenter(IPersistenceStateView view)
    {
        _view = view;

        InitializeModel();
        InitializeView();
    }

    private void InitializeModel()
    {
        _model = new NoteModel(); // could also be passed in as an argument.
        _model.PropertyChanged += new PropertyChangedEventHandler(_model_PropertyChanged);
    }

    private void InitializeView()
    {
        UpdateFormTitle();
    }

    private void _model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "TheText")
            _view.TheText = _model.TheText;

        UpdateFormTitle();
    }

    private void UpdateFormTitle()
    {
        string sTitle = _model.Filename;
        if (_model.IsDirty)
            sTitle += "*";

        _view.UpdateFormTitle(sTitle);
    }

    public void Save()
    {
        string sFilename;

        if (_model.Filename == _model.DefaultName || _model.Filename == null)
        {
            sFilename = _view.AskUserForSaveFilename();
            if (sFilename == null)
                return; // user canceled the save request.
        }
        else
            sFilename = _model.Filename;

        try
        {
            _model.Save(sFilename);
        }
        catch (Exception ex)
        {
            _view.NotifyUser("Could not save your file.");
        }

        UpdateFormTitle();
    }

    public void TextModified()
    {
        _model.TheText = _view.TheText;
    }

    public void Open()
    {
        CleanSlate();

        string sFilename = _view.AskUserForOpenFilename();

        if (sFilename == null)
            return;

        _model.Open(sFilename);
        _model.IsDirty = false;
        UpdateFormTitle();
    }

    public void Close()
    {
        bool bCanClose = true;

        if (_model.IsDirty)
            bCanClose = _view.AskUserOkDiscardChanges();

        if (bCanClose)
        {
            _view.CloseView();
        }
    }

    public void CleanSlate()
    {
        bool bCanClear = true;

        if (_model.IsDirty)
            bCanClear = _view.AskUserOkDiscardChanges();

        if (bCanClear)
        {
            _view.ClearView();
            InitializeModel();
            InitializeView();
        }
    }
}
War es hilfreich?

Lösung

Die einzige Möglichkeit, näher an einen perfekten MVP passive Ansicht Muster zu erhalten wäre eine eigene MVP Triaden für die Dialoge zu schreiben, anstatt die WinForms-Dialoge verwenden. Dann könnten Sie das Dialogerstellungslogik aus der Sicht auf den Vortragenden bewegen.

Dies wird in das Thema der Kommunikation zwischen mvp Triaden, die ein Thema in der Regel ist beschönigt, wenn dieses Muster zu untersuchen. Was ich für mich gefunden Werke Triaden an ihren Moderatoren zu verbinden.

public class PersistenceStatePresenter
{
    ...
    public Save
    {
        string sFilename;

        if (_model.Filename == _model.DefaultName || _model.Filename == null)
        {
            var openDialogPresenter = new OpenDialogPresenter();
            openDialogPresenter.Show();
            if(!openDialogPresenter.Cancel)
            {
                return; // user canceled the save request.
            }
            else
                sFilename = openDialogPresenter.FileName;

        ...

Die Show() Methode, natürlich, ist verantwortlich für eine unerwähnt OpenDialogView zeigt, die den Benutzer Eingabe es akzeptieren würde, und übergeben Sie zusammen mit dem OpenDialogPresenter. In jedem Fall sollte es klar zu werden beginnen, dass ein Moderator ein aufwendiger Mittelsmann ist. Unter anderen Umständen könnten Sie einen Zwischenhändler zu Refactoring versucht sein, aber hier ist sein als beabsichtigt:

  • Halten Logik aus der Sicht, wo es schwieriger ist, zu testen
  • Vermeiden Sie direkte Abhängigkeiten zwischen der Ansicht und dem Modell

Manchmal habe ich auch das Modell für MVP Triade Kommunikation verwendet gesehen habe. Der Vorteil davon ist, die Moderations brauchen einander nicht existieren kennen. Die in der Regel durch das Setzen eines Zustands, in dem Modell erreicht, die ein Ereignis auslöst, die ein anderer Moderator dann hört. Eine interessante Idee. Eines habe ich persönlich nicht verwendet.

Hier ein paar Links mit einigen der Techniken haben andere Wissenschaftler mit Triade Kommunikation beschäftigen:

Andere Tipps

Alles sieht gut die einzig mögliche Stufe I weiter die Datei der Logik zu abstrahieren ist gehen würde, für das Speichern und hat, dass von den Anbietern so später konnte man leicht biegt in alternativen Sparmethoden wie Datenbank, E-Mail, Cloud-Speicher behandelt.

IMO jederzeit behandeln Sie das Dateisystem zu berühren eine Ebene ist es immer besser zu abstrahieren es weg, macht auch spöttisch und Testen viel einfacher.

Eine Sache, die ich tun möchte, ist der direkte Blick auf Presenter Kommunikation loszuwerden. Der Grund dafür ist die Aussicht ist auf der UI-Ebene und der Moderator ist in der Business-Schicht. Ich mag meine Schichten nicht inhärenten Kenntnis voneinander haben, und ich suche die direkte Kommunikation so weit wie möglich zu begrenzen. Typischerweise ist mein Modell das einzige, was Schichten hinausgeht. So bedient der Moderator die Sicht durch die Schnittstelle, aber die Aussicht nicht viel gegen den Moderator direkte Maßnahmen ergreift. Ich mag der Moderator der Lage sein, zu hören und meine Ansicht zu manipulieren basierend auf Reaktion, aber Ich mag auch das Wissen meiner Sicht seines Moderator hat zu begrenzen.

würde ich einige Ereignisse meiner IPersistenceStateView hinzufügen:

event EventHandler Save;
event EventHandler Open;
// etc.

Dann haben meine Presenter auf diese Ereignisse hören:

public PersistenceStatePresenter(IPersistenceStateView view)
{
    _view = view;

    _view.Save += (sender, e) => this.Save();
    _view.Open += (sender, e) => this.Open();
   // etc.

   InitializeModel();
   InitializeView();
}

Dann die Ansicht Implementierung ändern auf die Schaltfläche klickt feuern die Ereignisse zu haben.

Das macht den Moderator wirken eher wie ein puppetmaster, zu der Ansicht zu reagieren und seine Fäden zieht; darin, das Entfernen der direkten Anrufe auf die Methoden des Moderators. Sie werden immer noch den Moderator in der Ansicht instanziiert, aber das ist die einzige direkte Arbeit, die Sie auf es tun werden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top