我正在尝试为我的WinForms应用程序实现MVVM(模型视图ViewModel)模式。我正在使用C#2005。
我的应用程序有一个MainForm(View),带有2个多行文本框和3个按钮。第一个文本框的目的是在单击按钮时显示应用程序正在执行的操作的运行注释。我继续向TextBox附加行以更新用户正在发生的事情。第二个文本框的目的是更新用户有关任何错误条件,冲突,重复值;简而言之,用户需要审查的任何内容。它将每条消息分类为INFO或WARNING或ERROR。 3个按钮中的每一个都执行一个动作,并不断更新2个文本框。
我创建了一个MainFormViewModel类。
第一个问题: 当用户点击MainForm中的按钮时,我必须清除2个文本框的内容,并禁用该按钮,以便在第1次操作完成之前不能再次单击该按钮。我应该直接在MainForm中执行此文本框和按钮更新,还是应该以某种方式使用MainFormViewModel?
第二个问题: 单击按钮会调用MainFormViewModel类上的方法。在调用方法之前和调用方法之后,我想在第一个文本框中显示类似“操作A已启动/已结束”的消息。我这样做是通过调用一个Common类来实现的,该类具有一个Log方法来将消息记录到TextBox或文件或两者。再次,是否可以直接从MainForm执行此操作?我在事件处理程序的开头和结尾调用此日志记录方法。
第3个问题: 如何将ViewModel中的错误消息传播回View?我创建了一个自定义的Exception类“TbtException”。那么我是否必须在每个按钮中写入2个catch块,一个用于TbtException,另一个用于遗传Exception类?
感谢。
答案 0 :(得分:4)
您应该仅在ViewModel对象的状态方面在视图中执行操作。例如。您不应该假设视图模型在单击按钮时计算,但您应该向视图模型添加一个状态,表明它正在执行更长的操作,然后在视图中识别该状态。您不应该根据需要在视图中禁用或启用按钮,但仅限于需要更改这些按钮的状态。这可以使得具有指示当前选择列表中的哪个项的属性,因此UI不调用列表控件的SelectedItem成员,而是调用viewmodel。当用户单击“删除”时,视图模型将从列表中删除所选成员,并通过事件形式的状态更改自动更新视图。
这就是我所说的视图模型。它通过视图可以绑定的可观察集合公开消息(即注册事件处理程序,因为在WinForms中不能很好地支持绑定)。文本框随时只呈现集合的内容。它具有清除视图可以调用的集合的操作。视图还可以调用底层模型的操作,但只能通过viewmodel进行更新!视图不应该为基础模型公开的事件注册任何事件处理程序。如果您想要这样做,您应该在视图模型中挂接该事件并将其暴露给那里的视图。有时这可能感觉“只是另一个层次的间接”,这就是为什么它对你这样的非常简单的应用程序来说可能有点过分。
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子句并不是真正的异常处理。我认为您应该非常好地测试您的UI,并且仅在必要时包含异常处理。这就是您对视图模型进行单元测试以及用户测试视图的原因。但是,如果您真的坚持原则并避免在视图中使用逻辑,那么您可以使用单元测试做很多事情。