如何构建C#Winforms模型视图 - 启示录(被动视图)程序?
-
29-09-2019 - |
题
我正在设计一个具有以下基本思想的GUI(类似地以Visual Studio的基本外观和视线为模型):
- 文件导航
- 控制选择器(用于选择在编辑器组件中显示的内容)
- 编辑
- 记录器(错误,警告,确认等)
目前,我将使用TreeView进行文件导航,这是一个用于选择要在编辑器中显示的控件的ListView,以及用于Logger的RichTextbox。编辑器将根据TreeView中选择的内容具有2种类型的编辑模式。编辑器将是用于手动编辑文件中文本的RichTextbox,或者它将是带有拖动/DROV DATAGRIDVIEWS和SUB-TEXTBOX的面板,以在此面板中进行编辑。
我试图遵循被动视图设计模式,以使模型从视图中完全分开,反之亦然。该项目的性质是,我添加的任何组件都需要编辑/删除。因此,我需要在那里从给定的控件到下一个独立。如果今天我正在使用树视图进行文件导航,但是明天我被告知要使用其他东西,那么我想相对轻松地实现新的控件。
我根本不明白如何构建程序。我每个控制都了解一个主持人,但我不知道如何使其起作用,以使我具有控制(子视图)(子视图)的视图(整个程序),使整个视图都可以替换,并且可以替换反映我模型的控件。
在主视图中,应该按被动视图标准轻巧,我是否单独实现子视图?如果是这样,请说我有一个接口inavigator来抽象导航对象的角色。导航器将需要一个主持人和模型才能在导航器视图和主视图之间作用。我觉得我在某个地方的设计模式术语中迷失了方向。
可以找到最相似的问题 这里, ,但它没有足够详细地回答我的问题。
有人会帮助我了解如何“构建”此程序?感谢任何帮助。
谢谢,
丹尼尔
解决方案
抽象很好,但重要的是要记住在某个时候 某物 必须知道一两件事,涉及一两件事,否则我们只会坐在地板上有一堆精美的乐高积木,而不是将它们组装到房子里。
控制/依赖式注入/flippy-dippy-upside-down-whe whe're-calling-it-it-it-it-it this-tim this-tim this tim-tim this this of Thise Containser AutoFac 确实可以帮助将所有这些拼凑在一起。
当我将Winforms应用程序放在一起时,通常会得到重复模式。
我将从 Program.cs
配置AUTOFAC容器然后获取一个实例的文件 MainForm
从中显示 MainForm
. 。有些人将其称为外壳或工作区或桌面,但无论如何,它的“表单”具有菜单栏,显示儿童窗口或子用户控制器,当它关闭时,应用程序退出。
接下来是上述 MainForm
. 。我做一些基本的事情,例如拖放一些 SplitContainers
和 MenuBar
s等在Visual Studio Visual Designer中,然后我开始获得代码的幻想:我将“注入”某些关键接口 MainForm
的构造函数,以便我可以利用它们,以便我的主场可以精心策划儿童控制,而不必真正了解它们。
例如,我可能有一个 IEventBroker
允许各个组件发布或订阅“事件”之类的接口 BarcodeScanned
或者 ProductSaved
. 。这使应用程序的一部分可以以松散的耦合方式响应事件,而不必依靠传统的.NET事件进行接线。例如, EditProductPresenter
与我一起 EditProductUserControl
可以说 this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah))
和 IEventBroker
将检查其该事件的订户列表,并致电他们的回调。例如, ListProductsPresenter
可以聆听该事件并动态更新 ListProductsUserControl
它附着在。净结果是,如果用户将产品保存在一个用户控件中,则另一个用户控件的演示者可以进行反应和更新,如果它恰好是打开的,而无需任何控件必须意识到彼此的存在,则可以 MainForm
不得不编排该活动。
如果我正在设计MDI应用程序,我可能有 MainForm
实施 IWindowWorkspace
具有的接口 Open()
和 Close()
方法。我可以将该接口注入我的各个演示者,以使他们可以打开和关闭其他窗口,而无需他们知道 MainForm
直接地。例如, ListProductsPresenter
可能想打开 EditProductPresenter
并对应 EditProductUserControl
当用户双击数据网格中的一行时 ListProductsUserControl
. 。它可以参考 IWindowWorkspace
- 实际上是 MainForm
, ,但它不需要知道 - 并致电 Open(newInstanceOfAnEditControl)
并假设控件以某种方式显示在应用程序的适当位置。 (这 MainForm
大概,实施将把控件交换为在某个地方的面板上的视图。)
但是到底会怎么 ListProductsPresenter
创造 那个实例 EditProductUserControl
? AutoFac的代表工厂 在这里是一个真正的快乐,因为您可以将代表注入主持人,而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提供的委托来解决所有这些依赖关系。
通常,我发现我在“主持人/视图模型/监督控制器”之间有一对一的对应关系(让我们不太遵守这些差异,因为它们在一天结束时都非常相似)和一个”和A”UserControl
/Form
“。 这 UserControl
在其构造函数中接受演示者/视图模型/控制器,并根据适当的方式进行数据冠军,并尽可能地推荐给主持人。有些人隐藏 UserControl
从主持人通过界面,就像 IEditProductView
, ,如果视图不是完全被动的,这将很有用。我倾向于将数据用于所有内容,因此通信是通过 INotifyPropertyChanged
而且不要打扰。
但是,如果主持人无耻地与视野息息相关,您将使您的生活更加轻松。您的对象模型中的属性是否与数据指标不隔离?公开一个新物业,这样可以。你永远不会有一个 EditProductPresenter
和 EditProductUserControl
使用一个布局,然后想编写与同一主持人一起使用的用户控件的新版本。您只需对它们进行编辑,它们是所有意图和目的的一个单元,一个功能,仅存在的主持人,因为它易于单位可测试,并且用户控件没有。
如果您希望可以更换功能,则需要将整个功能抽象。所以你可能有一个 INavigationFeature
接口您 MainForm
与之交谈。你可以有一个 TreeBasedNavigationPresenter
实施 INavigationFeature
并被 TreeBasedUserControl
. 。而且您可能有一个 CarouselBasedNavigationPresenter
这也实现了 INavigationFeature
并被 CarouselBasedUserControl
. 。用户控制和演示者仍然齐头并进,但是您的 MainForm
不必关心它是否与基于树的视图或基于旋转木马的视图进行交互,您可以在没有的情况下将它们交换 MainForm
成为明智的人。
结束时,很容易混淆自己。每个人都是学费的,并使用略有不同的术语来传达它们在类似建筑模式之间的微妙(并且经常不重要的)差异。以我的拙见,依赖注入确实会构建可构建,可扩展的应用,因为耦合被保留下来。将功能分离为“主持人/视图/控制器”和“视图/用户控件/表单”的质量奇迹,因为大多数逻辑都将其拉入了前者,从而可以轻松地对其进行单位测试;结合这两个原则似乎确实是您想要的,您只是对术语感到困惑。
或者,我可能会充满它。祝你好运!
其他提示
我知道这个问题已经快2岁了,但我发现自己处于非常相似的情况。像您一样,我已经搜寻了几天,没有找到适合我需求的具体示例 - 我搜索的次数越多,我越多地回到相同的网站,以至于我拥有大约10页的紫色Google中的链接!
无论如何,我想知道您是否曾经为这个问题提出令人满意的解决方案吗?根据我上周的阅读,我将概述到目前为止的情况:
我的目的是:被动形式,主持人首先(主持人实例化表单,因此表格不了解其主持人)在主持人中通过以形式提出事件(视图)来呼叫方法。
该应用程序具有一个单个Formmain,其中包含2个用户控件:
controlSView(具有3个按钮)DocumentView(第三方图像缩略图查看器)
“主表单”保存了一个用于常规文件的工具栏,保存内容等,几乎没有。 “ ControlSView”用户控件允许用户单击“扫描文档”,它还包含一个TreeView控件,以显示文档的层次结构和页面“ DocumentView”显示扫描文档的缩略图
在我看来,每个控件都应该拥有自己的MVP三合会以及主要形式,但我希望它们都参考相同的模型。我只是无法确定如何协调控件之间的通信。
例如,当用户单击“扫描”时,ControlSenter负责从扫描仪中获取图像,我希望它在每个页面从扫描仪中返回时将页面添加到TreeView中 - 没问题 - 但是我也想要缩略图同时出现在文档中(由于主持人彼此不了解的问题)。
我的解决方案是让ControlSenter调用模型中的方法以将新页面添加到业务对象中,然后在模型中提出了“ pageaddd”事件。
然后,我同时将ControlSpresenter和DocumentPresenter“聆听”此事件,以便ControlStesenter告诉它的视图将新页面添加到TreeView中,并且DocumentPresenter告诉添加新的缩略图。
总结:
控制视图 - 提高活动“ ScanButtonClicked”
控件演示者 - 听到活动,呼吁扫描仪类获取如下:
GDPictureScanning scanner = new GDPictureScanning();
IEnumerable<Page> pages = scanner.AquireImages();
foreach (Page page in pages)
{
m_DocumentModel.AddPage(page);
//The view gets notified of new pages via events raised by the model
//The events are subscribed to by the various presenters so they can
//update views accordingly
}
当每个页面扫描时,扫描循环称为“收益返回新页面(pageID)”。以上方法调用m_documentModel.addpage(page)。新页面已添加到模型中,该模型提出了一个事件。两者控制主持人和文档主持人“听”事件,并相应地添加项目。
我不确定的一点是所有演示者的初始化 - 我在program.cs中进行此操作:如下:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
IDocumotiveCaptureView view = new DocumotiveCaptureView();
IDocumentModel model = new DocumentModel();
IDocumotiveCapturePresenter Presenter = new DocumotiveCapturePresenter(view, model);
IControlsPresenter ControlsPresenter = new ControlsPresenter(view.ControlsView, model);
IDocumentPresenter DocumentPresenter = new DocumentPresenter(view.DocumentView, model);
Application.Run((Form)view);
}
不确定这是好,坏还是无动于衷!
无论如何,在一个两年历史的问题上,这是一个巨大的帖子 - 不过要获得一些反馈...