2013-02-01 91 views
6

我有一個在MVP中實現的WinForms應用程序。我的表單有TextBox,我想將其Text屬性數據綁定到模型中的某個屬性。我不想在視圖中引用模型。MVP winforms中的數據綁定

在谷歌搜索後,我發現通過耦合模型和視圖的數據綁定是一個壞主意。我的樣本初始化爲Model,ViewPresenter如下。

class View : Form, IView 
{ 
    public View() 
    { 
     InitializeComponent(); 
     new Presenter(this); 
    } 
} 

class Presenter 
{ 
    public Presenter(IView) : this.Presenter(this, new Model()) 
    { 
    } 

    public Presenter(IView view) 
    { 
    } 
} 

class Model : IModel 
{ 
    public Model() 
    { 
    } 

} 

目前我有各3個項目爲ModelViewPresenter。查看具有參考PresenterPresenter參考Model。任何人都可以指導我如何形成到Model屬性的控件的數據綁定?

編輯

我知道做的事情的網格。我們可以在演示網的Datasource屬性分配給一個List(或類似的東西),如:

_view.DataSource = _model.ListOfEmployees; 

這將在UI體現價值的時候ListOfEmployees變化模型。但是TextBox哪個公開Text屬性呢?我怎樣才能綁定在MVP架構?

+1

爲什麼在視圖中引用模型將是一個壞主意?我只是向我的演示者添加一個方法,將模型綁定到IView。 –

+1

@WiktorZychla:我在演示者中封裝了視圖和模型。目的是讓演示者以外的任何對象都不應該混淆它的觀點 - 包括模型。否則,在其他對象接管視圖控制的情況下可能發生函數蠕變。 – IAbstract

+0

然後讓您的演示者使用視圖模型,即吸收演示者中的模型並將視圖綁定到演示者,因爲它將是視圖模型。這將是一個推薦的方法。 –

回答

8

我的建議是將視圖和模型封裝在Presenter中。這意味着一個特定的Presenter(在大多數情況下)爲給定的視圖。在我看來,這樣做很好,因爲大多數模型都會有所不同。

class Presenter { 
    readonly IView view; 
    readonly IModel model; 

    public Presenter() { 
     // if view needs ref. to presenter, pass into view ctor 
     view = new View(this); 
     model = new Model(); 
    } 

    // alternatively - with the model injected - my preference 
    public Presenter(IModel Model) { 
     // if view needs ref. to presenter, pass into view ctor 
     view = new View(this); 
     model = Model; 
    } 
} 

在你的iVIEW,暴露或控制的數據源屬性:

interface IView { 
    object GridDataSource { get; set; } 
} 

添加到您的演示一些方法:

void SetGridDatasource() { 
    view.GridDatasource = model.SomeBindableData; 
} 

View實現:

public object GridDatasource { 
    get { return myGridView.DataSource; } 
    set { myGridView.DataSource = value; } 
} 

N ote:
代碼片段未經測試並推薦作爲起點。

更新到評論:
INotifyPropertyChangedIViewIModel之間更新屬性非常重要的機制。

大多數控件確實具有某種綁定功能。我會建議儘可能使用那些DataBinding methods。只需通過IView公開這些屬性,並讓Presenter將這些綁定設置爲IModel屬性。

+0

如果你知道我的意思,那麼對於一個簡單的'{Binding}'來說,代碼太多了。 –

+2

MVP/MVC/MVVM總是有更多的代碼可供使用。 ;) – IAbstract

+0

但如果我需要將綁定添加到文本框的「文本」屬性。它不公開數據源。使用'文本'簡單的文本屬性將只分配和不綁定......任何指針?? – Sandy

3

我在WinForms中涉足了MVP,還有很多問題需要解決。大多數問題來自於你在VS中的事實,並且能夠使用Forms設計器輕鬆設計表單是很好的。

我試用了一個由廣泛使用的WebForms MVP項目開發的WinForms MVP API,但由於表單使用泛型的文件背後的代碼(例如public class TheForm:UserControl),您將失去設計窗體的能力,因爲設計師知道如何處理泛型。

我結束了與核心接口,IPresenter,IView,IViewModel去。即使我沒有添加任何額外的屬性,我總是爲特定的實現創建了一箇中間接口,主要是因爲稍後當我想要額外添加時,容易進行更改。IPresenter採用類型爲IView的同變種generice類型所以在繼承鏈中,我可以製作特定子視圖類型的演示者。最終創建一個對話框實例化一個演示,並調用顯示完成:

ISomePresenter<ISomeView> somePresenter = new SomeFactory.GetSomePresenter(); 
somePresenter.Show(); 

我的觀點持有IViewModel的副本:

public void Show() 
{ 
    ISomeView theView = new V(); 
    theView.ViewModel = new SomePresenterViewModel(); 
    . 
    . 
    . 
} 

沒有回到原來的問題... 的SampleView無法瞭解ISampleViewModel,因此無法將ViewModel進行標準數據綁定而無需在其他位置投射。這在我開發所有這些東西的項目中失控,人們在事件處理程序以及BindingSource嚮導中遍佈各處。 MVP的全部內容都丟失了。

因此,現在我已經非常嚴格地處理事件,並將控件設置爲屬性中的公共屬性(以及附帶的ISampleView屬性),以便演示者可以看到它們或者簡單地創建重複事件以重新啓動該活動將由主持人提起我一直在思考整個Databinding難題。真的,做到這一點的唯一方法是沒有設計人員的支持,並完成設計人員在Presenter的代碼中所做的一切。也許可以使用Designer在.designer.cs文件中獲取自動生成的代碼,但將所有代碼剪切到Presenter中。也許這樣做只是爲了獲得語法等,然後打開一些鍋爐板代碼或創建一個基於生成的代碼片段。您仍然需要訪問視圖上的實際控件以指定綁定,因此還需要將屬性添加到返回控件實例的ISampleView。另外,我建議將BindingSource實例放入Presenter中,或者至少讓Presenter擁有實例的其他類。

我喜歡儘可能地使用設計師,但有時您需要休息一下。 正如我所說的CodePlex上的WinForms MVP項目很棒,但所有的表單設計都是在代碼中完成的。在我的場景中,只有DataBinding需要在代碼中完成,而這實際上並不是一個可視化的東西,因此處理起來更容易。

此外,作爲一個附註,用戶NotifyPropertyWeaver(IL編織)支持完整的數據綁定。這很棒,因爲您可以在視圖模型中創建自動屬性,這使得您的代碼變得簡潔並且更具可讀性,而無需在每個屬性上調用NotifyPropertyChanging等。 IL Fitting with Fody在最終構建輸出步驟之前完成所有後期編譯。非常方便。

無論如何,我希望圍繞這個問題的這個大腦轉儲概念對某個人有價值。我花了很長時間把它整理出來,但對我來說它工作得很好。

史蒂夫

編輯2014年4月23日

你知道嗎,.NET數據綁定是流浪漢一個巨大的痛苦。最近在一個項目中,我們剛剛結束了自己的數據綁定代碼的特定控制,因爲它們都很難處理。

重新思考我最初的答覆還有更近的經驗,核心模型應該保持完全獨立。我傾向於創建我稱之爲ViewModel的ViewModel,它與數據庫進行交談,並且是DataBindable並由View查看。數據綁定讓我非常悲傷,尤其是在處理控件事件時,比如DateTimePicker的ValueChanged。在一種情況下,我有一個開始和結束日期選擇器以及一個複選框,以便將結束日期設置爲開始日期後一天以及我需要考慮的其他範圍規則。在根據某些規則更改值時,將數據綁定配置給虛擬機,事件再次觸發並最終取得我所做的最重要的選擇。我最終不得不放置bool值來幫助瞭解事件處理程序是否應該繼續,然後有潛在的競爭條件或不知道事件處理程序(在另一個線程中)是否應該等待。非常迅速得到混亂。因此,我現在的做法是創建一個大型的MODEL,觸及數據庫,並且可以根據捕獲的數據進行驗證規則檢查,但是我會創建一個小而輕的版本,它只保存數據綁定的屬性。與真實模型的分離仍然存在,並且Presenter/Controller響應的任何事件都可以在表單數據驗證/數據持久化時間從VM複製到主模型。如果我對事件做出響應,然後將vm值綁定到一個更輕的虛擬機,我可以創建一個全新的虛擬機實例並重新分配驗證結果,然後將此新的虛擬機實例設置爲.DataSource當我準備好時,視圖上的BindingSource避免了事件處理器的混亂。

主模型可以響應NotifyPropertyChanged事件更新自己的更改或更好的只是讓主持人在適當的時間做到這一點。

順便說一句,似乎Visual Studio 2012和2013現在處理非常酷的設計師的通用控件。

作爲一個方面說明,我一直在iOS開發最近涉足。有一件事讓我印象深刻,就是他們在MVC中作爲過程的一部分被烘焙的方式,與.NET不同的是,它允許我們採用各種方式來實現它。我從中吸取了一些教訓,並將它們應用於.NET,並發現我的大腦並沒有突破這麼多。我特別喜歡的一件事是列表控件的工作方式,它非常類似於Qt(C++框架)MVC控件。具有單體後端對象列表的能力,但視圖只能保持它在可見區域需要的東西比.NET控件的默認行爲好得多。

無論如何,祝你好運與.NET數據綁定。我個人建議任何新來者......不要使用它,只需讓控制器在適當的時候明確地分配所有的值。但是,如果你感到舒服並且能夠理解煩人的細微差別,我希望我所說的某些東西能夠傳達給某個人。

+0

我會將DataBinding的體驗解釋爲Fine Grained Synchronization的問題,並且轉向中介模型可能是Fowler在http://martinfowler.com/eaaDev/MediatedSynchronization.html和http:/ /martinfowler.com/eaaDev/OrganizingPresentations.html#SynchronizingBetweenLayers感謝您分享您的經驗和錯誤。 – wezzix

5

說明:

如果你想數據綁定一個文本框和相關物業的模式:

第一:和許多人一樣有一定的狀態,無論包含您的屬性必須實現INotifyPropertyChanged接口,這樣當對象屬性被改變,必要的事件被觸發以通知改變的視圖。我會在這方面使用視圖模型作爲模型的一個屬性來封裝您希望將數據綁定到視圖的特定屬性。第二步:您的IView將包含視圖必須實現的viewmodel屬性。

第三:您的視圖將在視圖模型對象上僅使用set訪問器來實現IView屬性,以將每個文本框數據綁定到dto屬性。注意在下面的例子中,我如何在視圖加載後再次手動設置文本框。文本框。當底層模型的viewmodel屬性更改時,現在將更新文本值。這兩種方式(雙向數據綁定)。用用戶輸入編輯文本框會改變底層模型的dto屬性值。

第四:您的演示者將在查看負載時將IView的屬性設置爲Model的屬性一次。例如:請記住,這是一個非常簡化的粗略示例,並沒有像OP所使用的任何Model抽象,但應該爲Winforms MVP中的文本框數據綁定提供一個很好的起點。另一件事,我會改變生產應用程序,將使模型無狀態和移動viewmodel(人)到主持人。

//VIEWMODEL 
public class Person : INotifyPropertyChanged 
{ 
    string _firstName; 
    string _lastName; 
    public string FirstName 
    { 
     get { return _firstName; } 
     set 
     { 
      if(value != _firstName) 
      { 
       _firstName = value; 
       NotifyPropertyChanged("FirstName"); 
      } 
     } 
    } 
    public string LastName 
    { 
     get { return _lastName; } 
     set 
     { 
      if (value != _lastName) 
      { 
       _lastName = value; 
       NotifyPropertyChanged("LastName"); 
      } 
     } 
    } 
    public event PropertyChangedEventHandler PropertyChanged; 

    private void NotifyPropertyChanged(String info) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(info)); 
     } 
    } 

} 

//MODEL 
class Model 
{ 
    Person _person; 
    public Person Person { get { return _person; } } 

    public Model() 
    { 
     //Set default value 
     _person = new Person(){ FirstName = "Test", LastName = "Subject" }; 
    } 

    public void ChangePerson() 
    { 
     //When presenter calls this method, it will change the underlying source field and will reflect the changes in the View. 
     _person.FirstName = "Homer"; 
     _person.LastName = "Simpson"; 
    } 
} 

//PRESENTER 
class Presenter 
{ 
    readonly View _view; 
    readonly Model _model; 
    public Presenter(View view) 
    { 
     _view = view; 
     _model = new Model(); 

     _view.OnViewLoad += Load; 
     _view.OnChangePerson += ChangePerson; 
    } 

    private void Load() 
    { 
     _view.Person = _model.Person; 
    } 

    private void ChangePerson() 
    { 
     _model.ChangePerson(); 
    } 
} 

//IVIEW 
interface IView 
{ 
    Person person { set; } 

    event Action OnViewLoad; 
    event Action OnChangePerson; 
} 

//VIEW 
public partial class View : IView 
{ 
    public View() 
    { 
     Presenter presenter = new Presenter(this); 
     this.Load += (s, e) => OnViewLoad(); //Shorthand event delegate 
     this.btnChange.Click += (s, e) => OnChangePerson(); //Shorthand event delegate 
    } 

    public event Action OnViewLoad; 
    public event Action OnChangePerson; 

    public Person person 
    { //This is how you set textbox two-way databinding 
     set 
     { 
       //Databinding syntax: property of control, source, source property, enable formatting, when to update datasource, null value 
       txtFirstName.DataBindings.Add(new Binding("Text", value, "FirstName", true, DataSourceUpdateMode.OnPropertyChanged, string.Empty)); 
       txtLastName.DataBindings.Add(new Binding("Text", value, "LastName", true, DataSourceUpdateMode.OnPropertyChanged, string.Empty)); 
     } 
    } 

}