批评我简单的MVP Winforms应用程序 [英] Critique my simple MVP Winforms app

查看:115
本文介绍了批评我简单的MVP Winforms应用程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图围绕C#/ Winforms应用程序中使用的MVP模式。所以我创建了一个简单的记事本的应用程序来试图找出所有的细节。我的目标是创建一些能够打开,保存,新建的经典窗口行为以及标题栏中保存的文件的名称。此外,当有未保存的更改时,标题栏应包含*。

I'm trying to wrap my mind around the MVP pattern used in a C#/Winforms app. So I created a simple "notepad" like application to try to work out all the details. My goal is to create something that does the classic windows behaviors of open, save, new as well as reflecting the name of the saved file in the title bar. Also, when there are unsaved changes, the title bar should include an *.

所以我创建了一个视图&一个管理应用程序持久状态的演示者。我所考虑的一个改进是打破了文本处理代码,因此视图/演示者是真正的一个单一的实体。

So I created a view & a presenter that manage the application's persistence state. One improvement I've considered is breaking out the text handling code so the view/presenter is truly a single-purpose entity.

这是一个屏幕截图,供参考。 。

Here is a screen shot for reference ...

我正在包括以下所有相关文件。我有兴趣提供反馈意见,说明我是以正确的方式做到,还是有改进的方法。

I'm including all of the relevant files below. I'm interested in feedback about whether I've done it the right way or if there are ways to improve.

NoteModel.cs:

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: p>

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

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

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


推荐答案

获得更完美的MVP被动视图模式的唯一方法就是为对话框编写自己的MVP三元组,而不是使用WinForms对话框。然后,您可以将对话创建逻辑从视图移动到演示者。

The only way to get any closer to a perfect MVP passive view pattern would be to write your own MVP triads for the dialogs instead of using the WinForms dialogs. Then you could move the dialog creation logic from the view to the presenter.

这涉及到mvp三元组之间的通信主题,这是一个在检查时通常被忽略的主题这种模式。我发现对我来说,是在他们的演示者上连接黑社会。

This gets into the topic of communication between mvp triads, a topic which is usually glossed over when examining this pattern. What I've found works for me is to connect triads at their presenters.

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;

        ...

Show() 方法当然是负责显示一个未提及的 OpenDialogView ,它将接受用户输入并将其传递给 OpenDialogPresenter 。无论如何,应该开始明白,演讲者是一位精心设计的中间人。在不同的情况下,你可能会试图重构一个中间人,但这里是有意的:

The Show() method, of course, is responsible for showing an unmentioned OpenDialogView, which would accept the users input and pass it along to the OpenDialogPresenter. In any case, it should be starting to become clear that a presenter is an elaborate middleman. Under different circumstances, you might be tempted to refactor a middleman out but here its is intentional to:


  • 将逻辑保留在视图之外,其中更难测试

  • 避免视图和模型之间的直接依赖关系

有时我也看过用于MVP三元组通信的模式。这样做的好处是演示者不需要彼此认识。它通常通过在模型中设置状态来实现,该状态触发一个事件,另一个演示者然后监听该状态。一个有趣的想法一个我没有亲自使用。

At times I've also seen the model used for MVP triad communication. The benefit of this is the presenter's don't need to know each other exist. Its usually accomplished by setting a state in the model, which triggers an event, which another presenter then listens for. An interesting idea. One I've not used personally.

以下是与其他人用于处理黑社会信息的一些技巧的一些链接:

Here's a few links with some of the techniques others have used to deal with triad communication:

  • http://www.rickardnilsson.net/post/The-Humble-dialog-v2.aspx
  • http://spin.atomicobject.com/2008/01/30/presenter-first-get-your-triads-talking/

这篇关于批评我简单的MVP Winforms应用程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆