从WPF中的ViewModel类(MVVM模式)更新UI

时间:2009-11-18 09:49:36

标签: wpf mvvm

我在我的第一个WPF应用程序中使用MVVM模式,并且遇到了我认为非常基本的问题。

当用户点击我视图上的“保存”按钮时,会执行一个命令,该命令会在我的ViewModel中调用私有void Save()。

问题是“Save()”中的代码需要一些时间来执行,所以我想在执行大块代码之前隐藏UI视图中的“Save”按钮。

问题是视图不会更新,直到所有代码都在viewmodel中执行。 在执行Save()代码之前,如何强制视图重绘并处理PropertyChanged事件?

此外,我想要一种可重复使用的方式,这样我就可以轻松地在其他页面中做同样的事情。其他人已经做过这样的事情了? “正在载入...”消息?

6 个答案:

答案 0 :(得分:11)

如果需要很长时间,请考虑使用单独的线程,例如使用BackgroundWorker,以便UI线程可以在执行操作时保持响应(即更新UI)。

Save方法中,您会

  • 更改UI(即修改绑定到UI的一些INotifyPropertyChanged或DependencyProperty IsBusySaving布尔值,隐藏“保存”按钮,并显示一些带有IsIndeterminate = True的进度条和
  • 开始BackgroundWorker

在BackgroundWorker的DoWork事件处理程序中,执行冗长的保存操作。

在UI线程中执行的RunWorkerCompleted事件处理程序中,您将IsBusySaving设置为false,并可能更改UI中的其他内容以显示您已完成。

代码示例(未经测试):

BackgroundWorker bwSave;
DependencyProperty IsBusySavingProperty = ...;

private MyViewModel() {
    bwSave = new BackgroundWorker();

    bwSave.DoWork += (sender, args) => {
        // do your lengthy save stuff here -- this happens in a separate thread
    }

    bwSave.RunWorkerCompleted += (sender, args) => {
        IsBusySaving = false;
        if (args.Error != null)  // if an exception occurred during DoWork,
            MessageBox.Show(args.Error.ToString());  // do your error handling here
    }
}

private void Save() {
    if (IsBusySaving) {
        throw new Exception("Save in progress -- this should be prevented by the UI");
    }
    IsBusySaving = true;
    bwSave.RunWorkerAsync();
}

答案 1 :(得分:3)

您正在使用MVVM模式,因此您的保存按钮的命令被设置为RoutedCommand对象的实例,该对象以声明方式或命令方式添加到Window的CommandBindings集合中。

假设您以声明方式执行此操作。像

这样的东西
<Window.CommandBindings>
    <CommandBinding
        Command="{x:Static namespace:ClassName.StaticRoutedCommandObj}"
        CanExecute="Save_CanExecute"
        Executed="Save"
    />
</Window.CommandBindings>

对于Executed routed事件的处理程序,您的Save()方法在输入时将变量设置为false,返回时将其设置为true。像。的东西。

void Save(object sender, ExecutedRoutedEventArgs e)
{
    _canExecute = false;
    // do work
    _canExecute = true; 
}

对于CanExecute路由事件的处理程序,Save_CanExecute()方法,您将该变量用作其中一个条件。

void ShowSelectedXray_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = _canExecute && _others;
}

我希望我很清楚。 :)

答案 2 :(得分:0)

您可以随时执行以下操作:

public class SaveDemo : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
  private bool _canSave;

  public bool CanSave
  {
    get { return _canSave; }
    set
    {
      if (_canSave != value)
      {
        _canSave = value;
        OnChange("CanSave");
      }
    }
  }

  public void Save()
  {
    _canSave = false;

    // Do the lengthy operation
    _canSave = true;
  }

  private void OnChange(string p)
  {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
      handler(this, new PropertyChangedEventArgs(p));
    }
  }
}

然后,您可以将按钮的IsEnabled属性绑定到CanSave属性,它将自动启用/禁用。另一种方法,也就是我要使用的方法是使用命令CanExecute对此进行排序,但这个想法足够类似于你可以使用。

答案 3 :(得分:0)

答案 4 :(得分:0)

您可以通过以下代码完成此操作..

Thread workerThread = null;
void Save(object sender, ExecutedRoutedEventArgs e)
{
workerThread = new Thread(new ThreadStart(doWork));
SaveButton.isEnable = false;
workerThread.start();
}

dowork()方法

中完成所有漫长的过程

用其他方法...

workerThread.join();
SaveButtton.isEnable = true;

这将导致在另一个线程中运行保存冗长的进程并且不会阻止您的UI,如果您想在用户单击保存按钮时显示动画,然后显示一些进度条,如iPhone等...给我反馈我'我会尽力帮助你。

答案 5 :(得分:0)

迟到的答案,但我认为输入一点也不错。

不是创建自己的新线程,最好将它留给线程池来运行保存。它不会强制它像创建自己的线程一样立即运行,但它确实允许您保存线程资源。

这样做的方法是:

ThreadPool.QueueUserWorkItem(Save);

使用这种方法的问题在于,您需要让“Save()”方法接受将充当状态的对象。我遇到了类似的问题并决定走这条路,因为我工作的地方非常缺乏资源。