WinForms 中的模型-视图-显示器

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

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

在我的程序中,我有一个 GUI 按钮,点击它就会打开一个 openfile 对话框窗口。

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

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

更新: 我已经决定提供一个奖金,因为我觉得我需要在这方面的进一步帮助,最好是针对我下面的具体观点,以便我有上下文。

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

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

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

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

  4. 我认为视图应该处理关于表示的每一件事情,从树视图节点颜色到数据网格大小等等,这种想法对吗?

我认为他们是我的主要关注点,如果我理解这些流程应该是什么样的,我想我会没事的。

50423 次浏览

The presenter should act on the request end show the openfiledialog window as you suggested. Since no data is required from the model the presenter can, and should, handle the request.

Let's assume you need the data to create some entities in your model. You can either pass the stream trough to the access layer where you have a method to create entities from the stream, but I suggest you handle the parsing of the file in your presenter and use a constructor or Create method per entity in your model.

The presenter, which contains all logic in the view, should respond to the button being clicked as @JochemKempe says. In practical terms, the button click event handler calls presenter.OpenFile(). The presenter is then able to determine what should be done.

If it decides that the user must select a file, it calls back into the view (via a view interface) and let the view, which contains all UI technicalities, display the OpenFileDialog. This is a very important distinction in that the presenter should not be allowed to perform operations tied to the UI technology in use.

The selected file will then be returned to the presenter which continues its logic. This may involve whatever model or service should handle processing the file.

The primary reason for using an MVP pattern, imo is to separate the UI technology from the view logic. Thus the presenter orchestrates all logic while the view keeps it separated from UI logic. This has the very nice side effect of making the presenter fully unit testable.

Update: since the presenter is the embodiment of the logic found in one specific view, the view-presenter relationship is IMO a one-to-one relationship. And for all practical purposes, one view instance (say a Form) interacts with one presenter instance, and one presenter instance interacts with only one view instance.

That said, in my implementation of MVP with WinForms the presenter always interacts with the view through an interface representing the UI abilities of the view. There is no limitation on what view implements this interface, thus different "widgets" may implement the same view interface and reuse the presenter class.

This is my humble take on MVP and your specific issues.

First, anything that a user can interact with, or just be shown, is a view. The laws, behavior and characteristics of such a view is described by an interface. That interface can be implemented using a WinForms UI, a console UI, a web UI or even no UI at all (usually when testing a presenter) - the concrete implementation just doesn't matter as long as it obeys the laws of its view interface.

Second, a view is always controlled by a presenter. The laws, behavior and characteristics of such a presenter is also described by an interface. That interface has no interest in the concrete view implementation as long as it obeys the laws of its view interface.

Third, since a presenter controls its view, to minimize dependencies there's really no gain in having the view knowing anything at all about its presenter. There's an agreed contract between the presenter and the view and that's stated by the view interface.

The implications of Third are:

  • The presenter doesn't have any methods that the view can call, but the view has events that the presenter can subscribe to.
  • The presenter knows its view. I prefer to accomplish this with constructor injection on the concrete presenter.
  • The view has no idea what presenter is controlling it; it'll just never be provided any presenter.

For your issue, the above could look like this in somewhat simplified code:

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();
}
}

In addition to the above, I usually have a base IView interface where I stash the Show() and any owner view or view title that my views usually benefit from.

To your questions:

1. When the winform loads, it has to obtain a treeview. Am I correct in thinking that the view should therefore call a method such as: presenter.gettree(), this in turn will delegate to the model, which will obtain the data for the treeview, create it and configure it, return it to the presenter, which in turn will pass to the view which will then simply assign it to, say, a panel?

I would call IConfigurationView.SetTreeData(...) from IConfigurationPresenter.ShowView(), right before the call to IConfigurationView.Show()

2. Would this be the same for any data control on the Winform, as I also have a datagridview?

Yes, I would call IConfigurationView.SetTableData(...) for that. It's up to the view to format the data given to it. The presenter simply obeys the view's contract that it wants tabular data.

3. My App, has a number of model classes with the same assembly. It also supports a plugin architecture with plugins that need to be loaded at startup. Would the view simply call a presenter method, which in turn would call a method that loads the plugins and display the information in the view? Which tier would then control the plugin references. Would the view hold references to them or the presenter?

If the plugins are view-related, then the views should know about them, but not the presenter. If they are all about data and model, then the view shouldn't have anything to do with them.

4. Am I correct in thinking that the view should handle every single thing about presentation, from treeview node colour, to datagrid size, etc?

Yes. Think about it as the presenter providing XML that describes data and the view that takes the data and applies a CSS stylesheet to it. In concrete terms, the presenter might call IRoadMapView.SetRoadCondition(RoadCondition.Slippery) and the view then renders the road in red color.

What about data for clicked nodes?

5. If when I click on the treenodes, should I pass through the specific node to the presenter and then from that the presenter would work out what data it needs and then asks the model for that data, before presenting it back to the view?

If possible, I would pass all data needed to present the tree in a view in one shot. But if some data is too large to be passed from the beginning or if it's dynamic in its nature and needs the "latest snapshot" from the model (via the presenter), then I would add something like event LoadNodeDetailsEventHandler LoadNodeDetails to the view interface, so that the presenter can subscribe to it, fetch the details of the node in LoadNodeDetailsEventArgs.Node (possibly via its ID of some kind) from the model, so that the view can update its shown node details when the event handler delegate returns. Note that async patterns of this might be needed if fetching the data might be too slow for a good user experience.