MVP Passive View - раздельный просмотр данных и данных модели - PullRequest
3 голосов
/ 08 декабря 2010

Я реализовал триаду MVP, используя шаблон пассивного представления, то есть представление содержит только простые методы получения и установки. Однако у меня возникли проблемы с разделением данных вида и модели. В частности, при обработке изменения состояния просмотра.

Триада используется, чтобы позволить пользователю выбрать деталь из списка. Список деталей предоставляется моделью, причем каждая деталь уникально идентифицируется уникальным идентификатором.

Допустим, детали выглядят так:

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

Представление отображает список для пользователя и позволяет ему выбрать деталь

Список отображается в DataGridView, и часть выбирается щелчком по строке в dataGridView.

Идентификатор не должен отображаться пользователю, равно как и напряжение, поэтому модель создает DataTable, который содержит только partCode и описание. Этот DataTable назначается презентатором свойству в представлении, которое сопоставляется со свойством DataSource 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;
          }
      }
}

Пока все хорошо. Представление отображает отфильтрованные данные в DataGridView.

У меня проблема с возвратом детали, выбранной пользователем.

Представление не знает уникального идентификатора, поскольку оно не отображается, а другая информация не может быть гарантированно уникальной - поэтому невозможно однозначно идентифицировать выбранную деталь.

По сути, я пытаюсь преобразовать данные вида (выбранную строку) в данные модели (выбранную деталь) без одного компонента, используя другие данные.

Пока у меня есть следующие решения:

1) Представлению передается DataTable, которое содержит идентификатор, а затем фильтрует отображение, чтобы оно не отображалось пользователю. Затем тривиально вернуть идентификатор для выбранной строки. Проблема здесь в том, что я теперь загрязнил вид непроверенной логикой (фильтрация дисплея).

2) Представление возвращает индекс строки, и модель сопоставляет этот индекс со строкой в ​​исходных данных. Это будет означать, что порядок в представлении никогда не изменяется, что, хотя и возможно, ограничивает то, как представление может отображать (и манипулировать) данные. Это также загрязняет модель данными представления (индекс строки).

    public int RowIndexSelected { get; private set; }

    //...//

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

            SelectedPartChangedEvent();            
        }
    }

3) Вариация (2). Создайте объект адаптера, который будет находиться между докладчиком и представлением. Переместите строку в код преобразования идентификатора из модели в адаптер. Затем докладчик обрабатывает событие изменения детали 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, так как он тестируемый, защищает логику от представления и данные от модели и презентатора.

Как бы вы занялись этим - есть ли лучшее решение?

Ответы [ 3 ]

2 голосов
/ 08 декабря 2010

Я выложил простое решение ранее; это более подробный ответ на вопрос

Есть ли причина, по которой вы не хотите передавать List<Part> в представление?

Вы можете настроить сетку, чтобы скрыть столбец идентификатора и напряжения. Вы можете просто получить выбранный объект из источника привязки в представлении. Докладчик может запросить представление для этого выбора, или представление может вызвать SelectionChanged(Part selected) на докладчике.

Это означало бы, что вы больше не строго следуете шаблону пассивного просмотра , а контролирующему контроллеру , потому что теперь ваше представление знает о модели.

Если вам не нравится это, вы можете представить модель представления , которую вы уже неявно делаете с вашей DataTable. (Это не обязательно плохо, кстати.)

В вашем примере классы моделей знают о моделях представления, потому что у вас есть методы в модели, которые их генерируют. Я бы посоветовал вам изменить это соотношение: создать методы для вашей модели представления, которые зависят от объектов вашей модели. Таким образом, вы будете сохранять классы моделей красивыми и чистыми и независимыми от всех данных пользовательского интерфейса, необходимых на уровне представления.

При использовании модели представления / контролирующего контроллера рассмотрите возможность отказа от концепции DataTable в пользу простых классов.

РЕДАКТИРОВАТЬ: альтернатива, чтобы сделать представление совершенно неосведомленным о модели:

Создайте экземпляр этого класса в презентаторе, где вы знаете модель и модель представления:

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

Передайте List<PartViewModel> в качестве источника данных в DataGridView. Вы можете вернуть выбранный объект PartViewModel докладчику (используя событие или метод). Презентатор знает, что он может привести свойство PartModel обратно к экземпляру Part. Представлению не нужно ничего знать о модели, так как вы говорите, что предпочитаете. Но вы все равно можете использовать простой идентификатор объекта в презентере, избегая «сложного» поиска с использованием идентификаторов.

С обратным вызовом ведущего:

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

Предполагая, что partBindingSource является источником привязки, к которому подключено сеточное представление, вы можете обработать событие CurrentChanged для partBindingSource следующим образом:

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 голос
/ 08 декабря 2010

Идентификатор не должен отображаться пользователю, равно как и напряжение, поэтому модель создает таблицу данных, которая содержит только partCode и описание.

Простое решение: do создайте столбец ID в таблице данных и скройте его в представлении таблицы данных .

0 голосов
/ 08 декабря 2010

Я думаю, что вы немного неправильно поняли всю концепцию!

Это должен делать Ведущий, а не Модель. Модель должна сосредоточиться только на своей исключительной ответственности, в противном случае вы держите View и Model слишком близко!

Я предлагаю сохранить скрытый столбец в вашей таблице и передать событие выбранной строки вашему докладчику, а затем позволить докладчику справиться с работой!

Это будет правильное использование MVP.

...