Вы должны выполнять операции в представлении только в отношении состояния объекта 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 на самом деле не являются обработкой исключений. Я думаю, что вы должны очень хорошо протестировать свой интерфейс и включать обработку исключений только при необходимости. Вот почему вы тестируете свою модель представления, а пользователь проверяет представление. Однако, если вы действительно придерживаетесь принципа и избегаете логики в представлении, вы можете многое сделать с помощью модульных тестов.