繁体   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