【C#】多个视图的Winforms如何实现MVP(Model-View-Presenter)模式

网上已经有很多讲MVC、MVP、MVVM模式的区别和原理的文章,这里不细说了。具体可以翻本文的参考资料。这里主要讲讲实际项目中的一些经验。

背景

工作原因接手一个用C#开发的Winforms软件,代码大概一万多行,业务逻辑完全和界面混在一起,没有单元测试。考虑到后面还要优化、修改,每次下手之前读代码都得半天,于是决定重构成MVP模式。

MVP模式又分为Passive ViewSupervising Controller,为了便于测试,这里选了Passive View模式,把View和Model彻底隔离。基本架构如下:

  • 一个Model,多个Views;
  • Views由一个主窗体MainForm和多个子视图UserControl组成;
  • 每个View都有一个接口IView,并分别对应一个Presenter;
  • Views和Model互相不知道对方,且不引用任何一个Presenter;
  • 程序调用Application.Run(MainForm)之前初始化所有的Presenter、Views和Model;
  • Presenter的构造函数中传入对应的IView和Model;

Model的实现

通常WinformMVP模式中,只有一个model,这样可以保证presenter和其他弹出式form的构造函数只需要传入一个model。如果model中包含的内容比较多,可以包含其他类作为属性。

如果model中的属性是类,需要清空该类中的所有属性时,不能采用new的方法,最好是写一个方法,将所有属性赋初始值。示例代码如下:

// Winform MVP模式中的 model和view结构
namespace My.Models
{
    // MVP中的主 model
    public class Model
    {
        public Class1 SubModel1 { get; set; }
        public Class2 SubModel2 { get; set; }

    }

    public class Class1
    {
        // 初始化 Class1 中的属性值
        public void Clear() { }
    }

    public class Class2
    {
        // 初始化 Class2 中的属性值
        public void Clear() { }
    }
}

namespace My.Presenters
{
    using Models;
    
    // MVP中的主 presenter
    public class Presenter
    {
        private Model _model;
        private Class1 _subModel1;
        private Class2 _subModel2;

        // 构造函数
        public Presenter(Model model)
        {
            _model = model;
            _subModel1 = _model.SubModel1;
            _subModel2 = _model.SubModel2;
        }
    }
}

UserControl和MainForm之间的值传递

UserControl的用法可以参考之前写的文章。

MainForm和UserControl为父子关系,两者都有接口和presenter,有公共属性与各自控件的值绑定。如果不考虑复用,有以下两种情况:

FormUserControl
直接将UserControl拖动添加到Form中,在Form中添加公共属性指向UserControl,就可以直接在Form的Presenter中传值给UserControl。

// Form View
public class Form1
{
    private TextBox textBox1;
    private UC1 uc1;
    
    //公共属性与textBox控件值绑定
    public string TxtValue1
    {
        get { return textBox1.Text; }
        set { textBox1.Text = value; }
    }
    
    // UserControl实例可以通过公共属性获取
    public UC1 UC1 => uc1; 
}

// UserControl View
public class UC1 : UserControl
{
    private TextBox textBox2;
    public string TxtValue2
    {
        get { return textBox2.Text; }
        set { textBox2.Text = value; }
    }
}

// Form Presenter
public class MainPresenter
{
    private Form1 _view;
    
    public MainPresenter(Form1 view)
    {
        _view = view;
    }

    public void Method1()
    {
        // 直接通过公共属性访问
        _view.UC1.TxtValue2 = _view.TxtValue1;
    }
}

UserControlForm
在UserControl中使用parent属性可以操作Form的公共属性。

// UserControl View
public string ID2
{
    get { return textBox1.Text; }
}

private void textBox1_TextChanged(object sender, EventArgs e)
{
    var textBoxContent = this.textBox1.Text;
     //这样UserControl与特定的Form1产生依赖,复用UC时需要更改代码
    var parent = this.Parent as Form1;  
    parent.ID2 = ID2;  
}

// Form View
public string ID2
{
    set { textBox1.Text = value; }
}

更优雅的实现方式,使用事件委托
参考:Transferring information between two forms


参考资料:
[1] 界面之下:还原真实的 MVC、MVP、MVVM 模式
[2] MVP Design Pattern for C# Window Form?
[3] Implement MVP for main form and sub-views
[4] Winforms MVP Pattern w/ Multiple Views
[5] Introducing MVP (Model-View-Presenter) Pattern (WinForms)

你可能感兴趣的:(C#,MVP模式,UserControl,winforms,设计模式)