سؤال

أحاول أن ألتف حول نمط MVP المستخدم في تطبيق C#/Winforms.لذلك قمت بإنشاء "مفكرة" بسيطة مثل التطبيق لمحاولة حل جميع التفاصيل.هدفي هو إنشاء شيء يقوم بسلوكيات Windows الكلاسيكية المتمثلة في الفتح والحفظ والجديد بالإضافة إلى عكس اسم الملف المحفوظ في شريط العنوان.وأيضًا، عندما تكون هناك تغييرات غير محفوظة، يجب أن يتضمن شريط العنوان علامة *.

لذلك قمت بإنشاء عرض ومقدم لإدارة حالة استمرار التطبيق.أحد التحسينات التي أخذتها في الاعتبار هو كسر كود معالجة النص بحيث يكون العرض/المقدم كيانًا أحادي الغرض حقًا.

هذه لقطه لتكون مرجعا ...

alt text

أقوم بتضمين جميع الملفات ذات الصلة أدناه.أنا مهتم بالتعليقات حول ما إذا كنت قد فعلت ذلك بالطريقة الصحيحة أو إذا كانت هناك طرق للتحسين.

نوتموديل.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();
        }
    }
}
هل كانت مفيدة؟

المحلول

الطريقة الوحيدة للاقتراب من نمط عرض سلبي مثالي لـ MVP هي كتابة ثلاثيات MVP الخاصة بك لمربعات الحوار بدلاً من استخدام مربعات حوار WinForms.ثم يمكنك نقل منطق إنشاء الحوار من العرض إلى مقدم العرض.

يدخل هذا في موضوع التواصل بين ثلاثيات mvp، وهو موضوع عادة ما يتم تجاهله عند فحص هذا النمط.ما وجدته مفيدًا بالنسبة لي هو ربط الثلاثيات بمقدميهم.

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.على أية حال، يجب أن يصبح من الواضح أن مقدم العرض هو وسيط متقن.في ظل ظروف مختلفة، قد تميل إلى إعادة هيكلة الوسيط ولكن هنا يكون المقصود:

  • أبقِ المنطق بعيدًا عن الأنظار، حيث يصعب اختباره
  • تجنب التبعيات المباشرة بين العرض والنموذج

في بعض الأحيان رأيت أيضًا النموذج المستخدم للاتصال الثلاثي MVP.فائدة هذا هي أن مقدم العرض لا يحتاج إلى معرفة وجود بعضهم البعض.ويتم ذلك عادة عن طريق تحديد حالة في النموذج، والتي تؤدي إلى حدث، ثم يستمع إليه مقدم آخر.فكرة مثيرة للاهتمام.واحد لم أستخدمه شخصيًا.

فيما يلي بعض الروابط مع بعض التقنيات التي استخدمها الآخرون للتعامل مع الاتصال الثلاثي:

نصائح أخرى

وكل شيء يبدو جيدا على المستوى الوحيد الممكن وأود أن تذهب أبعد من ذلك هو أن مجردة بعيدا عن منطق لحفظ الملف ووأن تعالج من قبل مقدمي ذلك في وقت لاحق هل يمكن العطف بسهولة في أساليب الادخار بديلة مثل قواعد البيانات والبريد الإلكتروني، وتخزين سحابة.

والمنظمة البحرية الدولية في أي وقت كنت تتعامل مع لمس نظام الملفات فإنه من الأفضل دائما أن مجردة بعيدا المستوى، كما يجعل السخرية واختبار أسهل كثيرا.

وشيء واحد أنا أحب هو التخلص من عرض مباشر للتواصل مقدم للقيام به. والسبب في ذلك هو الرأي على مستوى واجهة المستخدم ومقدم هو في طبقة رجال الأعمال. أنا لا أحب طبقات جهدي لديهم معرفة المتأصلة في بعضها البعض، وأسعى للحد من الاتصال المباشر قدر الإمكان. عادة، نموذج بلدي هو الشيء الوحيد الذي يتجاوز الطبقات. وهكذا مقدم بالتلاعب في الرأي من خلال واجهة، ولكن الرأي لا يأخذ عمل كبير ومباشر ضد مقدم. أنا أحب مقدم لتكون قادرة على الاستماع إلى والتلاعب وجهة نظري على أساس رد الفعل، لكني أحب أيضا للحد من معرفة وجهة نظري له من مقدم لها.

وكنت أضيف بعض الأحداث إلى بلدي IPersistenceStateView:

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

وبعد ذلك يكون لي مقدم الاستماع إلى هذه الأحداث:

public PersistenceStatePresenter(IPersistenceStateView view)
{
    _view = view;

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

   InitializeModel();
   InitializeView();
}

وبعد ذلك تغيير التنفيذ بغية ديك ينقر على زر النار الأحداث.

وهذا يجعل الفعل مقدم أشبه دمية متحركة، ردا على وجهة نظر وسحب السلاسل بها؛ فيها، وإزالة المكالمات مباشرة على أساليب المقدم. عليك لا تزال بحاجة الى مثيل مقدم في طريقة العرض، ولكن هذا عن العمل المباشر الوحيد عليك القيام به على ذلك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top