简体   繁体   English

MVP winforms中的数据绑定

[英]Databinding in MVP winforms

I have a WinForms application implemented in MVP. 我有一个在MVP中实现的WinForms应用程序。 My form has a TextBox and I want to databind its Text property to a property in the Model. 我的表单有一个TextBox ,我想将其Text属性数据绑定到Model中的属性。 I don't want to refer to the Model in the View. 我不想在视图中引用模型。

After searching in Google, I found that databinding by coupling Model and View is a bad idea. 在谷歌搜索后,我发现通过耦合模型和视图进行数据绑定是一个坏主意。 My sample initialization of Model , View and Presenter is as follows. 我的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()
    {
    }

}

At present I have 3 projects each for Model , View and Presenter . 目前我有3个项目,分别用于ModelViewPresenter View has reference to Presenter and Presenter has reference to Model . View参考了PresenterPresenterModel引用。 Can anyone guide me how to form a databinding to a control in View to a property in Model ? 任何人都可以指导我如何在View的控件中形成数据绑定到Model的属性?

EDIT 编辑

I know to do the things in Grid. 我知道在Grid中做的事情。 We can assign Datasource property of grid to a List (or something similar) in presenter like: 我们可以将网格的Datasource属性分配给演示者中的List (或类似的东西),如:

_view.DataSource = _model.ListOfEmployees;

This will reflect the value in UI when ListOfEmployees changes in the Model. ListOfEmployees在模型中发生更改时,这将反映UI中的值。 But what about a TextBox which exposes a Text property? 但是暴露Text属性的TextBox呢? How can I bind that in MVP architecture? 我怎样才能在MVP架构中绑定它?

My recommendation is to encapsulate the View and Model in the Presenter. 我的建议是将视图和模型封装在Presenter中。 This means a specialized Presenter (in most cases) for a given View. 这意味着给定视图的专用Presenter(在大多数情况下)。 In my opinion, this works out well since most Models will be different anyway. 在我看来,这很好用,因为大多数模型无论如何都会有所不同。

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

In your IView, expose a control or control's data source property: 在IView中,公开控件或控件的数据源属性:

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

Add to your Presenter some method: 添加到您的Presenter的一些方法:

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

View implementation: 查看实施:

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

Note: 注意:
Code snippets are untested and recommended as a starting point. 代码段未经测试,建议作为起点。

Update to comments: 更新评论:
INotifyPropertyChanged is a very valuable mechanism for updating properties between IView and IModel . INotifyPropertyChanged是一种非常有价值的机制,用于更新IViewIModel之间的属性。

Most controls do have some sort of binding capability. 大多数控件都具有某种绑定功能。 I would recommend using those DataBinding methods whenever possible. 我建议尽可能使用这些DataBinding方法 Simply expose those properties through IView and let the Presenter set those bindings to the IModel properties. 只需通过IView公开这些属性,然后让Presenter将这些绑定设置为IModel属性。

Explanation: 说明:

If you want to databind a textbox to and underlying property of the model: 如果要将文本框数据绑定到模型的底层属性:

First: Like many others have states, whatever contains your properties must implement the INotifyPropertyChanged interface so that when the object property is changed, the necessary event is fired to notify the view of the change. 第一:与许多其他状态一样,包含属性的任何内容都必须实现INotifyPropertyChanged接口,以便在更改object属性时触发必要的事件以通知视图更改。 I would use a viewmodel as a property of your model in this regard to encapsulate the specific properties you would like to databind your view to. 在这方面,我将使用viewmodel作为模型的属性来封装您希望将视图数据绑定的特定属性。

Second: Your IView will include the viewmodel property which your View must implement. 第二:您的IView将包含View必须实现的viewmodel属性。

Third: Your View will implement the IView property with only a set accessor on the viewmodel object to databind each textbox to the dto properties. 第三:您的View将实现IView属性,viewmodel对象上只有一个set访问器,用于将每个文本框数据绑定到dto属性。 Notice in the example below, how i never again manually set the textbox after view load. 请注意,在下面的示例中,我如何在视图加载后再次手动设置文本框。 The textbox.text values will now be updated when the underlying model's viewmodel property changes. 现在,当底层模型的viewmodel属性更改时,将更新textbox.text值。 This works both ways (2 way databinding). 这适用于两种方式(双向数据绑定)。 Editing the textbox with user input will change the underlying model's dto property value. 使用用户输入编辑文本框将更改基础模型的dto属性值。

Fourth: Your presenter will set the IView's property to the Model's property only once, on view load. 第四:您的演示者只会在视图加载时将IView的属性设置为Model的属性一次。

Example: keep in mind, this is a very very simplified rough example and does not have any Model abstraction like the OP is using, but should give a good starting point for textbox databinding in Winforms MVP. 示例:请记住,这是一个非常简化的粗略示例,并且没有像OP正在使用的任何模型抽象,但应该为Winforms MVP中的文本框数据绑定提供一个良好的起点。 Another thing i would change in a production app, would be to make the Model stateless and move the viewmodel (person) into the presenter. 我将在生产应用程序中更改的另一件事是使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)); 
         }
     }

}

I dabbled a bit with MVP in WinForms and there are many issues to address. 我在WinForms中涉足MVP并且有很多问题需要解决。 Most of the problems come from the fact that you're in VS and it's nice to be able to design the forms easily using the form Designers. 大多数问题来自于你在VS中的事实,能够使用Designers表单轻松设计表单是很好的。

I tried out a WinForms MVP API someone developed from a widely used WebForms MVP project, but because the code behind file for the form used Generics (eg. public class TheForm : UserControl) you lose the ability to design the form because the designer know how to handle Generics. 我尝试了一个从广泛使用的WebForms MVP项目开发的WinForms MVP API,但由于表单背后的代码使用Generics(例如公共类TheForm:UserControl),你失去了设计表单的能力,因为设计师知道如何处理泛型。

I ended up going with the core interfaces, IPresenter, IView, IViewModel. 我最终选择了核心接口,IPresenter,IView,IViewModel。 I ALWAYS created an intermediate interface for the specific implementation even if I don't add any extra properties, mainly because it's just easier to accomodate changes later when I do want to put extras in. IPresenter take a co-variant generice type of type IView so down the inheritance chain I can make presenters of a specific child view type. 我总是为特定的实现创建一个中间接口,即使我没有添加任何额外的属性,主要是因为当我想要添加额外的内容时,它更容易适应更改.IPresenter采用类型为IView的共同变量generice类型所以在继承链中,我可以制作特定子视图类型的演示者。 In the end creating a dialog is done by instantiating a Presenter and calling Show: 最后,通过实例化Presenter并调用Show来创建对话框:

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

My view holds a copy of the IViewModel: 我的视图包含IViewModel的副本:

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

No back to the original question... The SampleView can't know about the ISampleViewModel, therefore it's impossible to do the standard databinding to the ViewModel without putting a cast in somewhere. 没有回到原来的问题...... SampleView无法了解ISampleViewModel,因此如果不在某个地方放置转换,就不可能对ViewModel进行标准数据绑定。 This got out of hand in the project I developed all this stuff in and people were casting all over the place in event handlers as well as the BindingSource wizards. 在我开发所有这些东西的项目中,这已经失控了,人们在事件处理程序以及BindingSource向导中遍布各处。 The whole point of MVP was lost. MVP的重点丢失了。

So now that I've been getting really strict on how to handle events and either setting the control as public in properties (and an accompanying ISampleView property) so the Presenter can see them or simply creating duplicate events to re-fire the event to be picked up by the Presenter I've been thinking about the whole Databinding conundrum. 所以现在我已经非常严格地处理事件并将控件设置为属性中的公共(以及附带的ISampleView属性),以便Presenter可以看到它们或者只是创建重复事件来重新激活事件由Presenter接收我一直在思考整个数据绑定难题。 Really, the only way to do it is without designer support and do everything the designer does in code inside the Presenter. 实际上,唯一的方法是在没有设计师支持的情况下完成设计师在Presenter中的代码中所做的一切。 Perhaps use the Designer to get the auto generated code in the .designer.cs file, but cut all the code out into the Presenter. 也许使用Designer在.designer.cs文件中获取自动生成的代码,但将所有代码剪切到Presenter中。 Maybe do it once to get the syntax etc right, then bash out some boiler plate code or create a snippet based on what's generated. 也许这样做一次以获得正确的语法等,然后抨击一些锅炉板代码或根据生成的内容创建一个片段。 You would still need access to the actual control on the view in order to specify the binding, so in conjunction also add a property to the ISampleView which returns the control instance. 您仍然需要访问视图上的实际控件以指定绑定,因此还要同时向ISampleView添加一个返回控件实例的属性。 Also, I'd recommend putting the BindingSource instances in the Presenter as well or at least some other class which the Presenter holds an instance to. 此外,我建议将BindingSource实例也放在Presenter中,或者至少将Presenter保存实例的其他类放入其中。

I like to use the Designer as much as possible but sometimes you need to make the break. 我喜欢尽可能多地使用Designer,但有时你需要休息一下。 As I said the WinForms MVP project on CodePlex is great but all form design is done in code. 正如我所说,CodePlex上的WinForms MVP项目非常棒,但所有表单设计都是通过代码完成的。 In my scenario it's only the DataBinding which needs to be done in code, which isn't actually a visual thing anyway so it's easier to deal with. 在我的场景中,只有DataBinding需要在代码中完成,这实际上并不是一个视觉事物,所以它更容易处理。

Also, as a side note, user NotifyPropertyWeaver (IL Weaving) to support full databinding. 另外,作为旁注,用户NotifyPropertyWeaver(IL Weaving)支持完全数据绑定。 It's brilliant in that you can create automatic properties in your view models which keeps your code succinct and more readable without having to put the calls to NotifyPropertyChanging, etc on each property. 它的优点在于您可以在视图模型中创建自动属性,这样可以使代码简洁易读,而无需在每个属性上调用NotifyPropertyChanging等。 The IL Weaving with Fody does all that post compile before the final build output step. IL Weaving with Fody在最终构建输出步骤之前完成所有后编译。 Extremely handy. 非常方便。

Anyway, I hope this brain dump of concepts around the issue are of value to someone. 无论如何,我希望围绕这个问题的大脑概念对某人有价值。 I spent a long time sorting it out but it works pretty well for me. 我花了很长时间整理它,但它对我来说非常好。

Steve 史蒂夫

Edit 2014-04-23 编辑2014-04-23

You know what, .NET Databinding is a massive pain in the bum. 你知道吗,.NET Databinding是一个巨大的痛苦。 Recently on a project we just ended up rolling our own databinding code for a specific control because it all just got to hard to work with. 最近在一个项目中,我们最终为特定控件滚动了我们自己的数据绑定代码,因为它只是难以使用。

Rethinking my initial reply further more recent experiences, the core model should be kept completely separate. 重新思考我最初的回答,进一步的近期经验,核心模型应该完全分开。 I've tended to create what I've called a ViewModel which talks to the database and is DataBindable and seen by the View. 我倾向于创建我所谓的ViewModel,它与数据库通信并且是DataBindable并且由View看到。 Databinding has caused me SO much grief especially when handling control events, like DateTimePicker's ValueChanged. 数据绑定让我非常悲痛,特别是在处理控制事件时,比如DateTimePicker的ValueChanged。 In one scenario I have a start and an end date picker as well as a checked box to set the end date to one day after the start date and other range rules I need to consider. 在一个场景中,我有一个开始日期和结束日期选择器以及一个复选框,用于将结束日期设置为开始日期之后的一天以及我需要考虑的其他范围规则。 With databinding configured to the VM when changing values based on some rule the events fire again and end up overriding choices I've made. 在根据某些规则更改值时将数据绑定配置到VM,事件再次触发并最终覆盖我所做的选择。 I end up having to put bool values to aid in knowing if the event handler should continue or not, then there's potential race conditions or not knowing if an event handler (in another thread) should wait or not. 我最终不得不放置bool值来帮助知道事件处理程序是否应该继续,然后是潜在的竞争条件或者不知道事件处理程序(在另一个线程中)是否应该等待。 Gets messy really quick. 很快变得凌乱。

So, my approach from now on is to create a big MODEL which touches the database and can do validation rule checking based on data captured, but I'll create a small, lighter version which just holds properties for databinding. 因此,从现在开始,我的方法是创建一个接触数据库的大型模型,并根据捕获的数据进行验证规则检查,但我将创建一个小型,更轻的版本,它只包含数据绑定的属性。 Separation to the real model is still in place and any events the Presenter / Controller responds to can just copy from the VM to the main model at form data validation / data persistence time. 与真实模型的分离仍然存在,Presenter / Controller响应的任何事件都可以在表单数据验证/数据持久时间从VM复制到主模型。 If I'm responding to events, then setting vm values being bound to then which a much lighter weight VM I can create a whole new VM instance and re-assign the results of validation, then set this new VM instance as the .DataSource of the BindingSource on the View when I'm ready which avoids the event handler mess. 如果我正在响应事件,那么设置vm值被绑定到那么一个重量轻得多的VM我可以创建一个全新的VM实例并重新分配验证结果,然后将这个新的VM实例设置为.DataSource我准备好时,View上的BindingSource可以避免事件处理程序的混乱。

The main Model could respond to the NotifyPropertyChanged event to update itself on changes or even better just get the presenter to do that at the right time. 主模型可以响应NotifyPropertyChanged事件以在更改时更新自己,甚至可以更好地让演示者在正确的时间执行此操作。

By the way, it seems that Visual Studio 2012 and 2013 now handle generic controls in the designer which is very cool. 顺便说一句,似乎Visual Studio 2012和2013现在处理设计器中的通用控件非常酷。

As a side note I've been dabbling recently in iOS development. 作为旁注我最近在iOS开发中涉足过。 One thing I'm very impressed about is the way they have baked in MVC as part of the process, unlike .NET which allows us to come up with all manner of hack ways of doing it. 有一件事让我印象深刻的是他们在MVC中作为流程的一部分而被烘焙的方式,与.NET不同,它允许我们提出各种各样的黑客行为方式。 I've taken some lessons from this and applied them to .NET and finding my brain isn't breaking so much. 我从中吸取了一些教训并将它们应用到.NET中,发现我的大脑没有那么多。 One thing in particular I love is the way the list controls work, which are much like Qt's (C++ framework) MVC controls. 我特别喜欢的是列表控件的工作方式,这与Qt(C ++框架)MVC控件非常相似。 The ability to have monolithic backend lists of objects yet the view only holds what it needs in the visible area is so much nicer than the .NET controls default behaviour. 拥有单个后端对象列表但视图只能在可见区域中保存所需内容的能力比.NET控件默认行为要好得多。

Anyway, good luck with .NET databinding. 无论如何,祝你好运.NET数据绑定。 I'd personally recommend to any new comers...don't use it and just get the controller to assign all the values explicitly at appropriate times. 我个人建议任何新来的人......不要使用它,只是让控制器在适当的时候明确地分配所有的值。 But if you are comfortable and understand the annoying nuances I hope some of what I've said reaches someone. 但如果你感到舒服并且理解烦人的细微差别,我希望我所说的一些内容能够达到某种程度。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM