Реализация MVVM для приложения WinForm - PullRequest
7 голосов
/ 06 февраля 2012

Я пытаюсь реализовать шаблон MVVM (Model View ViewModel) для моего приложения WinForms. Я использую C # 2005.

Мое приложение имеет MainForm (View) с 2 многострочными текстовыми полями и 3 кнопками. Цель 1-го текстового поля - показать текущий комментарий о том, что делает приложение, когда кнопка нажата. Я продолжаю добавлять строки в TextBox, чтобы обновить пользователя, что происходит. Целью 2-го текстового поля является информирование пользователя о любых ошибках, конфликтах, повторяющихся значениях; короче говоря, все, что требуется пользователю для просмотра. Каждое сообщение классифицируется как INFO, WARNING или ERROR. Каждая из 3 кнопок выполняет действие и обновляет 2 текстовых поля.

Я создал класс MainFormViewModel.

1-й вопрос: Когда пользователь нажимает кнопку в MainForm, я должен очистить содержимое 2 текстовых полей и отключить кнопку, чтобы ее нельзя было нажимать снова до завершения 1-й операции. Должен ли я выполнить это обновление текстового поля и кнопки непосредственно в MainForm или мне следует каким-то образом использовать MainFormViewModel?

2-й вопрос: Нажатие кнопки вызывает метод класса MainFormViewModel. Перед вызовом метода и после вызова метода я хочу показать сообщение в 1-м текстовом поле, что-то вроде «Операция A началась / закончилась». Я делаю это, вызывая класс Common, у которого есть метод Log для записи сообщений в TextBox или в файл или в оба. Опять же, нормально ли это делать прямо из MainForm? Я вызываю этот метод ведения журнала в начале и в конце обработчика событий.

3-й вопрос: Как распространять сообщения об ошибках из ViewModel обратно в View? Я создал пользовательский класс исключений "TbtException". Так что мне нужно написать 2 блока catch в каждой кнопке, один для TbtException и другой для генетического класса Exception?

Спасибо.

1 Ответ

4 голосов
/ 06 февраля 2012

Вы должны выполнять операции в представлении только в отношении состояния объекта ViewModel. Например. вы не должны предполагать, что модель представления вычисляется при нажатии кнопки, но вы должны добавить состояние к модели представления, которое говорит, что она выполняет что-то более длинное, а затем распознать это состояние в представлении. Не следует отключать или включать кнопки в представлении по своему усмотрению, но только в том случае, если существует состояние, требующее изменения этих кнопок. Это может зайти так далеко, чтобы иметь свойство, которое указывает, какой элемент в списке выбран в данный момент, поэтому пользовательский интерфейс вызывает не элемент SelectedItem элемента управления списком, а элемент представления. А когда пользователь нажимает кнопку «Удалить», модель представления удаляет выбранного члена из своего списка, и представление автоматически обновляется через изменения состояния в форме событий.

Вот то, что я бы назвал моделью для вашего вида. Он предоставляет сообщения через наблюдаемую коллекцию, к которой может привязываться представление (т. Е. Регистрировать обработчики событий, поскольку привязка плохо поддерживается в WinForms). Текстовое поле в любое время отображает только содержимое коллекции. У него есть действия для очистки тех коллекций, которые может вызвать ваш взгляд. Представление также может вызывать действия базовой модели, но оно должно обновляться только через модель представления! Представление никогда не должно регистрировать какие-либо обработчики событий для событий, предоставляемых базовой моделью. Если вы когда-нибудь захотите это сделать, вам нужно подключить это событие в модели представления и показать его там. Иногда это может показаться «просто еще одним уровнем косвенности», поэтому это может быть излишним для очень простых приложений, таких как ваше.

public class MainFormViewModel : INotifyPropertyChanged {
  private object syncObject = new object();

  private MainFormModel model;
  public virtual MainFormModel Model {
    get { return model; }
    set {
      bool changed = (model != value);
      if (changed && model != null) DeregisterModelEvents();
      model = value;
      if (changed) {
        OnPropertyChanged("Model");
        if (model != null) RegisterModelEvents();
      }
    }
  }

  private bool isCalculating;
  public bool IsCalculating {
    get { return isCalculating; }
    protected set {
      bool changed = (isCalculating != value);
      isCalculating = value;
      if (changed) OnPropertyChanged("IsCalculating");
    }
  }

  public ObservableCollection<string> Messages { get; private set; }
  public ObservableCollection<Exception> Exceptions { get; private set; }

  protected MainFormViewModel() {
    this.Messages = new ObservableCollection<string>();
    this.Exceptions = new ObservableCollection<string>();
  }

  public MainFormViewModel(MainFormModel model)
    : this() {
    Model = model;
  }

  protected virtual void RegisterModelEvents() {
    Model.NewMessage += new EventHandler<SomeEventArg>(Model_NewMessage);
    Model.ExceptionThrown += new EventHandler<OtherEventArg>(Model_ExceptionThrown);
  }

  protected virtual void DeregisterModelEvents() {
    Model.NewMessage -= new EventHandler<SomeEventArg>(Model_NewMessage);
    Model.ExceptionThrown -= new EventHandler<OtherEventArg>(Model_ExceptionThrown);
  }

  protected virtual void Model_NewMessage(object sender, SomeEventArg e) {
    Messages.Add(e.Message);
  }

  protected virtual void Model_ExceptionThrown(object sender, OtherEventArg e) {
    Exceptions.Add(e.Exception);
  }

  public virtual void ClearMessages() {
    lock (syncObject) {
      IsCalculating = true;
      try {
        Messages.Clear();
      } finally { IsCalculating = false; }
    }
  }

  public virtual void ClearExceptions() {
    lock (syncObject) {
      IsCalculating = true;
      try {
        Exceptions.Clear();
      } finally { IsCalculating = false; }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropetyChanged(string property) {
    var handler = PropertyChanged;
    if (handler != null) handler(this, new PropertyChangedEventArgs(property));
  }
}

РЕДАКТИРОВАТЬ: Относительно обработки исключений

Я бы предпочел ловить исключения во ViewModel, чем в представлении. Модель представления лучше подходит для подготовки их к показу. Я не знаю, как это работает в WPF. Я еще не запрограммировал приложение в WPF, мы все еще делаем много WinForms.

Мнения могут различаться, но я думаю, что общие предложения try / catch на самом деле не являются обработкой исключений. Я думаю, что вы должны очень хорошо протестировать свой интерфейс и включать обработку исключений только при необходимости. Вот почему вы тестируете свою модель представления, а пользователь проверяет представление. Однако, если вы действительно придерживаетесь принципа и избегаете логики в представлении, вы можете многое сделать с помощью модульных тестов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...