c# - 如何构建 C# WinForms Model-View-Presenter(被动 View )程序?

标签 c# winforms design-patterns mvp passive-view

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

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

  • 现在,我将使用 TreeView 进行文件导航,使用 ListView 选择要在编辑器中显示的控件,并使用 RichTextBox 进行记录器。编辑器将有 2 种类型的编辑模式,具体取决于在 TreeView 中选择的内容。编辑器要么是一个 RichTextBox,用于手动编辑文件内的文本,要么是一个带有拖放 DataGridViews 和子文本框的面板,用于在此面板中进行编辑。

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

    我根本不明白如何构建程序。我理解每个控件有一个演示者,但我不知道如何使其工作,以便我有一个带有控件( subview )的 View (程序的整个 GUI),这样整个 View 和个人 View 都是可替换的反射(reflect)我的模型的控件。

    在按被动 View 标准应该是轻量级的主 View 中,我是否单独实现 subview ?如果是这样,假设我有一个接口(interface) INavigator 来抽象 Navigator 对象的角色。导航器将需要一个演示者和一个模型来在导航器 View 和主 View 之间起作用。我觉得我迷失在某个地方的设计模式行话中。

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

    有人能帮我理解如何“构建”这个程序吗?我很感激任何帮助。

    谢谢,

    丹尼尔

    最佳答案

    抽象是好的,但重要的是要记住,在某些时候,某些东西必须了解一两件事的一两件事,否则我们只会在地板上放一堆抽象的乐高积木,而不是将它们组装成一个房子。

    控制反转/依赖注入(inject)/flippy-dippy-upside-down-whatever-we-re-call-it-this-week 容器,如 Autofac真的可以帮助将这一切拼凑在一起。

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

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

    接下来是前面提到的MainForm .我做一些基本的事情,比如拖放一些 SplitContainersMenuBar s 等在 Visual Studio 可视化设计器中,然后我开始对代码感兴趣:我将某些关键接口(interface)“注入(inject)”到 MainForm 中。的构造函数,以便我可以使用它们,以便我的 MainForm 可以编排子控件,而不必真正了解它们。

    例如,我可能有一个 IEventBroker允许各种组件发布或订阅“事件”的接口(interface),如 BarcodeScannedProductSaved .这允许应用程序的各个部分以松散耦合的方式响应事件,而不必依赖于连接传统的 .NET 事件。例如,EditProductPresenter我的 EditProductUserControl可以说 this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah))IEventBroker将检查该事件的订阅者列表并调用他们的回调。例如,ListProductsPresenter可以监听该事件并动态更新 ListProductsUserControl它附属于。最终结果是,如果用户将产品保存在一个用户控件中,则另一个用户控件的展示器可以在它碰巧打开时使用react并更新自身,而无需任何一个控件都知道彼此的存在,也无需 MainForm不得不策划那个事件。

    如果我正在设计 MDI 应用程序,我可能有 MainForm实现 IWindowWorkspace具有 Open() 的接口(interface)和 Close()方法。我可以将该界面注入(inject)到我的各种演示者中,以允许他们在不知道 MainForm 的情况下打开和关闭其他窗口。直接地。例如,ListProductsPresenter可能想开一个 EditProductPresenter和相应的 EditProductUserControl当用户在 ListProductsUserControl 中双击数据网格中的一行时.它可以引用 IWindowWorkspace --实际上是MainForm ,但它不需要知道——并拨打 Open(newInstanceOfAnEditControl)并假设控件以某种方式显示在应用程序的适当位置。 (据推测,MainForm 实现会将控件交换到某个面板上的 View 中。)

    但是怎么会ListProductsPresenter创建 EditProductUserControl 的实例? Autofac's delegate factories这是一个真正的乐趣,因为您只需将一个委托(delegate)注入(inject)演示者,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 提供的委托(delegate)来解决所有这些依赖项。

    一般来说,我发现我在“演示者/ View 模型/监督 Controller ”(让我们不要太关注差异,因为在一天结束时它们都非常相似)和“UserControl/Form”。 UserControl在其构造函数中接受演示者/ View 模型/ Controller ,并在适当时将自身数据绑定(bind),尽可能地推迟到演示者。有人隐藏UserControl从演示者通过界面,如 IEditProductView ,如果 View 不是完全被动的,这会很有用。我倾向于对所有内容使用数据绑定(bind),因此通信是通过 INotifyPropertyChanged 完成的。不要打扰。

    但是,如果演示者无耻地与 View 相关联,您将使您的生活变得更加轻松。对象模型中的属性是否与数据绑定(bind)不匹配?公开一个新属性,这样它就可以了。你永远不会有 EditProductPresenter和一个 EditProductUserControl一个布局,然后想编写一个新版本的用户控件,它可以与同一个演示者一起使用。您只需编辑它们,它们都是一个单元,一个功能,仅存在一个单元,因为它易于单元测试而用户控件不是。

    如果您希望某个功能可替换,则需要将整个功能抽象为这样。所以你可能有一个 INavigationFeature您的 MainForm 的界面与。您可以拥有一个 TreeBasedNavigationPresenter实现 INavigationFeature并被 TreeBasedUserControl 消耗.你可能有一个 CarouselBasedNavigationPresenter也实现了 INavigationFeature并被 CarouselBasedUserControl 消耗.用户控件和演示者仍然齐头并进,但您的 MainForm不必关心它是与基于树的 View 还是基于轮播的 View 交互,您可以在没有 MainForm 的情况下将它们交换出去。成为更聪明的人。

    最后,很容易混淆自己。每个人都很迂腐,并使用略有不同的术语来表达相似架构模式之间的细微(通常是不重要的)差异。在我看来,依赖注入(inject)对于构建可组合的、可扩展的应用程序有奇效,因为耦合被抑制了;将功能分为“演示者/ View 模型/ Controller ”和“ View /用户控件/表单”对质量产生了奇迹,因为大多数逻辑都被拉入前者,使其易于进行单元测试;将这两个原则结合起来似乎确实是您正在寻找的东西,您只是对术语感到困惑。

    或者,我可以充满它。祝你好运!

    关于c# - 如何构建 C# WinForms Model-View-Presenter(被动 View )程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4329776/

    相关文章:

    c# - 以图形方式模板化 .NET winforms 应用程序

    oop - OOA中的类(class)设计

    c# - 为什么 Linq 在 Nullable<T> 上的连接与 == 的工作方式不同?

    c# - 在 c#.net 中将多变量从字符串转换为十进制在 asp.net 中抛出类型为 'System.FormatException' 的异常

    c# - Datagridview 未在已打开的表单上更新

    java - 原型(prototype)设计模式Java实现困惑

    object - 值对象内部的业务逻辑

    C#驱动开发?

    c# - Azure Cosmos 数据库引发套接字异常

    C# 在 WndProc 中检测点击并在点击发生后调用函数