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

时间:2014-08-22 15:03:29

标签: c# wpf mvvm mvvm-light

在我的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);
   ...
}

2 个答案:

答案 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());