MVP 被动视图 - 将视图数据和模型数据分开

发布于 2024-10-06 18:27:46 字数 2402 浏览 2 评论 0原文

我已经使用被动视图模式实现了 MVP 三元组 - 即视图仅包含简单的 getter 和 setter。但是,我在分离视图数据和模型数据时遇到问题。特别是在处理视图状态的更改时。

三元组用于使用户能够从列表中选择部件。零件列表由模型提供,每个零件都由唯一的 id 唯一标识。

假设这些部件如下所示:

class Part
{
    int ID; // this code uniquely identifies the part within the model
    String partCode;
    String description;
    double voltage;
}

视图向用户显示列表并允许他们选择部件

该列表显示在 DataGridView 中,并且通过单击 dataGridView 中的行来选择部件。

ID 不会向用户显示,电压也不会显示,因此模型会创建一个仅包含零件代码和描述的数据表。此 DataTable 由演示者分配给视图上的一个属性,该属性映射到 DataGridView 的 DataSource 属性。

class Presenter
{
    IView _view;
    IModel _model;

    //...///

    _view.Data = _model.GetFilteredData();
}

class Model
{
    public DataTable GetFilteredData()
    {
        // create a DataTable with the partCode and Description columns only
        // return DataTable
    } 
}

class View  //winform
{
      public DataTable Data
      {
          set 
          {
              this.dataGridView.Source = value;
          }
      }
}

到目前为止,一切都很好。 View 在 DataGridView 中显示过滤后的数据。

我遇到的问题是返回用户选择的部分。

视图不知道唯一 ID,因为它不会显示,并且无法保证其他信息是唯一的 - 因此不可能唯一标识所选部件。

本质上,我正在尝试将视图数据(所选行)转换为模型数据(所选部分),而无需一个组件使用其他数据。

到目前为止我有以下解决方案:

1)视图传递一个包含ID的DataTable,然后过滤显示,以便不向用户显示。然后返回所选行的 ID 就很简单了。这里的问题是我现在用未经测试的逻辑(显示的过滤)污染了视图。

2) 视图返回行索引,模型将该索引与原始数据中的行进行匹配。这意味着确保视图中的顺序永远不会改变,这虽然可能,但限制了视图显示(和操作)数据的方式。这也会用视图数据(行索引)污染模型。

    public int RowIndexSelected { get; private set; }

    //...//

    private void gridParts_CellEnter(object sender, DataGridViewCellEventArgs e)
    {
        if (SelectedPartChangedEvent != null)
        {
            RowIndexSelected = e.RowIndex;

            SelectedPartChangedEvent();            
        }
    }

3) (2) 的变体。创建一个适配器对象以位于演示者和视图之间。将行到 ID 转换代码从模型移动到适配器。然后演示者处理 dataGridAdapters 部分更改事件。

    public PartSelectDataGridAdapter(IPartSelectView view, PartCollection data)
    {
        _view = view;
        _data = data;

        _view.SelectedPanelChangedEvent += HandleSelectedPartChanged;
    }

    void HandleSelectedPartChanged()
    {
        int id = _data[_view.RowIndexSelected].ID;

        if (SelectedPartChanged != null)
        {
            SelectedPartChanged(id);
        }
    }

目前我正在学习 3,因为它是可测试的,将逻辑置于视图之外,将视图数据置于模型和演示者之外。

您将如何解决这个问题 - 有更好的解决方案吗?

I have implemented an MVP triad using the passive view pattern - i.e. the view contains only simple getters and setters. However I am having trouble seperating the view data and model data. In particular when handling a change in the view state.

The triad is used to enable the user to select a part from a list. The list of parts is supplied by the model with each part uniquely identified by a unique id.

Lets say the parts look like this:

class Part
{
    int ID; // this code uniquely identifies the part within the model
    String partCode;
    String description;
    double voltage;
}

The view displays the list to the user and allows them to select a part

The list is displayed in a DataGridView and a part is selected by clicking on a row in the dataGridView.

The ID is not to be displayed to the user and neither is the voltage, therefore the model creates a DataTable that contains just the partCode and description. This DataTable is assigned by the presenter to a property on the view that maps to the DataSource property of the DataGridView.

class Presenter
{
    IView _view;
    IModel _model;

    //...///

    _view.Data = _model.GetFilteredData();
}

class Model
{
    public DataTable GetFilteredData()
    {
        // create a DataTable with the partCode and Description columns only
        // return DataTable
    } 
}

class View  //winform
{
      public DataTable Data
      {
          set 
          {
              this.dataGridView.Source = value;
          }
      }
}

So far so good. The View dislays the filtered data in the DataGridView.

The problem I have is returning the part selected by the user.

The view has no knowledge of the unique ID since it is not displayed and the other information cannot be guaranteed to be unique - therfore it is not possible to uniquely identify the part selected.

Essentially i'm trying to convert view data (the selected row) to model data (the selected part) without one component using the others data.

So far I have the following solutions:

1) The view is passed a DataTable that contains the ID and then filters the display so that it is not displayed to the user. It is then trivial to return an ID for the selected row. The problem here is that i have now polluted the view with logic that is untested (the filtering of the display).

2) The view returns the row index and the model matches this index to a row in the original data. This would mean ensuring that the order in the view never changes, which while possible, restricts how the view can show (and manipulate) the data. This also pollutes the model with view data (the row index).

    public int RowIndexSelected { get; private set; }

    //...//

    private void gridParts_CellEnter(object sender, DataGridViewCellEventArgs e)
    {
        if (SelectedPartChangedEvent != null)
        {
            RowIndexSelected = e.RowIndex;

            SelectedPartChangedEvent();            
        }
    }

3) A variation on (2). Create an adapter object to sit between the presenter and view. Move the row to ID conversion code from the model to the adapter. The presenter then handles the dataGridAdapters part changed event.

    public PartSelectDataGridAdapter(IPartSelectView view, PartCollection data)
    {
        _view = view;
        _data = data;

        _view.SelectedPanelChangedEvent += HandleSelectedPartChanged;
    }

    void HandleSelectedPartChanged()
    {
        int id = _data[_view.RowIndexSelected].ID;

        if (SelectedPartChanged != null)
        {
            SelectedPartChanged(id);
        }
    }

At present im learning towards 3 since it is testable, keeps logic out of the view and view data out of the model and presenter.

How would you tackle this - is there a better solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

↘人皮目录ツ 2024-10-13 18:27:46

我之前发布了一个简单的解决方案;这是对问题的更详细答复

您是否有理由不想将 List 传递给视图?

您可以配置网格以隐藏 ID 和电压列。您可以简单地从视图中的绑定源获取选定的对象。演示者可以在视图中查询此选择,或者视图可以在演示者上调用 SelectionChanged(Part selected)

这意味着您不再严格遵循被动视图模式,而是监督控制器,因为现在您的视图了解模型。

如果您不喜欢这样,您可以引入一个视图模型,您已经在 DataTable 中隐式地执行了该操作。 (顺便说一句,这不一定是坏事。)

在您的示例中,模型类了解视图模型,因为您在模型上有生成它们的方法。我建议您反转这种关系:在视图模型上创建依赖于模型对象的方法。
这样,您将保持模型类美观、干净,并且独立于表示层中所需的所有 UI 数据。

当使用视图模型/监督控制器方式时,请考虑放弃 DataTable 概念,转而使用简单的类。

编辑:使视图完全不了解模型的替代方法:

在演示器中构造此类的实例,您知道模型和视图模型:

public class PartViewModel
{
  object PartModel { get; set; }
  string Name { get; set; }
  string Description { get; set; }
}

传递 List; 作为 DataGridView 的数据源。
您可以将选定的 PartViewModel 对象返回给演示者(使用事件或使用方法)。演示者知道它可以将 PartModel 属性强制转换回 Part 实例。正如您所说,视图不需要了解有关模型的任何信息。但您仍然可以在演示器中使用简单的对象标识,避免使用 id 进行“复杂”查找。

使用演示者回调:

interface IPartListPresenter
{
  // other methods
  void SelectedPartChanged(PartViewModel nowSelected);
}

假设partBindingSource是gridview连接到的绑定源,您可以像这样处理partBindingSource的CurrentChanged事件:

private void partBindingSource_CurrentChanged(object sender, EventArgs e)
{
  _presenter.SelectedPartChanged(partBindingSource.Current as PartViewModel);
}

在演示者中:

public void SelectedPartChanged(PartViewModel nowSelected)
{
  if(nowSelected == null)
  {
    return;
  }
  part myPart = (Part) nowSelected.Part;
  // dos stuff
}

希望这会有所帮助。

I posted a simple solution earlier; this is a more detailed reply to the question

Is there a reason you don't want to pass a List<Part> to the view?

You could configure the grid to hide the id and voltage column. You can simply get the selected object from the binding source in the view. The presenter could query the view for this selection, or the view can call a SelectionChanged(Part selected) on the presenter.

It would mean you are no longer strictly following the passive-view pattern, but a supervising controller, because now your view knows about the model.

If you don't like this, you can introduce a view model, which you already do implicitly with your DataTable. (This is not necessarily bad, btw.)

In your example, the model classes knows about the view models, because you have methods on the model that generate them. I'd advice you to inverse this relationship: create methods on your view model that depend on your model objects.
This way, you'll keep your model classes nice and clean and independent of all the UI data needed in the presentation layer.

When using the view model/supervising controller way, consider dropping the DataTable concept in favor of simple classes.

EDIT: alternative to make the view completely ignorant of the model:

Construct an instance of this class in the presenter, where you know of the both model and view model:

public class PartViewModel
{
  object PartModel { get; set; }
  string Name { get; set; }
  string Description { get; set; }
}

Pass a List<PartViewModel> as a datasource to the DataGridView.
You can return the selected PartViewModel object to the presenter (either using an event or using a method). The presenter knows it can cast the PartModel property back to a Part instance. The view doesn't need to know anything about the model, as you say you are preferring. But you can still use simple object identity in the presenter, avoiding "complicated" lookup using id's.

With a presenter callback:

interface IPartListPresenter
{
  // other methods
  void SelectedPartChanged(PartViewModel nowSelected);
}

Assuming partBindingSource is the bindingsource the gridview is connected to, you can handle the CurrentChanged event of partBindingSource like this:

private void partBindingSource_CurrentChanged(object sender, EventArgs e)
{
  _presenter.SelectedPartChanged(partBindingSource.Current as PartViewModel);
}

In the presenter:

public void SelectedPartChanged(PartViewModel nowSelected)
{
  if(nowSelected == null)
  {
    return;
  }
  part myPart = (Part) nowSelected.Part;
  // dos stuff
}

Hope this helps.

谈场末日恋爱 2024-10-13 18:27:46

ID 不会显示给
用户也不是电压,
因此该模型创建了一个
数据表仅包含
零件代码和描述。

简单的解决方案:在数据表中创建一个 ID 列,并将其隐藏在数据网格视图中

The ID is not to be displayed to the
user and neither is the voltage,
therefore the model creates a
DataTable that contains just the
partCode and description.

Simple solution: do create an ID column in the datatable and hide it in in the datagrid view.

帅气尐潴 2024-10-13 18:27:46

我认为您在这里有点误解了整个概念!

应该由演示者而不是模型来处理这个问题。模型应该只专注于其唯一的职责,否则,视图和模型就太接近了!

我的建议是在表中保留一个隐藏列,并将所选行的事件传递给演示者,然后让演示者处理工作!

这就是MVP的正确用法。

I think you have misunderstood the whole concept a bit here!

It is the Presenter that should handle this, not the Model. The Model should only concentrate on the its sole responsibility, if not, you keep the View and Model too close!

My suggestion is to keep a hidden column in your table an pass the event of the selected row to your Presenter, and then let the Presenter handle to the work!

This will be the correct usage of MVP.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文