如何构建C#WinForms模型 - 视图 - 演示者(被动视图)程序?

Dan*_*and 7 c# mvp design-patterns passive-view winforms

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

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

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

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

我根本不明白如何构建程序.我理解每个Control的一个Presenter,但我不知道如何使它工作,以便我有一个View(程序的整个GUI)和控件(子视图),这样整个视图是可替换的以及个人反映我的模型的控件.

在主视图中,Passive View标准应该是轻量级的,我是否单独实现子视图?如果是这样,请说我有一个接口INavigator来抽象导航器对象的角色.导航器需要Presenter和Model才能在Navigator View和主View之间进行操作.我觉得我迷失在某个地方的设计模式行话中.

可以在这里找到最相似的相关问题,但它没有足够详细地回答我的问题.

有人请帮我理解如何"构建"这个程序吗?我感谢任何帮助.

谢谢,

丹尼尔

Nic*_*cki 23

抽象是好的,但重要的是要记住,在某些时候某事要知道一两件事,否则我们只会在地板上放一堆漂亮抽象的腿,而不是将它们组装成一个房子.

一个反向控制/依赖注入/ flippy-dippy-up- down-无论我们正在调用它本周的容器如Autofac可以真正帮助拼凑这些.

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

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

接下来是前面提到的MainForm.我在Visual Studio可视化设计器中执行基本的操作,例如拖放一些SplitContainersMenuBars等等,然后我开始对代码感兴趣:我将某些关键接口"注入"到MainForm构造函数中,以便我可以使用它们,这样我的MainForm可以协调子控件,而不必真正了解它们.

例如,我可能有一个IEventBroker接口,允许各种组件发布或订阅"事件",如BarcodeScannedProductSaved.这允许应用程序的某些部分以松散耦合的方式响应事件,而不必依赖于连接传统的.NET事件.例如,EditProductPresenterEditProductUserControl可以说this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah)),并且IEventBroker会检查该事件的订阅者列表并调用他们的回调.例如,ListProductsPresenter可以侦听该事件并动态更新ListProductsUserControl它附属于.最终结果是,如果用户将产品保存在一个用户控件中,则另一个用户控件的演示者可以在其打开时做出反应并更新自己,而无需控制必须知道彼此的存在,并且无需MainForm编排那件事.

如果我正在设计一个MDI应用程序,我可能会拥有MainForm一个IWindowWorkspace具有Open()Close()方法的接口.我可以将该界面注入我的各种演示者,以允许他们打开和关闭其他窗口,而无需MainForm直接了解.例如,当用户双击数据网格中的一行时,ListProductsPresenter可能想要打开EditProductPresenter并对应.它可以引用 - 实际上是- 但它不需要知道 - 并且调用并假设控件以某种方式显示在应用程序的适当位置.(据推测,实现可以将控件交换到某个面板上的视图中.)EditProductUserControlListProductsUserControlIWindowWorkspaceMainFormOpen(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提供委托来解决所有这些依赖它.

一般来说,我发现我在"演示者/视图模型/监督控制器"之间有一对一的对应关系(让我们不太注意差异,因为它们在一天结束时都非常相似)和" UserControl/ Form".在UserControl接受在它的构造和databinds本身,只要适合演示/视图模型/控制器,推迟到演示尽可能.有些人UserControl通过界面隐藏了演示者,IEditProductView如果视图不是完全被动的话,这可能很有用.我倾向于使用数据绑定来处理所有内容,因此通过INotifyPropertyChanged并且不打扰进行通信.

但是,如果演示者与视图无耻地联系在一起,您将使您的生活更轻松.对象模型中的属性是否与数据绑定无关?露出一个新的财产,所以它.您永远不会拥有一个EditProductPresenterEditProductUserControl一个布局,然后想要编写一个与同一个演示者一起使用的新版本的用户控件.您将只编辑它们,它们适用于所有意图和目的一个单元,一个功能,仅存在的演示器,因为它易于单元测试而用户控制不是.

如果您希望功能可以替换,则需要抽象整个功能.所以你可能有一个INavigationFeature与你MainForm交谈的界面.你可以拥有一个TreeBasedNavigationPresenter实现INavigationFeature并由a消费的TreeBasedUserControl.你可能有一个CarouselBasedNavigationPresenter也实现INavigationFeature并被a消耗CarouselBasedUserControl.用户控件和演示者仍然是相辅相成的,但是MainForm如果它与基于树的视图或基于轮播的视图进行交互,则不必关心,并且您可以在不MainForm更明智的情况下将它们交换出来.

最后,很容易让自己迷惑.每个人都是迂腐的,并且使用稍微不同的术语来表达它们在类似的建筑模式之间的微妙(并且通常是不重要的)差异.在我看来,依赖注入确实构建了可组合的,可扩展的应用程序,因为耦合被保持下来; 将特征分离为"演示者/视图模型/控制器"和"视图/用户控件/形式"确实对质量产生了奇迹,因为大多数逻辑被拉入前者,使其易于单元测试; 结合这两个原则似乎真的是你正在寻找的,你只是对术语感到困惑.

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