WPF MVVM Light - Show notification before work is done

…衆ロ難τιáo~ 提交于 2019-12-04 19:03:28

The reason why you not getting a notification is that you running on the dispatcher thread (aka: UI thread), i.e. the main thread of your application, that is also used for redrawing your screen. So while you wait for the GetTheJobDone(project) method to complete no screen drawing will be done. Therefore, you will need two threads to get this working, one to do the work and one to do the notification.

E.g.:

Lets move the work on its own thread, however, we then need to disable the screen (as by your design). This can be done by setting IsHitTestVisible on the window (see end of post for a better approach).

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;
}

The extensive error handling is necessary as we blocked the interaction with the UI - not a good thing to do (see below) - and must make sure that it becomes available again should an error occur.

When using .NET 4.5 you can boil that code down to this:

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;
}

Here no Dispatcher.Invoke is required, as await marshals back to the calling thread. Also the error handling becomes much clearer as several execution paths are covered by a common error handling now.

However, instead of just blocking the UI it might be better to show a busy indicator (an overlay with a progress indicator), so that she knows what's going on. In this case setting IsHitTestVisible is not necessary.

AxdorphCoder

Here's my own solution based on AxelEckenbergers answer. As suggested I had to make it asynchronous to work. For now i don't lock the GUI, and it works fine.

In the base class for all my ViewModels i add a method called 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;
}

The call looks like this

AddTask(() => GetTheJobDone(), 
   "Doin' it",
   "It's done",
   t => LoadProjects());
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!