如何构造C#WinForms模型视图呈现器(被动视图)程序? - c#

我正在设计一个具有以下基本思想的GUI(类似地模仿Visual Studio的基本外观):

文件导航
控制选择器(用于选择在编辑器组件中显示的内容)
编辑
记录器(错误,警告,确认等)

现在,我将使用TreeView进行文件导航,使用ListView选择要在编辑器中显示的控件,并使用RichTextBox记录器。根据在TreeView中选择的内容,编辑器将具有2种类型的编辑模式。编辑器将是一个RichTextBox,用于手动编辑文件中的文本,或者它将是一个带有拖放数据面板视图的面板和子TextBox,以便在此面板中进行编辑。

我试图遵循Passive View设计模式,以将Model与View完全分离,反之亦然。该项目的性质是,我添加的任何组件都必须进行编辑/删除。因此,我需要独立于给定的控件到下一个控件。如果今天我使用TreeView进行文件导航,但是明天我被告知要使用其他方法,那么我想相对轻松地实现一个新控件。

我根本不了解如何构建程序。我了解每个控件一个Presenter,但是我不知道如何使它工作,以至于我拥有一个带有控件(子视图)的视图(程序的整个GUI),使得整个视图以及单个视图都可以替换。反映我的模型的控件。

在主视图(按被动视图标准应该是轻量级的)中,我是否单独实现子视图?如果是这样,说我有一个接口INavigator来抽象Navigator对象的角色。导航器将需要一个Presenter和一个Model才能在Navigator视图和主视图之间起作用。我感觉好像迷失在某个地方的设计模式行话中。

可以在here中找到最相似的问题,但是它没有足够详细地回答我的问题。

有人能帮我理解如何“构建”该程序吗?感谢您的帮助。

谢谢,

丹尼尔

参考方案

抽象是好的,但重要的是要记住,在某个时候某事物必须对一两个事物了解一两个事物,否则我们只会将一堆精美抽象的乐高玩具坐在地板上,而不是将它们组装成一堆。一个房子。

控制反转/依赖注入/ flippy-dippy-upside-down-无论我们如何称呼本周之类的容器,例如Autofac都可以真正帮助将它们拼凑在一起。

当我将WinForms应用程序放在一起时,通常会得到重复的模式。

我将从一个Program.cs文件开始,该文件配置Autofac容器,然后从中获取MainForm的实例,并显示MainForm。有人将其称为外壳,工作区或桌面,但无论如何,它是具有菜单栏并显示子窗口或子用户控件的“窗体”,并且在关闭时退出应用程序。

接下来是前面提到的MainForm。我在Visual Studio视觉设计器中做了一些基本的事情,例如拖放SplitContainersMenuBar等,然后我开始在代码中花哨:我将某些关键接口“注入”到了MainForm的构造函数,以便我可以使用它们,以便MainForm可以编排子控件,而无需真正了解它们。

例如,我可能有一个IEventBroker界面,该界面允许各种组件发布或订阅“事件”(如BarcodeScannedProductSaved)。这允许应用程序的某些部分以松散耦合的方式响应事件,而不必依赖于连接传统的.NET事件。例如,与我的EditProductPresenter一起出现的EditProductUserControl可能会说this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah)),而IEventBroker会检查该事件的订户列表并调用其回调。例如,ListProductsPresenter可以侦听该事件并动态更新其附加到的ListProductsUserControl。最终结果是,如果用户将产品保存在一个用户控件中,则另一个用户控件的演示者可以在打开的情况下做出反应并进行自我更新,而无需任何控件都知道彼此的存在,并且无需MainForm必须安排该事件。

如果我正在设计MDI应用程序,则可能要让MainForm实现具有IWindowWorkspaceOpen()方法的Close()接口。我可以将该界面注入我的各种演示者中,以允许他们打开和关闭其他窗口,而无需他们直接了解MainForm。例如,当用户双击ListProductsPresenter中数据网格中的一行时,EditProductPresenter可能想要打开EditProductUserControl和相应的ListProductsUserControl。它可以引用IWindowWorkspace(实际上是MainForm,但不需要知道),然后调用Open(newInstanceOfAnEditControl)并假定控件以某种方式显示在应用程序的适当位置。 (MainForm实现可能会将控件交换到面板上某处的视图中。)

但是ListProductsPresenter将如何创建EditProductUserControl的实例? Autofac's delegate factories在这里是一个真正的快乐,因为您只需将一个代表注入到演示者中,Autofac就会自动将其连接起来,就好像它是一个工厂一样(下面是伪代码):


public class EditProductUserControl : UserControl
{
public EditProductUserControl(EditProductPresenter presenter)
{
// initialize databindings based on properties of the presenter
}
}

public class EditProductPresenter
{
// Autofac will do some magic when it sees this injected anywhere
public delegate EditProductPresenter Factory(int productId);

public EditProductPresenter(
ISession session, // The NHibernate session reference
IEventBroker eventBroker,
int productId) // An optional product identifier
{
// do stuff....
}

public void Save()
{
// do stuff...
this.eventBroker.Publish("ProductSaved", new EventArgs(this.product));
}
}

public class ListProductsPresenter
{
private IEventBroker eventBroker;
private EditProductsPresenter.Factory factory;
private IWindowWorkspace workspace;

public ListProductsPresenter(
IEventBroker eventBroker,
EditProductsPresenter.Factory factory,
IWindowWorkspace workspace)
{
this.eventBroker = eventBroker;
this.factory = factory;
this.workspace = workspace;

this.eventBroker.Subscribe("ProductSaved", this.WhenProductSaved);
}

public void WhenDataGridRowDoubleClicked(int productId)
{
var editPresenter = this.factory(productId);
var editControl = new EditProductUserControl(editPresenter);
this.workspace.Open(editControl);
}

public void WhenProductSaved(object sender, EventArgs e)
{
// refresh the data grid, etc.
}
}

因此ListProductsPresenter知道Edit功能集(即,编辑演示者和编辑用户控件)-很好,他们可以并驾齐驱-但不必知道关于Edit功能集的所有依赖关系的说明,而是依靠Autofac提供的委托为其解决所有这些依赖关系。

通常,我发现我在“演示者/视图模型/监督控制器”之间存在一对一的对应关系(让我们在当天结束时不要太着迷于它们之间的差异)。 UserControl / Form”。 UserControl在其构造函数中接受演示者/视图模型/控制器,并在适当时对自身进行数据绑定,从而尽可能多地推迟给演示者。有人通过UserControl之类的界面从演示者隐藏IEditProductView,如果视图不是完全被动的,则很有用。我倾向于对所有内容都使用数据绑定,因此通信是通过INotifyPropertyChanged完成的,不要打扰。

但是,如果演示者与视图无懈可击,您将使生活变得更加轻松。对象模型中的属性是否与数据绑定无关?公开一个新的属性。您永远不会拥有具有一个布局的EditProductPresenterEditProductUserControl,然后想要编写与同一演示者一起使用的用户控件的新版本。您将只对它们进行编辑,它们对于所有意图和目的都是一个单元,一项功能,演示者仅存在,因为它很容易进行单元测试,而用户控件则不行。

如果您希望功能是可替换的,则需要像这样抽象整个功能。因此,您可能具有与INavigationFeature进行对话的MainForm接口。您可以具有实现TreeBasedNavigationPresenterINavigationFeature,并由TreeBasedUserControl使用。您可能有一个同时实现CarouselBasedNavigationPresenter且被INavigationFeature占用的CarouselBasedUserControl。用户控件和演示者仍然可以并驾齐驱,但是您的MainForm不必关心它是否与基于树的视图或基于轮播的视图进行交互,并且您可以将它们换出而无需是更明智的。

最后,很容易混淆自己。每个人都是书呆子,并且使用略有不同的术语来传达相似构架模式之间的细微差异(通常不重要)。在我的拙见中,依赖关系注入对于构建可组合的,可扩展的应用程序确实产生了奇迹,因为保持了耦合。将功能分为“演示者/视图模型/控制器”和“视图/用户控件/表单”确实对质量产生了疑问,因为大多数逻辑都被拉入了前者,因此可以轻松地对其进行单元测试;而结合这两个原则似乎确实是您要寻找的,您只是对术语感到困惑。

或者,我可能会充满。祝好运!

LeetCode题解计算机为什么是基于二进制的?

可以是三进制么?二进制有什么好处?题解:为什么叫电子计算机?算盘应该没有二进制

LeetCode题解统计城市的所有灯泡

这个是我刚毕业的时候,一个真实的面试题,这是一个开放题。题目描述:想办法,将一个城市的所有灯泡数量统计出来。题解:费米估算法1、如果某个城市常驻人口有1000万2、假设每5人居住在一套房里,每套房有灯泡5只,那么住宅灯泡共有1000万只3、假设公众场所每10人共享一只灯泡,那么共有100万只4、主要的这两者相加就得出了1100万只当然实际上这是估算的,具体应…

LeetCode题解黑白圆盘

一个圆盘被涂上了黑白二色,两种颜色各占一个半圆。圆盘以一个未知的速度、按一个未知的方向旋转。你有一种特殊的相机可以让你即时观察到圆上的一个点的颜色。你需要多少个相机才能确定圆盘旋转的方向?题解:可以用一个相机即可

LeetCode题解圆上任取三点构成锐角三角形的概率

来自字节跳动的一道几何题题解:1/4

LeetCode题解深度优先遍历和回溯的关系?

深度优先遍历的范围更大还是回溯的范围更大?为什么?题解:我的理解是:dfs是回溯思想的一种体现- 回溯:是在整个搜索空间中搜索出可行解,在搜索过程中不断剪枝回退,这是回溯的思想,这个搜索空间并没有限制于特定的数据结构。- dfs:dfs是指特定的数据结构中如图,树(特殊的图)中搜索答案,范围限制在了特定的数据结构。个人拙见。