WinForm应用程序的MVVM实现

本文关键字:实现 MVVM 应用程序 WinForm | 更新日期: 2023-09-27 18:23:37

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

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

我已经创建了一个MainFormViewModel类。

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

第2个问题:单击按钮调用MainFormViewModel类上的一个方法。在调用方法之前和调用方法之后,我想在第一个文本框中显示一条消息,类似于"操作a开始/结束"。我通过调用一个Common类来实现这一点,该类有一个Log方法来将消息记录到一个TextBox或一个文件中,或者两者兼而有之。同样,是否可以直接从MainForm执行此操作?我在事件处理程序的开始和结束时调用这个日志记录方法。

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

谢谢。

WinForm应用程序的MVVM实现

您应该只在视图中执行与ViewModel对象的状态有关的操作。例如,当您单击按钮时,您不应该假设视图模型正在计算,但您应该向视图模型添加一个状态,说明它正在做更长的事情,然后在视图中识别该状态。您不应该随意禁用或启用视图中的按钮,但前提是存在要求更改这些按钮的状态。这甚至可以有一个属性来指示当前选择了列表中的哪个项目,因此UI不会调用列表控件的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子句并不是真正的异常处理。我认为您应该非常好地测试您的UI,并且只有在必要时才包括异常处理。这就是为什么要对视图模型进行单元测试,并由用户对视图进行测试。然而,如果你真的坚持原则,避免视图中的逻辑,你就可以用单元测试做很多事情。