我使用被动视图模式实现了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不会显示给用户,也不会显示电压,因此模型会创建一个仅包含partCode和description的DataTable。此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;
}
}
}
到目前为止一切顺利。视图在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学习,因为它是可测试的,将逻辑排除在视图之外,并从模型和演示者中查看数据。
你会如何解决这个问题?有更好的解决方案吗?
答案 0 :(得分:2)
我之前发布了一个简单的解决方案;这是对问题的更详细的回复
您是否有理由不想将List<Part>
传递给视图?
您可以配置网格以隐藏ID和电压列。您只需从视图中的绑定源获取所选对象即可。演示者可以查询该选择的视图,或者视图可以在演示者上调用SelectionChanged(Part selected)
。
这意味着您不再严格遵循passive-view模式,而是supervising controller,因为现在您的视图了解模型。
如果您不喜欢这样,可以引入视图模型,您已经使用DataTable隐式执行了该操作。 (这不一定是坏事,顺便说一句。)
在您的示例中,模型类知道视图模型,因为您在模型上有生成它们的方法。我建议您反转这种关系:在视图模型上创建依赖于模型对象的方法。 这样,您将保持模型类的美观和干净,并且独立于表示层中所需的所有UI数据。
使用视图模型/监督控制器方式时,请考虑删除DataTable概念以支持简单类。
编辑:使视图完全不知道模型的替代方法:
在演示者中构造此类的实例,您可以在其中了解模型和视图模型:
public class PartViewModel
{
object PartModel { get; set; }
string Name { get; set; }
string Description { get; set; }
}
将List<PartViewModel>
作为数据源传递给DataGridView。
您可以将选定的PartViewModel对象返回给演示者(使用事件或使用方法)。演示者知道它可以将PartModel属性强制转换回Part实例。视图不需要知道关于模型的任何信息,正如您所说的那样。但您仍然可以在演示者中使用简单的对象标识,避免使用id进行“复杂”查找。
使用演示者回调:
interface IPartListPresenter
{
// other methods
void SelectedPartChanged(PartViewModel nowSelected);
}
假设partBindingSource是gridview连接的bindingsource,你可以像这样处理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
}
希望这有帮助。
答案 1 :(得分:1)
不会向ID显示ID 用户,也不是电压, 因此模型创建了一个 DataTable只包含 partCode和description。
简单解决方案:执行在数据表和hide it in in the datagrid view中创建ID列。
答案 2 :(得分:0)
我认为你在这里误解了整个概念!
Presenter应该处理这个,而不是模型。模型应该只专注于自己的责任,如果没有,你保持视图和模型太近了!
我的建议是在表格中保留一个隐藏的列,将所选行的事件传递给Presenter,然后让Presenter处理工作!
这将是MVP的正确用法。