簡體   English   中英

MVP winforms中的數據綁定

[英]Databinding in MVP winforms

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

在谷歌搜索后,我發現通過耦合模型和視圖進行數據綁定是一個壞主意。 我的ModelViewPresenter示例初始化如下。

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 View參考了PresenterPresenterModel引用。 任何人都可以指導我如何在View的控件中形成數據綁定到Model的屬性?

編輯

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

_view.DataSource = _model.ListOfEmployees;

ListOfEmployees在模型中發生更改時,這將反映UI中的值。 但是暴露Text屬性的TextBox呢? 我怎樣才能在MVP架構中綁定它?

我的建議是將視圖和模型封裝在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; }
}

添加到您的Presenter的一些方法:

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

查看實施:

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

注意:
代碼段未經測試,建議作為起點。

更新評論:
INotifyPropertyChanged是一種非常有價值的機制,用於更新IViewIModel之間的屬性。

大多數控件都具有某種綁定功能。 我建議盡可能使用這些DataBinding方法 只需通過IView公開這些屬性,然后讓Presenter將這些綁定設置為IModel屬性。

說明:

如果要將文本框數據綁定到模型的底層屬性:

第一:與許多其他狀態一樣,包含屬性的任何內容都必須實現INotifyPropertyChanged接口,以便在更改object屬性時觸發必要的事件以通知視圖更改。 在這方面,我將使用viewmodel作為模型的屬性來封裝您希望將視圖數據綁定的特定屬性。

第二:您的IView將包含View必須實現的viewmodel屬性。

第三:您的View將實現IView屬性,viewmodel對象上只有一個set訪問器,用於將每個文本框數據綁定到dto屬性。 請注意,在下面的示例中,我如何在視圖加載后再次手動設置文本框。 現在,當底層模型的viewmodel屬性更改時,將更新textbox.text值。 這適用於兩種方式(雙向數據綁定)。 使用用戶輸入編輯文本框將更改基礎模型的dto屬性值。

第四:您的演示者只會在視圖加載時將IView的屬性設置為Model的屬性一次。

示例:請記住,這是一個非常簡化的粗略示例,並且沒有像OP正在使用的任何模型抽象,但應該為Winforms MVP中的文本框數據綁定提供一個良好的起點。 我將在生產應用程序中更改的另一件事是使Model無狀態並將viewmodel(person)移動到演示者中。

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

}

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

我嘗試了一個從廣泛使用的WebForms MVP項目開發的WinForms MVP API,但由於表單背后的代碼使用Generics(例如公共類TheForm:UserControl),你失去了設計表單的能力,因為設計師知道如何處理泛型。

我最終選擇了核心接口,IPresenter,IView,IViewModel。 我總是為特定的實現創建一個中間接口,即使我沒有添加任何額外的屬性,主要是因為當我想要添加額外的內容時,它更容易適應更改.IPresenter采用類型為IView的共同變量generice類型所以在繼承鏈中,我可以制作特定子視圖類型的演示者。 最后,通過實例化Presenter並調用Show來創建對話框:

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屬性),以便Presenter可以看到它們或者只是創建重復事件來重新激活事件由Presenter接收我一直在思考整個數據綁定難題。 實際上,唯一的方法是在沒有設計師支持的情況下完成設計師在Presenter中的代碼中所做的一切。 也許使用Designer在.designer.cs文件中獲取自動生成的代碼,但將所有代碼剪切到Presenter中。 也許這樣做一次以獲得正確的語法等,然后抨擊一些鍋爐板代碼或根據生成的內容創建一個片段。 您仍然需要訪問視圖上的實際控件以指定綁定,因此還要同時向ISampleView添加一個返回控件實例的屬性。 此外,我建議將BindingSource實例也放在Presenter中,或者至少將Presenter保存實例的其他類放入其中。

我喜歡盡可能多地使用Designer,但有時你需要休息一下。 正如我所說,CodePlex上的WinForms MVP項目非常棒,但所有表單設計都是通過代碼完成的。 在我的場景中,只有DataBinding需要在代碼中完成,這實際上並不是一個視覺事物,所以它更容易處理。

另外,作為旁注,用戶NotifyPropertyWeaver(IL Weaving)支持完全數據綁定。 它的優點在於您可以在視圖模型中創建自動屬性,這樣可以使代碼簡潔易讀,而無需在每個屬性上調用NotifyPropertyChanging等。 IL Weaving with Fody在最終構建輸出步驟之前完成所有后編譯。 非常方便。

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

史蒂夫

編輯2014-04-23

你知道嗎,.NET Databinding是一個巨大的痛苦。 最近在一個項目中,我們最終為特定控件滾動了我們自己的數據綁定代碼,因為它只是難以使用。

重新思考我最初的回答,進一步的近期經驗,核心模型應該完全分開。 我傾向於創建我所謂的ViewModel,它與數據庫通信並且是DataBindable並且由View看到。 數據綁定讓我非常悲痛,特別是在處理控制事件時,比如DateTimePicker的ValueChanged。 在一個場景中,我有一個開始日期和結束日期選擇器以及一個復選框,用於將結束日期設置為開始日期之后的一天以及我需要考慮的其他范圍規則。 在根據某些規則更改值時將數據綁定配置到VM,事件再次觸發並最終覆蓋我所做的選擇。 我最終不得不放置bool值來幫助知道事件處理程序是否應該繼續,然后是潛在的競爭條件或者不知道事件處理程序(在另一個線程中)是否應該等待。 很快變得凌亂。

因此,從現在開始,我的方法是創建一個接觸數據庫的大型模型,並根據捕獲的數據進行驗證規則檢查,但我將創建一個小型,更輕的版本,它只包含數據綁定的屬性。 與真實模型的分離仍然存在,Presenter / Controller響應的任何事件都可以在表單數據驗證/數據持久時間從VM復制到主模型。 如果我正在響應事件,那么設置vm值被綁定到那么一個重量輕得多的VM我可以創建一個全新的VM實例並重新分配驗證結果,然后將這個新的VM實例設置為.DataSource我准備好時,View上的BindingSource可以避免事件處理程序的混亂。

主模型可以響應NotifyPropertyChanged事件以在更改時更新自己,甚至可以更好地讓演示者在正確的時間執行此操作。

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

作為旁注我最近在iOS開發中涉足過。 有一件事讓我印象深刻的是他們在MVC中作為流程的一部分而被烘焙的方式,與.NET不同,它允許我們提出各種各樣的黑客行為方式。 我從中吸取了一些教訓並將它們應用到.NET中,發現我的大腦沒有那么多。 我特別喜歡的是列表控件的工作方式,這與Qt(C ++框架)MVC控件非常相似。 擁有單個后端對象列表但視圖只能在可見區域中保存所需內容的能力比.NET控件默認行為要好得多。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM