C# 工作完成前WPF MVVM灯光显示通知
在我的MVVM Light应用程序中,我想在做一些需要两三秒钟的同步工作之前显示一个通知。我不希望用户在工作完成时做任何事情,因此不需要异步、任务和IProgress或backgroundworkers等 在ViewModel中,我有以下代码。(请注意,它不是位于XAML文件的代码隐藏中,而是位于数据绑定的ViewModel中) NavigationService将通知文本添加到客户端窗口顶部的数据绑定列表视图中 我的问题是,“Doin'it”在加载项目后显示,无论它花费多长时间 一个例子是当我想加载并显示一个项目时。如果加载部分需要几秒钟的时间,界面将冻结,用户不知道发生了什么 编辑 由于请求,我添加了更多代码。这段代码运行良好 显示通知的视图具有以下XAMLC# 工作完成前WPF MVVM灯光显示通知,c#,wpf,mvvm,mvvm-light,C#,Wpf,Mvvm,Mvvm Light,在我的MVVM Light应用程序中,我想在做一些需要两三秒钟的同步工作之前显示一个通知。我不希望用户在工作完成时做任何事情,因此不需要异步、任务和IProgress或backgroundworkers等 在ViewModel中,我有以下代码。(请注意,它不是位于XAML文件的代码隐藏中,而是位于数据绑定的ViewModel中) NavigationService将通知文本添加到客户端窗口顶部的数据绑定列表视图中 我的问题是,“Doin'it”在加载项目后显示,无论它花费多长时间 一个例子是当我
<ListView ItemsSource="{Binding Notifications}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Foreground="LightGray" Content="{Binding Message}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
您没有收到通知的原因是您在调度程序线程(又名:UI线程)上运行,即应用程序的主线程,该线程也用于重新绘制屏幕。因此,当您等待
GetTheJobDone(project)
方法完成时,不会进行屏幕绘制。因此,您需要两个线程来完成这项工作,一个线程来完成这项工作,另一个线程来完成通知
例如:
让我们在自己的线程上移动工作,但是,我们需要禁用屏幕(根据您的设计)。这可以通过在窗口上设置ishitestvisible
来实现(有关更好的方法,请参见文章末尾)
广泛的错误处理是必要的,因为我们阻止了与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
,因为等待封送返回调用线程。此外,错误处理变得更加清晰,因为现在常见的错误处理涵盖了多个执行路径
然而,与其只是阻塞UI,不如显示一个忙碌的指示器(一个带有进度指示器的覆盖图),这样她就知道发生了什么。在这种情况下,不需要设置
ishitestvisible
。以下是我自己基于的解决方案。正如我所建议的,我必须使它异步工作。现在我不锁定GUI,它工作正常
在所有ViewModels的基类中,我添加了一个名为AddTask的方法
public Task AddTask(Action work,
string notification,
string workDoneNotification,
Action<Task> 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;
}
您可能仍然需要一个线程来完成这项工作,还需要一个线程来显示对话框。否则,UI线程将忙于执行工作,并且在工作完成之前无法显示消息。您需要将代码发布到方法,可能还有一些XAML,以便我们查看您是否正在更新绑定到UI的属性。您也可以自己确定,并告诉我们您是否是。谢谢,但是您是否有建议如何根据MVVM模式实现它。为了方便起见,我在示例中使用了
文本框。通常,您只需调用NavigationService.AddNotification
方法,就像您在回答中所做的那样。如果一个答案是有帮助的,那么获得一个向上投票的奖励将是很好的;-)我会的,我只是以前没有这个名声:)
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;
}
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;
}
public Task AddTask(Action work,
string notification,
string workDoneNotification,
Action<Task> 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());