15

If I am in a function in the code behind, and I want to implement displaying a "Loading..." in the status bar the following makes sense, but as we know from WinForms is a NoNo:

StatusBarMessageText.Text = "Loading Configuration Settings...";            
LoadSettingsGridData();
StatusBarMessageText.Text = "Done";

What we all now from WinForms Chapter 1 class 101, is the form won't display changes to the user until after the Entire Function completes... meaning the "Loading" message will never be displayed to the user. The following code is needed.

Form1.SuspendLayout();    
StatusBarMessageText.Text = "Loading Configuration Settings...";                
Form1.ResumeLayout();

LoadSettingsGridData();

Form1.SuspendLayout();    
StatusBarMessageText.Text = "Done";
Form1.ResumeLayout();

What is the best practice for dealing with this fundamental issue in WPF?

g t
  • 7,287
  • 7
  • 50
  • 85
mrbradleyt
  • 2,312
  • 7
  • 33
  • 38

3 Answers3

37

Best and simplest:

using(var d = Dispatcher.DisableProcessing())
{
    /* your work... Use dispacher.begininvoke... */
}

Or

IDisposable d;

try
{
    d = Dispatcher.DisableProcessing();
    /* your work... Use dispacher.begininvoke... */
} finally {
    d.Dispose();
}
Rand Random
  • 7,300
  • 10
  • 40
  • 88
Ernest
  • 371
  • 3
  • 3
  • 1
    I just added this to a DataGrid update in a .NET 4 app and it made the grid updates much smoother. – yzorg Sep 22 '10 at 18:36
  • 15
    Surely you want to put this into a `using (var d = Dispatcher.DisableProcessing())`, just so that any exceptions don't leave it permanently disabled. – Roman Starkov Jan 02 '12 at 14:08
  • 2
    Somewhat dangerous since this pauses the Dispatcher and not just a single window, so all UI operations will be halted as long as they are running using the same dispatcher. (which is all in most cases) – Kelly Jun 01 '17 at 19:15
0

The easiest way to get this to work is to add the LoadSettingsGridData to the dispatcher queue. If you set the operation's DispatcherPriority sufficiently low enough, the layout operations will occur, and you will be good to go.

StatusBarMessageText.Text = "Loading Configuration Settings...";
this.Dispatcher.BeginInvoke(new Action(LoadSettingsGridData), DispatcherPriority.Render);
this.Dispatcher.BeginInvoke(new Action(() => StatusBarMessageText.Text = "Done"), DispatcherPriority.Render);
Abe Heidebrecht
  • 30,090
  • 7
  • 62
  • 66
0

In reading the article by Shawn Wildermuth WPF Threads: Build More Responsive Apps With The Dispatcher.

I came accross the following, which states you can use the Background Worker just like you could in WindowsForms. Fancy that:

BackgroundWorker Now that you have a sense of how the Dispatcher works, you might be surprised to know that you will not find use for it in most cases. In Windows Forms 2.0, Microsoft introduced a class for non-UI thread handling to simplify the development model for user interface developers. This class is called the BackgroundWorker. Figure 7 shows typical usage of the BackgroundWorker class.

Figure 7 Using a BackgroundWorker in WPF

BackgroundWorker _backgroundWorker = new BackgroundWorker();

...

// Set up the Background Worker Events
_backgroundWorker.DoWork += _backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += 
    _backgroundWorker_RunWorkerCompleted;

// Run the Background Worker
_backgroundWorker.RunWorkerAsync(5000);

...

// Worker Method
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Do something
}

// Completed Method
void _backgroundWorker_RunWorkerCompleted(
    object sender, 
    RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        statusText.Text = "Cancelled";
    }
    else if (e.Error != null) 
    {
        statusText.Text = "Exception Thrown";
    }
    else 
    {
        statusText.Text = "Completed";
    }
}

The BackgroundWorker component works well with WPF because underneath the covers it uses the AsyncOperationManager class, which in turn uses the SynchronizationContext class to deal with synchronization. In Windows Forms, the AsyncOperationManager hands off a WindowsFormsSynchronizationContext class that derives from the SynchronizationContext class. Likewise, in ASP.NET it works with a different derivation of SynchronizationContext called AspNetSynchronizationContext. These SynchronizationContext-derived classes know how to handle the cross-thread synchronization of method invocation.

In WPF, this model is extended with a DispatcherSynchronizationContext class. By using BackgroundWorker, the Dispatcher is being employed automatically to invoke cross-thread method calls. The good news is that since you are probably already familiar with this common pattern, you can continue using BackgroundWorker in your new WPF projects.

adrianbanks
  • 81,306
  • 22
  • 176
  • 206
mrbradleyt
  • 2,312
  • 7
  • 33
  • 38