WinForms中的Model-View-Presenter


90

我正在尝试使用WinForms首次实现MVP方法。

我试图了解每一层的功能。

在我的程序中,我有一个GUI按钮,单击该按钮会打开一个openfiledialog窗口。

因此,使用MVP,GUI会处理按钮单击事件,然后调用presenter.openfile();。

在presenter.openfile()中,然后应该将该文件的打开委托给模型层,还是由于没有要处理的数据或逻辑,它是否应仅对请求采取行动并打开openfile对话窗口?

更新: 我决定提供赏金,因为我认为我需要对此提供进一步的帮助,并且最好针对我在下面的特定要点进行调整,以便获得背景信息。

好的,在阅读了MVP之后,我决定实现被动视图。实际上,我将在Winform上具有一堆控件,这些控件将由Presenter处理,然后将任务委派给模型。我的具体观点如下:

  1. 当winform加载时,它必须获取树视图。我是否正确认为该视图因此应调用诸如presenter.gettree()之类的方法,而该方法又将委托给模型,该模型将获取树视图的数据,对其进行创建和配置,然后将其返回给演示者,该演示者又将转到视图,然后将其简单地分配给一个面板?

  2. Winform上的任何数据控件都一样吗,因为我也有一个datagridview?

  3. 我的应用程序具有许多具有相同装配的模型类。它还支持插件体系结构,其中的插件需要在启动时加载。视图是否会简单地调用presenter方法,而该方法又会调用加载插件并在视图中显示信息的方法?哪一层将控制插件引用。视图将保留对它们或演示者的引用吗?

  4. 我是否认为视图应该处理与表示有关的所有事情,从树视图节点的颜色到数据网格大小等,是否正确?

我认为这是我最关心的问题,如果我了解这些工作的流程,我会没事的。


此链接lostechies.com/derekgreer/2008/11/23/…解释了MVP的某些样式。除了Johann的出色回答之外,它还可能会有所帮助。
ak3nat0n 2012年

Answers:


123

这是我对MVP和您的特定问题的谦虚看法。

首先,用户可以与之交互或仅被显示的任何东西都是视图。此类视图的法律,行为和特征由界面描述。可以使用WinForms UI,控制台UI,Web UI甚至根本不使用UI来实现该接口(通常在测试演示者时)-只要遵循其视图接口的规律,具体的实现就无关紧要。

其次,视图始终由演示者控制。此类演示者的法律,行为和特征也通过界面描述。该接口只要遵守其视图接口的规律,就对具体的视图实现不感兴趣。

第三,由于演示者控制其视图,因此要最大限度地减少依赖关系,让视图完全了解其演示者一无所获。演示者和视图之间有一个约定的合同,该合同由视图界面声明。

第三的含义是:

  • 演示者没有该视图可以调用的任何方法,但是该视图具有演示者可以订阅的事件。
  • 演示者知道其观点。我更喜欢通过在具体演示者上注入构造函数来完成此任务。
  • 该视图不知道哪个演示者在控制它。永远不会提供任何演示者。

对于您的问题,上面的代码看起来有些简化:

interface IConfigurationView
{
    event EventHandler SelectConfigurationFile;

    void SetConfigurationFile(string fullPath);
    void Show();
}

class ConfigurationView : IConfigurationView
{
    Form form;
    Button selectConfigurationFileButton;
    Label fullPathLabel;

    public event EventHandler SelectConfigurationFile;

    public ConfigurationView()
    {
        // UI initialization.

        this.selectConfigurationFileButton.Click += delegate
        {
            var Handler = this.SelectConfigurationFile;

            if (Handler != null)
            {
                Handler(this, EventArgs.Empty);
            }
        };
    }

    public void SetConfigurationFile(string fullPath)
    {
        this.fullPathLabel.Text = fullPath;
    }

    public void Show()
    {
        this.form.ShowDialog();        
    }
}

interface IConfigurationPresenter
{
    void ShowView();
}

class ConfigurationPresenter : IConfigurationPresenter
{
    Configuration configuration = new Configuration();
    IConfigurationView view;

    public ConfigurationPresenter(IConfigurationView view)
    {
        this.view = view;            
        this.view.SelectConfigurationFile += delegate
        {
            // The ISelectFilePresenter and ISelectFileView behaviors
            // are implicit here, but in a WinForms case, a call to
            // OpenFileDialog wouldn't be too far fetched...
            var selectFilePresenter = Gimme.The<ISelectFilePresenter>();
            selectFilePresenter.ShowView();
            this.configuration.FullPath = selectFilePresenter.FullPath;
            this.view.SetConfigurationFile(this.configuration.FullPath);
        };
    }

    public void ShowView()
    {
        this.view.SetConfigurationFile(this.configuration.FullPath);
        this.view.Show();
    }
}

除了上述内容外,我通常还有一个基本IView界面,可以在其中存储Show()和所有所有者视图或我的视图通常受益的视图标题。

对您的问题:

1. 加载Winform时,必须获取树形视图。我是否正确认为该视图因此应调用诸如presenter.gettree()之类的方法,而该方法又将委托给模型,该模型将获取树视图的数据,对其进行创建和配置,然后将其返回给演示者,该演示者又将转到视图,然后将其简单地分配给一个面板?

我会打电话IConfigurationView.SetTreeData(...)IConfigurationPresenter.ShowView(),调用前右IConfigurationView.Show()

2. 对于Winform上的任何数据控件,这都一样吗,因为我也有一个datagridview?

是的,我会呼吁IConfigurationView.SetTableData(...)。由视图来格式化提供给它的数据。演示者只是遵循需要表格数据的视图协定。

3. 我的应用程序,具有多个具有相同装配的模型类。它还支持插件体系结构,其中包含需要在启动时加载的插件。视图是否会简单地调用presenter方法,而该方法又会调用加载插件并在视图中显示信息的方法?哪一层将控制插件引用。视图将保留对它们或演示者的引用吗?

如果插件与视图相关,则视图应该知道它们,但演示者不知道。如果它们全部与数据和模型有关,那么视图应该与它们无关。

4. 我是否正确认为视图应该处理有关表示的每件事,从树视图节点的颜色到数据网格的大小等?

是。可以认为它是演示者提供描述数据的XML和获取数据并将视图应用于CSS样式表的视图。具体来说,演示者可以调用IRoadMapView.SetRoadCondition(RoadCondition.Slippery),然后视图将道路渲染为红色。

那点击节点的数据呢?

5. 如果单击树节点时,是否应该将特定的节点传递给演示者,然后由该演示者确定需要的数据,然后向模型询问该数据,然后再将其展示给视图?

如果可能的话,我会一枪传递视图中呈现树的所有必要数据。但是,如果某些数据太大而无法从头开始传递,或者它本质上是动态的,并且需要模型中的“最新快照”(通过演示者),那么我会event LoadNodeDetailsEventHandler LoadNodeDetails在视图界面中添加类似内容,以便演示者可以订阅它,LoadNodeDetailsEventArgs.Node从模型中获取节点的详细信息(可能通过某种形式的ID),以便在事件处理程序委托返回时,视图可以更新其显示的节点详细信息。请注意,如果获取数据的速度可能太慢而无法获得良好的用户体验,则可能需要使用异步模式。


3
我认为您不必必须将视图和演示者分离。我通常使模型和演示者脱钩,使演示者侦听模型事件并采取相应的行动(更新视图)。在视图中具有演示者可以简化视图和演示者之间的通信。
kasperhj 2011年

11
@lejon:您说在视图有一个演示者可以简化视图和演示者之间的交流,但是我强烈不同意。我的观点是:当视图了解演示者时,则对于每个视图事件,视图都必须确定哪种presenter方法是合适的调用方法。这就是“两点复杂性”,因为视图实际上并不知道哪个视图事件对应于哪个presenter方法。合同没有具体说明。
Johann Gerell 2011年

5
@lejon:另一方面,如果视图仅公开实际事件,则演示者本身(谁知道在发生视图事件时要执行的操作)只是订阅它以执行正确的操作。那只是“ 1点复杂度”,在我的书中是“ 2点复杂度”的两倍。一般而言,更少的耦合意味着在整个项目运行期间的维护成本更低。
Johann Gerell 2011年

9
我也倾向于使用此链接中介绍的封装演示者lostechies.com/derekgreer/2008/11/23/…,其中视图是演示者的唯一持有人。
ak3nat0n 2012年

3
@ ak3nat0n:关于您提供的链接中介绍的MVP的三种样式,我相信Johann的回答可能与第三种样式一致,即观察演示者样式
DavidRR

11

演示者(包含视图中的所有逻辑)应响应被单击的按钮,如@JochemKempe 所说。实际上,按钮单击事件处理程序会调用presenter.OpenFile()。演示者然后能够确定应该做什么。

如果它决定用户必须选择一个文件,它将(通过视图界面)回调到视图中,并让包含所有UI技术的视图显示OpenFileDialog。这是非常重要的区别,因为不应允许演示者执行与使用中的UI技术相关的操作。

然后,选定的文件将被返回给演示者,演示者将继续其逻辑。这可能涉及任何模型或服务应处理的文件。

使用MVP模式的主要原因是,imo是将UI技术与视图逻辑分开。因此,演示者协调所有逻辑,同时视图将其与UI逻辑分开。这使演示者可以完全进行单元测试,具有很好的副作用。

更新:由于演示者是在一个特定视图中找到的逻辑的体现,因此视图与演示者的关系是IMO一对一的关系。并且出于所有实际目的,一个视图实例(例如一个窗体)与一个演示者实例进行交互,而一个演示者实例仅与一个视图实例进行交互。

就是说,在我使用WinForms实现MVP的过程中,演示者始终通过表示视图UI功能的接口与视图进行交互。对哪种视图实现此接口没有限制,因此不同的“小部件”可以实现相同的视图接口并重用presenter类。


谢谢。因此,在presenter.OpenFile()方法中,它应该没有显示openfiledialog的代码吗?相反,它应该回到视图中以显示该窗口?
达伦·扬

4
是的,我绝不会让演示者直接打开对话框,因为那样会破坏您的测试。要么将其卸载到视图中,要么像我在某些情况下所做的那样,将一个单独的“ FileOpenService”类用于处理实际的对话框交互。这样,您可以在测试期间伪造文件打开服务。将此类代码放在单独的服务中可能会给您带来很好的可重用性:)
Peter Lillevold 2011年

2

演示者应在请求端执行操作,按照您的建议显示openfiledialog窗口。由于不需要模型提供任何数据,因此演示者可以并且应该处理请求。

假设您需要数据来在模型中创建一些实体。您可以将流槽传递到访问层,在访问层中有一种方法可以从流中创建实体,但是我建议您在演示者中处理文件的解析,并在模型中为每个实体使用构造函数或Create方法。


1
感谢您的回复。另外,您将只有一个演示者来查看视图吗?那个演示者要么处理请求,要么如果需要数据,那么它将委托给根据特定请求起作用的任意数量的模型类?那是正确的方法吗?再次感谢。
达伦·扬

3
一个视图有一个演示者,但是一个演示者可以有多个视图。
JochemKempe 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.