2009-09-14 86 views
14

我試圖圍繞在C#/ Winforms應用程序中使用的MVP模式。所以我創建了一個簡單的「記事本」應用程序來試圖解決所有的細節。我的目標是創建一些可以打開,保存,新建的經典窗口行爲,並在標題欄中反映已保存文件的名稱。另外,當有未保存的更改時,標題欄應包含*。評論我的簡單MVP Winforms應用程序

因此,我創建了一個視圖&管理應用程序持久狀態的演示者。我考慮過的一個改進是分解文本處理代碼,以便視圖/演示者是真正的單一目的實體。

這裏是一個屏幕截圖以供參考......

alt text

我在內的所有下面的相關文件。我對我是否以正確的方式完成或者是否有改進的方法感興趣。

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

這個問題已經不在話題上,即使它在發佈時也沒有問題。現在這些問題在_Code Review_上會更好。 – halfer 2014-02-06 22:07:54

回答

5

獲得更接近完美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三元組通信的模型。這樣做的好處是主持人不需要知道彼此存在。它通常通過在模型中設置一個狀態來完成,該狀態觸發一個事件,然後另一個演示者再聽。一個有趣的想法。一個我沒有親自使用。

這裏有一些其他人用來對付黑社會通信技術的幾個環節:

+0

感謝您的反饋。你爲什麼在openDialogPresenter中使用var?你有任何與黑社會溝通有關的鏈接嗎?我想我目前的方法傾向於模型中的狀態,並在事件中引發適當的演示者的操作。這是一個壞主意嗎? – 2009-09-16 13:13:41

+0

我傾向於默認使用var,除非有一個有效的理由不僅僅是個人喜好。我用一些處理MVP黑社會溝通的鏈接更新了我的答案。 – 2009-09-17 19:53:38

2

一切都看起來不錯,我會走得更遠的唯一可能的級別是抽象出保存文件的邏輯,並讓提供程序處理這些邏輯,以便稍後可以輕鬆地在備用保存方法(如數據庫,電子郵件和雲存儲)中進行靈活操作。

海事組織任何時候處理觸摸文件系統總是更好地將其抽離一個級別,也使嘲笑和測試更容易。

+0

當然可以。在這個階段試圖保持簡單。 – 2009-09-14 15:47:13

1

有一件事我喜歡做的是擺脫直接查看演示者通信。原因在於該視圖位於UI級別,演示者位於業務層。我不喜歡我的圖層彼此具有固有的知識,我試圖儘可能地限制直接交流。通常,我的模型是唯一超越層次的東西。因此,演示者通過界面操縱視圖,但該視圖不會對演示者採取直接行動。我喜歡Presenter能夠根據反應傾聽並操縱我的觀點,但我也想限制我的觀點對其主持人的知識。

我一些事件添加到我的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(); 
} 

然後更改視圖實現有按鈕點擊觸發事件。

這使得演示者更像一個傀儡大師,對視圖做出反應並拉動它的字符串;從而消除了演示者方法的直接調用。您仍然必須在視圖中實例化演示者,但這是您要做的唯一直接工作。

+0

我也喜歡這個建議。 – 2009-09-30 14:28:25

+0

@ Travis:這種方法的問題在於,對視圖的控制不再保證只由演示者執行,因爲你需要公開事件。 – 2010-04-14 10:16:48

+0

@Johann:我認爲這不是問題。它使得視圖完全獨立,自成一體並且不知道控制它的是什麼。我發現這增加了靈活性,允許您在不同的上下文中使用視圖,同時仍然利用MVP模式。 – 2010-04-21 01:06:47

相關問題