如何构造C#WinForms模型视图呈现器(被动视图)程序? [英] How to Structure a C# WinForms Model-View-Presenter (Passive View) Program?

查看:48
本文介绍了如何构造C#WinForms模型视图呈现器(被动视图)程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在设计一个具有以下基本思想的GUI(以Visual Studio的基本外观为基础进行建模):

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

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

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

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

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

最相似相关的问题可以在此处找到,但找不到足够详细地回答我的问题.

任何人都可以帮助我了解如何构建"该程序吗?感谢您的帮助.

谢谢

丹尼尔

解决方案

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

一个反转控制/依赖注入/flippy-dippy-upside-down-无论我们如何调用本周的容器,例如

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

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

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

但是 ListProductsPresenter 会如何创建 EditProductUserControl 的实例? Autofac的委托工厂在这里真是一件乐事,因为您可以将委托注入演示者和Autofac会自动将其连接起来,就好像它是工厂一样(后面是伪代码):

 公共类EditProductUserControl:UserControl{公共EditProductUserControl(EditProductPresenter演示者){//根据演示者的属性初始化数据绑定}}公共类EditProductPresenter{//当看到它注入到任何地方时,Autofac都会做一些魔术公共委托EditProductPresenter Factory(int productId);公开EditProductPresenter(ISession会话,//NHibernate会话参考IEventBroker eventBroker,int productId)//可选的产品标识符{//做东西....}公共无效Save(){//做东西...this.eventBroker.Publish("ProductSaved",new EventArgs(this.product));}}公共类ListProductsPresenter{私有IEventBroker eventBroker;私人EditProductsPresenter.Factory工厂;私有IWindowWorkspace工作区;公共ListProductsPresenter(IEventBroker eventBroker,EditProductsPresenter.Factory工厂,IWindowWorkspace工作区){this.eventBroker = eventBroker;this.factory =工厂;this.workspace =工作空间;this.eventBroker.Subscribe("ProductSaved",this.WhenProductSaved);}公共无效WhenDataGridRowDoubleClicked(int productId){var editPresenter = this.factory(productId);var editControl = new EditProductUserControl(editPresenter);this.workspace.Open(editControl);}公共无效WhenProductSaved(对象发送者,EventArgs e){//刷新数据网格等}}

因此, ListProductsPresenter 知道 Edit 功能集(即,编辑演示者和编辑用户控件),这很好,他们可以-在手头-但是不需要了解 Edit 功能集的所有依赖项,而是依靠Autofac提供的委托来解决所有问题.那些依赖.

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

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

如果您希望功能是可替换的,则需要像这样抽象整个功能.因此,您可能有一个与您的 MainForm 对话的 INavigationFeature 接口.您可以具有实现 INavigationFeature TreeBasedNavigationPresenter ,并由 TreeBasedUserControl 使用.您可能有一个 CarouselBasedNavigationPresenter ,它也实现了 INavigationFeature ,并由 CarouselBasedUserControl 消耗.用户控件和演示者仍然可以并驾齐驱,但是您的 MainForm 不必关心它是否与基于树的视图或基于轮播的视图交互,您可以交换他们没有明智的 MainForm .

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

或者,我可能已经吃饱了.祝你好运!

I am designing a GUI that has the following basic idea (similarly modeled after Visual Studio's basic look-and-feel):

  1. File navigation
  2. Control selector (for selecting what to display in the Editor component)
  3. Editor
  4. Logger (errors, warnings, confirmation, etc.)

For now, I will be using a TreeView for file navigation, a ListView for selecting controls to be displayed in the Editor and a RichTextBox for the Logger. The Editor will have 2 types of editing modes depending on what is selected in the TreeView. The Editor will either be a RichTextBox for manually editing text inside files, or it will be a Panel with Drag/Drop DataGridViews and sub-TextBoxes for editing in this Panel.

I am attempting to follow the Passive View design pattern for complete separation of Model from View and vice versa. The nature of this project is that any component I add is subject to edit/removal. As such, I need there to independence from a given control to the next. If today I am using a TreeView for file navigation, but tomorrow I am told to use something else, then I want to implement a new control with relative ease.

I simply do not understand how to structure the program. I understand one Presenter per Control, but I do not know how to make it work such that I have a View (the entire GUI of the program) with controls (sub-Views) such that the ENTIRE View is replaceable as well as the individual controls that reflect my model.

In the main View, which is supposed to be lightweight by Passive View standards, do I implement the sub-Views individually? If so, say I have an interface INavigator to abstract the role of the Navigator object. The navigator will need a Presenter and a Model to act between the Navigator View and the main View. I feel like I am getting lost in the design pattern jargon somewhere.

The most similarly-related question can be found here, but it does not answer my question in sufficient detail.

Will anybody please help me understand how to "structure" this program? I appreciate any help.

Thanks,

Daniel

解决方案

Abstraction is good, but it's important to remember that at some point something has to know a thing or two about a thing or two, or else we'll just have a pile of nicely abstracted legos sitting on the floor instead of them being assembled into a house.

An inversion-of-control/dependency injection/flippy-dippy-upside-down-whatever-we're-calling-it-this-week container like Autofac can really help in piecing this all together.

When I throw together a WinForms application, I usually end up with a repeating pattern.

I'll start with a Program.cs file that configures the Autofac container and then fetches an instance of the MainForm from it, and shows the MainForm. Some people call this the shell or the workspace or the desktop but at any rate it's "the form" that has the menu bar and displays either child windows or child user controls, and when it closes, the application exits.

Next is the aforementioned MainForm. I do the basic stuff like drag-and-dropping some SplitContainers and MenuBars and such in the Visual Studio visual designer, and then I start getting fancy in code: I'll have certain key interfaces "injected" into the MainForm's constructor so that I can make use of them, so that my MainForm can orchestrate child controls without really having to know that much about them.

For example, I might have an IEventBroker interface that lets various components publish or subscribe to "events" like BarcodeScanned or ProductSaved. This allows parts of the application to respond to events in a loosely coupled way, without having to rely on wiring up traditional .NET events. For example, the EditProductPresenter that goes along with my EditProductUserControl could say this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah)) and the IEventBroker would check its list of subscribers for that event and call their callbacks. For example, the ListProductsPresenter could listen for that event and dynamically update the ListProductsUserControl that it is attached to. The net result is that if a user saves a product in one user control, another user control's presenter can react and update itself if it happens to be open, without either control having to be aware of each other's existence, and without the MainForm having to orchestrate that event.

If I'm designing an MDI application, I might have the MainForm implement an IWindowWorkspace interface that has Open() and Close() methods. I could inject that interface into my various presenters to allow them to open and close additional windows without them being aware of the MainForm directly. For example, the ListProductsPresenter might want to open an EditProductPresenter and corresponding EditProductUserControl when the user double-clicks a row in a data grid in a ListProductsUserControl. It can reference an IWindowWorkspace--which is actually the MainForm, but it doesn't need to know that--and call Open(newInstanceOfAnEditControl) and assume that the control was shown in the appropriate place of the application somehow. (The MainForm implementation would, presumably, swap the control into view on a panel somewhere.)

But how the hell would the ListProductsPresenter create that instance of the EditProductUserControl? Autofac's delegate factories are a true joy here, since you can just inject a delegate into the presenter and Autofac will automagically wire it up as if it were a factory (pseudocode follows):


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.
    }
}

So the ListProductsPresenter knows about the Edit feature set (i.e., the edit presenter and the edit user control)--and this is perfectly fine, they go hand-in-hand--but it doesn't need to know about all of the dependencies of the Edit feature set, instead relying on a delegate provided by Autofac to resolve all of those dependencies for it.

Generally, I find that I have a one-to-one correspondence between a "presenter/view model/supervising controller" (let's not too caught up on the differences as at the end of the day they are all quite similar) and a "UserControl/Form". The UserControl accepts the presenter/view model/controller in its constructor and databinds itself as is appropriate, deferring to the presenter as much as possible. Some people hide the UserControl from the presenter via an interface, like IEditProductView, which can be useful if the view is not completely passive. I tend to use databinding for everything so the communication is done via INotifyPropertyChanged and don't bother.

But, you will make your life much easier if the presenter is shamelessly tied to the view. Does a property in your object model not mesh with databinding? Expose a new property so it does. You are never going to have an EditProductPresenter and an EditProductUserControl with one layout and then want to write a new version of the user control that works with the same presenter. You will just edit them both, they are for all intents and purpose one unit, one feature, the presenter only existing because it is easily unit testable and the user control is not.

If you want a feature to be replaceable, you need to abstract the entire feature as such. So you might have an INavigationFeature interface that your MainForm talks to. You can have a TreeBasedNavigationPresenter that implements INavigationFeature and is consumed by a TreeBasedUserControl. And you might have a CarouselBasedNavigationPresenter that also implements INavigationFeature and is consumed by a CarouselBasedUserControl. The user controls and the presenters still go hand-in-hand, but your MainForm would not have to care if it is interacting with a tree-based view or a carousel-based one, and you could swap them out without the MainForm being the wiser.

In closing, it is easy to confuse yourself. Everyone is pedantic and uses slightly different terminology to convey they subtle (and oftentimes unimportant) differences between what are similar architectural patterns. In my humble opinion, dependency injection does wonders for building composable, extensible applications, since coupling is kept down; separation of features into "presenters/view models/controllers" and "views/user controls/forms" does wonders for quality since most logic is pulled into the former, allowing it to be easily unit tested; and combining the two principles seems to really be what you're looking for, you're just getting confused on the terminology.

Or, I could be full of it. Good luck!

这篇关于如何构造C#WinForms模型视图呈现器(被动视图)程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆