WPF MVVM Light – 在工作完成之前显示通知

在我的MVVM Light应用程序中,我想在做一些需要两到三秒的同步工作之前显示通知。 我不希望用户在工作完成时做任何事情,因此不需要异步,任务和IProgress或后台工作者等。

在ViewModel中我有这个代码。 (请注意,这不是位于XAML文件的代码隐藏中,而是位于数据绑定ViewModel中)

void MyCommand(Project project) { NavigationService.AddNotification("Doin' it"); GetTheJobDone(project); ... } 

NavigationService将通知文本添加到客户端窗口顶部的数据绑定ListView。

我的问题是,无论花费多长时间,项目加载后都会显示“Doin’it”。

一个例子可能是我想加载并显示一个项目。 如果加载部分需要几秒钟,则界面会冻结,而不会向用户提供任何信息。

编辑

由于请求,我添加了一些代码。 这段代码工作正常。

显示通知的视图具有此XAML

     

保存通知的ViewModel具有此代码

 public int AddNotification(string message) { ... Notification note = new Notification { Message = message }; Notifications.Add(note); ... } 

您没有收到通知的原因是您在调度程序线程(又名:UI线程)上运行,即应用程序的主线程,也用于重绘屏幕。 因此,当您等待GetTheJobDone(project)方法完成时,将不会进行任何屏幕绘制。 因此,您需要两个线程来完成这项工作,一个用于完成工作,另一个用于执行通知。

例如:

让我们在自己的线程上移动工作,然而,我们需要禁用屏幕(如您的设计)。 这可以通过在窗口上设置IsHitTestVisible来完成(请参阅文章末尾以获得更好的方法)。

 textBox1.Text = "Started"; this.IsHitTestVisible = false; // this being the window try { Task.Factory.StartNew(() => { // handle errors so that IsHitTestVisible will be enabled after error is handled try { this.GetTheJobDone(); Dispatcher.Invoke(() => { // invoke required as we are on a different thead textBox1.Text = "Done"; }); } catch (Exception ex) { Dispatcher.Invoke(() => { this.textBox1.Text = "Error: " + ex.Message; }); } finally { Dispatcher.Invoke(() => { this.IsHitTestVisible = true; }); } }); } catch (Exception ex) { this.textBox1.Text = "Error: " + ex.Message; this.IsHitTestVisible = true; } 

广泛的error handling是必要的,因为我们阻止了与UI的交互 – 这不是一件好事(见下文) – 并且必须确保在发生错误时它再次可用。

使用.NET 4.5时,您可以将代码简化为:

 try { textBox1.Text = "Started"; this.IsHitTestVisible = false; // this being the window await Task.Factory.StartNew(() => { this.GetTheJobDone(); }); textBox1.Text = "Done"; } catch (Exception ex) { this.textBox1.Text = "Error: " + ex.Message; } finally { this.IsHitTestVisible = true; } 

这里不需要Dispatcher.Invoke ,因为等待编组回到调用线程。 此外,error handling变得更加清晰,因为现在常见的error handling涵盖了几个执行路径。

但是,不是仅阻止UI,而是显示忙碌指示符(带有进度指示符的叠加层)可能更好,这样她就知道发生了什么。 在这种情况下,不需要设置IsHitTestVisible

这是基于AxelEckenbergers答案的我自己的解决方案。 正如所建议的那样,我必须让它异步工作。 现在我不锁定GUI,它工作正常。

在我所有ViewModel的基类中,我添加了一个名为AddTask的方法。

 public Task AddTask(Action work, string notification, string workDoneNotification, Action continueWith) { int notificationKey = NavigationService.AddNotification(notification, autoRemove:false); Task task = Task.Factory.StartNew(() => { work.Invoke(); }); task.ContinueWith(t => { NavigationService.RemoveNotification(notificationKey); NavigationService.AddNotification(workDoneNotification); if (continueWith != null) { continueWith.Invoke(t); } }, TaskScheduler.FromCurrentSynchronizationContext()); return task; } 

电话看起来像这样

 AddTask(() => GetTheJobDone(), "Doin' it", "It's done", t => LoadProjects());