在我的MVVM Light应用程序中,我想在做一些需要两到三秒的同步工作之前显示通知。我不希望用户在工作完成时做任何事情,因此不需要异步,任务和IProgress或后台工作者等。
在ViewModel中我有这个代码。 (请注意,这是不位于XAML文件的代码隐藏中,但位于数据绑定ViewModel中)
void MyCommand(Project project)
{
NavigationService.AddNotification("Doin' it");
GetTheJobDone(project);
...
}
NavigationService将通知文本添加到客户端窗口顶部的数据绑定ListView。
我的问题是" Doin'它"无论花费多长时间,在项目加载后显示。
一个例子可能是我想加载并显示项目。如果加载部分需要几秒钟,则界面会冻结而不会向用户提供任何信息。
修改
由于请求,我添加了一些代码。这段代码工作正常。
显示通知的视图具有此XAML
<ListView ItemsSource="{Binding Notifications}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Foreground="LightGray" Content="{Binding Message}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
保存通知的ViewModel具有此代码
public int AddNotification(string message)
{
...
Notification note = new Notification { Message = message };
Notifications.Add(note);
...
}
答案 0 :(得分:2)
您没有收到通知的原因是您在调度程序线程(也就是: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;
}
广泛的错误处理是必要的,因为我们阻止了与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,而是显示忙碌指示符(带有进度指示符的叠加层)可能更好,这样她就知道发生了什么。在这种情况下,不需要设置IsHitTestVisible
。
答案 1 :(得分:0)
这是基于AxelEckenbergers answer的我自己的解决方案。正如所建议的那样,我必须让它异步工作。现在我没有锁定GUI,它运行正常。
在我所有ViewModel的基类中,我添加了一个名为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;
}
电话看起来像这样
AddTask(() => GetTheJobDone(),
"Doin' it",
"It's done",
t => LoadProjects());