7

我有一个用 MVP 实现的 WinForms 应用程序。我的表单有一个TextBox,我想将它Text的属性数据绑定到模型中的一个属性。我不想在视图中引用模型。

在谷歌搜索后,我发现通过耦合模型和视图进行数据绑定是一个坏主意。我的示例初始化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 个项目,每个项目用于Model,ViewPresenter。View 有参考,PresenterPresenter有参考Model。谁能指导我如何将数据绑定到控件中View的属性中Model

编辑

我知道在 Grid 中做事。我们可以将Datasource网格的属性分配给List演示者中的一个(或类似的东西),例如:

_view.DataSource = _model.ListOfEmployees;

ListOfEmployees当模型发生变化时,这将反映 UI 中的值。但是TextBox暴露一个Text属性的 a 呢?如何在 MVP 架构中绑定它?

4

3 回答 3

10

我的建议是将 View 和 Model 封装在 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是一种非常有价值的机制,用于更新 和 之间的IView属性IModel

大多数控件确实具有某种绑定功能。我建议尽可能使用那些DataBinding 方法。只需通过 IView 公开这些属性,然后让 Presenter 将这些绑定设置为 IModel 属性。

于 2013-02-01T20:10:13.720 回答
6

解释:

如果要将文本框数据绑定到模型的基础属性:

第一:像许多其他状态一样,任何包含您的属性的内容都必须实现 INotifyPropertyChanged 接口,以便在更改对象属性时触发必要的事件以通知视图更改。在这方面,我将使用视图模型作为模型的属性来封装您希望将视图数据绑定到的特定属性。

第二:您的 IView 将包含您的 View 必须实现的 viewmodel 属性。

第三:您的视图将在 viewmodel 对象上仅使用 set 访问器实现 IView 属性,以将每个文本框数据绑定到 dto 属性。请注意,在下面的示例中,我再也不会在视图加载后手动设置文本框。现在,当底层模型的 viewmodel 属性更改时, textbox.text 值将被更新。这适用于两种方式(2 路数据绑定)。使用用户输入编辑文本框将更改基础模型的 dto 属性值。

第四:您的演示者只会在视图加载时将 IView 的属性设置为模型的属性一次。

示例:请记住,这是一个非常简单的粗略示例,没有像 OP 那样使用任何模型抽象,但应该为 Winforms MVP 中的文本框数据绑定提供一个很好的起点。我将在生产应用程序中更改的另一件事是使模型无状态并将视图模型(人)移动到演示者中。

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

}
于 2016-07-31T18:20:09.023 回答
3

我在 WinForms 中涉足了 MVP,有很多问题需要解决。大多数问题都来自于您在 VS 中的事实,并且能够使用表单设计器轻松设计表单真是太好了。

我尝试了一个从广泛使用的 WebForms MVP 项目开发的 WinForms MVP API,但是由于表单的隐藏文件代码使用了泛型(例如公共类 TheForm:UserControl),你失去了设计表单的能力,因为设计师知道如何处理泛型。

我最终选择了核心接口 IPresenter、IView、IViewModel。即使我没有添加任何额外的属性,我也总是为特定实现创建一个中间接口,主要是因为当我确实想要添加额外的属性时,它更容易适应以后的更改。IPresenter 采用 IView 类型的协变泛型类型所以在继承链中,我可以制作特定子视图类型的演示者。最后创建一个对话框是通过实例化一个 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 内的代码中所做的一切。也许使用 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 数据绑定是一个巨大的痛苦。最近在一个项目中,我们刚刚为特定控件滚动了自己的数据绑定代码,因为这一切都很难处理。

重新考虑我最初的回复以及最近的经验,核心模型应该完全分开。我倾向于创建一个我称之为 ViewModel 的东西,它与数据库对话并且是 DataBindable 并且被 View 看到。数据绑定让我非常伤心,尤其是在处理控制事件时,比如 DateTimePicker 的 ValueChanged。在一种情况下,我有一个开始和结束日期选择器以及一个复选框,用于将结束日期设置为开始日期后一天以及我需要考虑的其他范围规则。在根据某些规则更改值时将数据绑定配置到 VM 时,事件会再次触发并最终覆盖我所做的选择。我最终不得不输入 bool 值来帮助了解事件处理程序是否应该继续,然后' s 潜在的竞争条件或不知道事件处理程序(在另一个线程中)是否应该等待。很快就会变得混乱。

所以,从现在开始,我的方法是创建一个大模型,它接触数据库,可以根据捕获的数据进行验证规则检查,但我将创建一个更小、更轻的版本,它只包含数据绑定的属性。与真实模型的分离仍然存在,Presenter / Controller 响应的任何事件都可以在表单数据验证/数据持久性时从 VM 复制到主模型。如果我正在响应事件,那么设置 vm 值绑定到哪个更轻的 VM,我可以创建一个全新的 VM 实例并重新分配验证结果,然后将此新 VM 实例设置为 .DataSource当我准备好时视图上的 BindingSource 可以避免事件处理程序混乱。

主模型可以响应 NotifyPropertyChanged 事件来更新自己的变化,或者更好的是让演示者在正确的时间这样做。

顺便说一句,Visual Studio 2012 和 2013 现在似乎在设计器中处理通用控件,这非常酷。

作为旁注,我最近一直在涉足 iOS 开发。我印象非常深刻的一件事是他们在 MVC 中作为流程的一部分烘焙的方式,与 .NET 不同,它允许我们想出各种黑客方式来做这件事。我从中吸取了一些教训并将它们应用于.NET,发现我的大脑并没有那么严重。我特别喜欢的一件事是列表控件的工作方式,它很像 Qt(C++ 框架)的 MVC 控件。拥有单一后端对象列表但视图仅在可见区域中保存所需内容的能力比 .NET 控件的默认行为要好得多。

无论如何,祝 .NET 数据绑定好运。我个人会向任何新来者推荐......不要使用它,只需让控制器在适当的时间明确分配所有值。但是,如果您感到舒服并理解令人讨厌的细微差别,我希望我所说的某些内容能传达给某人。

于 2013-12-05T00:08:43.387 回答