我试图弄清楚如何改进 WinForms 体验以及分离逻辑和表示。我遇到了一个带有简短示例的 MVP 模板,并试图弄清楚如何使用它。目前我有以下代码,但我认为我做错了什么。此外,多线程的实现根本不适合这个模型。因此问题是,如何在 WinForms 中正确实施 MVP 设计模式?
查看实现
interface ICustomer
{
string FirstName { get; set; }
string LastName { get; set; }
string SurName { get; set; }
string Address { get; set; }
string Code { get; set; }
event Action Search;
event Action Cancel;
}
public partial class Customer : Form, ICustomer
{
public string FirstName
{
get { return lblFirstName.Text; }
set { lblFirstName.Text = value; }
}
public string LastName
{
get { return lblLastName.Text; }
set { lblLastName.Text = value; }
}
public string SurName
{
get { return lblSurname.Text; }
set { lblSurname.Text = value; }
}
public string Address
{
get { return lblAddress.Text; }
set { lblAddress.Text = value; }
}
public string Code
{
get { return txtCode.Text; }
set { txtCode.Text = value; }
}
public event Action Search;
public event Action Cancel;
public Customer()
{
InitializeComponent();
}
private void btnSearch_Click(object sender, EventArgs e)
{
Search();
}
private void btnCancel_Click(object sender, EventArgs e)
{
Cancel();
}
}
演示器实现
class CustomerPresenter
{
public ICustomer View;
public ICustomerModel Model;
public CustomerPresenter(ICustomer view, ICustomerModel model)
{
View = view;
Model = model;
View.Search += View_Search;
View.Cancel += View_Cancel;
}
private void View_Search()
{
new Task(() => {
View.FirstName = "Alex Krass";
}).Start();
}
private void View_Cancel()
{
}
}
模式与挑战
我的模型还是空的,按照我的理解,应该没有问题,通过Unity IoC调用所有这些耻辱。
class UnityIoC
{
private static UnityContainer unityContainer;
public static UnityContainer Instance
{
get
{
if (unityContainer == null) CreateContainer();
return unityContainer;
}
}
private static void CreateContainer()
{
unityContainer = new UnityContainer();
unityContainer.RegisterType<ICustomer, Customer>();
unityContainer.RegisterType<ICustomerModel, CustomerModel>();
}
}
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ApplicationContext context = new ApplicationContext()
{
MainForm = UnityIoC.Instance.Resolve<CustomerPresenter>().View as Form
};
context.MainForm.Show();
Application.Run(context);
}
那么,相应地,当我单击“搜索”按钮时,我无法更新 UI,我真的必须将 TextBox 本身转发给 Presenter 中的 ICustomer 并调用 BeginInvoke 吗?或者我只是误解了 MVP 的实施?
更新:
在不阻塞的情况下更新 UI 的问题似乎已经通过使用计时器解决了,当信息需要在完成时Task
和之后TaskScheduler
,当它需要在完成后更新时Task
。
**Вариант 1**
timer = new Timer() { Interval = 1000 };
timer.Tick += timer_Tick;
timer.Start();
private void timer_Tick(object sender, EventArgs e)
{
UpdateView();
}
**Вариант 2**
task = new Task(new Action(UpdateModel));
task.ContinueWith(new Action<Task>(UpdateView), TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
private void UpdateView(Task task = null)
{
view.SomeVal = SomeVal;
view.SomeValNext = SomeValNext;
}
关于设计模式的解释和实现的问题几乎总是有争议的,所以我立即警告你,下面写的所有内容都是我基于我自己的逻辑、理解和使用 WinForms 的实践的个人设想。您可以在关于 Habré 的文章中阅读解释和实施 MVP 的另一种选择
类比生物学,让我们从只包含简单控件(按钮、图片、带或不带输入的文本字段)的最简单的单细胞(单窗口)应用程序开始。
1. 单细胞
让我们定义模式的组件以及应用程序的哪些元素属于它们。
M - 模型 - 一个单独的类封装数据工作。对数据的所有操作都只在其中执行。如果我们在显微镜下考虑模型,那么我们可以考虑多线程或异步处理繁重的计算、访问服务和数据库以及模型中固有的其他层。
V - 视图 - 模型数据的可视化表示。这包括我们的所有控件,到目前为止是唯一一个从选定角度实际显示我们模型的窗体。
P - Presenter - 我不会发明一个特殊的翻译,我们将专注于一个较长但或多或少准确的定义 - 一个组件,负责从模型接收数据并知道如何以及何时显示它,并且还处理用户输入,为适当的方法拉取模型并处理模型事件。
在最简单的情况下,表示器将是表单类,在其逻辑中,我们组织数据从模型到控件的传输以呈现数据,以及控制事件的处理以将用户操作传输到模型。
2. 多细胞
当出现多个窗口或出现复杂的控件时,以前的应用程序模型仍然存在,但使用它变得不方便。
让我们添加一个 presenter 类,我们上面描述的单细胞类将作为表示。它的主要任务是将模型的正确部分传递给私人演示者,从中聚合事件,并以正确的顺序和数量将这些事件传递给模型,以及将模型事件路由给私人演示者。对于私人主持人——一般的主持人将充当模特。也可以在其中实现对模型的多线程/异步访问,因为 它只与模型和子演示者有关,与私人演示者负责的 UI 元素无关。
因此,事实证明,我们的应用程序由许多简单的、相对独立的片段组成,在高级演示者的指导下,确保他们的工作一致。
3.改变模式
应用程序增长并变得更加复杂,在某些时候甚至多细胞模型也变得不方便。
现在是记住 WinForms 支持数据绑定的时候了。在这方面,您可以稍微更改模板。它通常在网上被称为MVPVM。
这里出现了一个新组件 - VM - 视图模型,并且演示者的角色发生了一些变化。VM 本质上是要显示的模型的一部分。Presenter 的任务将不再包括将数据传输到从属视图,而只是创建到必要 VM 的绑定、将绑定绑定到视图以及处理事件。而且我们原则上可以拒绝一个普通的演示者,因为 创建下一个控件时,您只需将所需的 VM 传输给它,剩下的就由它自己完成。的确,在复杂的情况下,我们仍然需要一个事件聚合器,尤其是当来自不同控件的事件相互关联或相互冲突时。
该模型允许最灵活地扩展应用程序的功能和缩放。
4。结论
尽管我分别考虑了每个模型,但事实上,随着应用程序变得越来越复杂,它们会顺畅地依次出现,并且在实践中,所考虑的模型都不会以其纯粹的形式实际出现,即使是看似简单的应用程序也可能需要复杂的组合解决方案,反之亦然。只需遵循逻辑、常识和原则——“越简单越好”。
根据我建议的选项,您使用第二个选项,其中唯一缺少的是
CustomerPresenter
搜索完成的事件,您可以获取数据进行显示,表单可以订阅。或者定义一个方法ICustomer
并在表单中实现(或覆盖表单的 Control.Update 方法)并在搜索完成并且可以从主线程访问结果时调用它。Customer
DataUpdate
CustomerPresenter
短算法:
Customer
我们将搜索请求传递给CustomerPresenter
.CustomerPresenter
将请求传递给模型CustomerPresenter
接收搜索完成事件,从模型接收搜索结果,并:Customer
, 获取搜索结果 yCustomerPresenter
取决于前一项,并显示它们。搜索过程中界面无空闲时间。
也可以根据现有的场景:
Customer
我们将搜索请求传递给CustomerPresenter
.CustomerPresenter
在单独的线程中调用模型中的搜索方法。主线程的组件从现在开始可以免费进行其他有用的工作。Customer
, 获取搜索结果 yCustomerPresenter
取决于前一项,并显示它们。也可以把一个长搜索包在一个异步方法里,已经在里面等待搜索线程完成而不挂界面,不过我只是理论上想象这个选项,我并没有亲手做。