2013-02-01 90 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)); 
     } 
    } 

}