WinForms 中的模型视图演示者

Posted

技术标签:

【中文标题】WinForms 中的模型视图演示者【英文标题】:Model-View-Presenter in WinForms 【发布时间】:2011-06-15 05:07:05 【问题描述】:

我第一次尝试使用 WinForms 实现 MVP 方法。

我正在尝试了解每一层的功能。

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

所以使用MVP,GUI处理按钮点击事件,然后调用presenter.openfile();

在 presenter.openfile() 中,是否应该将该文件的打开委托给模型层,或者由于没有要处理的数据或逻辑,它是否应该简单地处理请求并打开 openfiledialog 窗口?

更新:我决定提供赏金,因为我觉得我需要这方面的进一步帮助,并且最好根据我下面的具体要点进行定制,以便我了解上下文。

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

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

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

    我的应用程序有许多具有相同程序集的模型类。它还支持带有需要在启动时加载的插件的插件架构。视图是否会简单地调用一个演示者方法,该方法又会调用一个加载插件并在视图中显示信息的方法?然后哪个层将控制插件引用。视图是否包含对他们或演示者的引用?

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

我认为它们是我主要关心的问题,如果我了解这些流程应该如何处理,我想我会没事的。

【问题讨论】:

这个链接lostechies.com/derekgreer/2008/11/23/…解释了MVP的一些风格。除了 Johann 的出色回答之外,它可能会有所帮助。 【参考方案1】:

这是我对 MVP 和您的具体问题的谦虚看法。

首先,用户可以与之交互或仅显示的任何内容都是视图。这种视图的规律、行为和特征由界面描述。该界面可以使用 WinForms UI、控制台 UI、Web UI 甚至根本不使用 UI(通常在测试演示者时)来实现——具体实现并不重要,只要它遵守其视图界面的法则.

第二,视图总是由presenter控制。 界面也描述了这种演示者的规律、行为和特征。该接口只要遵守其视图接口的规律,就不会对具体的视图实现感兴趣。

第三,由于演示者控制它的视图,为了最小化依赖关系,让视图完全了解它的演示者并没有任何好处。演示者和视图之间有一个约定的合同,并且由视图界面声明。

第三个的含义是:

演示者没有任何视图可以调用的方法,但视图有演示者可以订阅的事件。 演示者知道它的观点。我更喜欢通过在具体演示者上注入构造函数来实现这一点。 视图不知道是什么演示者在控制它;它永远不会被提供任何演示者。

对于您的问题,上面的代码可能看起来像这样简化:

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加载时,它必须获得一个treeview。我认为视图应该因此调用诸如presenter.gettree()之类的方法是否正确,这又将委托给模型,模型将获取树视图的数据,创建并配置它,将其返回给演示者,然后将传递给视图,然后将其简单地分配给一个面板?

我会在拨打IConfigurationView.Show()之前从IConfigurationPresenter.ShowView()拨打IConfigurationView.SetTreeData(...)

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

是的,我会为此打电话给IConfigurationView.SetTableData(...)。由视图来格式化给它的数据。演示者只是遵守视图的约定,即它需要表格数据。

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

如果插件与视图相关,那么视图应该知道它们,而不是演示者。如果它们都是关于数据和模型的,那么视图不应该与它们有任何关系。

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

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

点击节点的数据呢?

5. 如果当我点击树节点时,我是否应该将特定节点传递给演示者,然后演示者会从中计算出它需要什么数据,然后询问为该数据建立模型,然后再将其呈现回视图?

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

【讨论】:

我认为您不必将视图和演示者分离。我通常将模型和演示者解耦,让演示者监听模型事件并采取相应的行动(更新视图)。在视图中有一个演示者可以简化视图和演示者之间的通信。 @lejon:你说在视图中有一个演示者可以简化视图和演示者之间的沟通,但我强烈不同意。我的观点是:当视图知道演示者时,对于每个 视图事件,视图必须决定哪个 演示者方法 是正确的调用方法。这是“2 个复杂点”,因为视图并不真正知道哪个 view 事件 对应于哪个 presenter 方法。合同没有规定。 @lejon:另一方面,如果视图只公开实际事件,那么演示者本身(谁知道当视图事件发生时它想要做什么)只是订阅它来做正确的事情。这只是“1 点复杂度”,在我的书中它是“2 点复杂度”的两倍。一般来说,更少的耦合意味着项目运行期间的维护成本更低。 我也倾向于使用封装的演示者,如此链接lostechies.com/derekgreer/2008/11/23/… 中所述,其中视图是演示者的唯一持有者。 @ak3nat0n:关于您提供的链接中解释的三种 MVP 风格,我相信 Johann 的回答可能与名为 Observing Presenter Style 的第三种风格最接近“Observing Presenter 风格的好处在于,它将 Presenter 的知识与 View 完全分离,从而使 View 不易受到 Presenter 内部变化的影响。”【参考方案2】:

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

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

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

使用 MVP 模式的主要原因,imo 是将 UI 技术与视图逻辑分开。因此,演示者编排所有逻辑,而视图将其与 UI 逻辑分开。这具有使演示者完全可单元测试的非常好的副作用。

更新:由于演示者是一个特定视图中逻辑的体现,因此视图-演示者关系是 IMO 一对一的关系。出于所有实际目的,一个视图实例(比如一个表单)与一个演示者实例交互,一个演示者实例仅与一个视图实例交互。

也就是说,在我使用 WinForms 实现 MVP 时,演示者总是通过表示视图的 UI 功能的接口与视图交互。什么视图实现这个接口没有限制,因此不同的“小部件”可以实现相同的视图接口并重用演示者类。

【讨论】:

谢谢。那么在presenter.OpenFile()方法中,应该没有显示openfiledialog的代码吧?相反,它应该返回视图以显示该窗口? 对,我永远不会让演示者直接打开对话框,因为这会破坏您的测试。要么将其卸载到视图中,要么就像我在某些场景中所做的那样,让一个单独的“FileOpenService”类处理实际的对话交互。这样您就可以在测试期间伪造文件打开服务。将此类代码放在单独的服务中可能会给您带来很好的可重用性副作用:)【参考方案3】:

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

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

【讨论】:

感谢您的回复。另外,您会为视图指定一个演示者吗?并且该演示者要么处理请求,要么如果需要数据,那么它会委托给任意数量的模型类来处理特定请求?那是正确的方法吗?再次感谢。 一个视图有一个演示者,但一个演示者可以有多个视图。

以上是关于WinForms 中的模型视图演示者的主要内容,如果未能解决你的问题,请参考以下文章

如何将用户控件(视图)中的逻辑剥离到演示者

android模型视图演示者/控制器示例[关闭]

在 WinForms 中对一组单选按钮进行数据绑定的最佳方法

重命名列表视图中的项目c#WinForms

WinForms 中的 C# 虚拟列表视图

c#Winforms中的分层树视图,带有大陆,国家,城市