Search code examples
c#winformsdesign-patternsmvppassive-view

MVP Passive View - keeping view data and model data separate


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?


Solution

  • 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.