WinForm应用程序的MVVM实现

All*_*ons 7 c# design-patterns

我正在尝试为我的WinForms应用程序实现MVVM(模型视图ViewModel)模式.我正在使用C#2005.

我的应用程序有一个MainForm(View),带有2个多行文本框和3个按钮.第一个文本框的目的是在单击按钮时显示应用程序正在执行的操作的运行注释.我继续向TextBox附加行以更新用户正在发生的事情.第二个文本框的目的是更新用户有关任何错误条件,冲突,重复值; 简而言之,用户需要审查的任何内容.它将每条消息分类为INFO或WARNING或ERROR.3个按钮中的每一个都执行一个动作,并不断更新2个文本框.

我创建了一个MainFormViewModel类.

第一个问题:当用户点击MainForm中的按钮时,我必须清除2个文本框的内容,并禁用该按钮,以便在第一次操作完成之前不能再次单击该按钮.我应该直接在MainForm中执行此文本框和按钮更新,还是应该以某种方式使用MainFormViewModel?

第二个问题:按钮单击调用MainFormViewModel类上的方法.在调用方法之前和调用方法之后,我想在第一个文本框中显示类似"操作A已启动/已结束"的消息.我这样做是通过调用一个Common类来实现的,该类具有一个Log方法来将消息记录到TextBox或文件或两者.再次,是否可以直接从MainForm执行此操作?我在事件处理程序的开头和结尾调用此日志记录方法.

第3个问题:如何将ViewModel中的错误消息传播回View?我创建了一个自定义的Exception类"TbtException".那么我是否必须在每个按钮中写入2个catch块,一个用于TbtException,另一个用于遗传Exception类?

谢谢.

And*_*eas 4

您应该仅在视图中执行与 ViewModel 对象的状态相关的操作。例如,当您单击按钮时,您不应该假设视图模型正在计算,但您应该向视图模型添加一个状态,表明它正在执行更长时间的操作,然后在视图中识别该状态。您不应随意禁用或启用视图中的按钮,但前提是存在需要更改这些按钮的状态。这甚至可以有一个属性来指示当前选择列表中的哪个项目,因此 UI 不会调用列表控件的 SelectedItem 成员,而是调用视图模型的 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));
  }
}
Run Code Online (Sandbox Code Playgroud)

编辑:关于异常处理

我宁愿在 ViewModel 中捕获异常,而不是在视图中捕获异常。视图模型更适合准备它们以进行显示。我不知道这在 WPF 中是如何工作的。我还没有在 WPF 中编写应用程序,我们仍在编写大量 WinForm。

意见可能有所不同,但我认为通用的 try/catch 子句并不是真正的异常处理。我认为你应该很好地测试你的 UI,并仅在必要时包含异常处理。这就是为什么您要对视图模型进行单元测试并进行用户测试视图。但是,如果您确实坚持原则并避免视图中的逻辑,则可以通过单元测试做很多事情。