I am trying to display a please wait dialog for a long running operation. The problem is since this is single threaded even though I tell the WaitScreen to display it never
Check out my comprehensive research of this very delicate topic. If there's nothing you can do to improve the actual performance, you have the following options to display a waiting message:
Option #1 Execute a code to display a waiting message synchronously in the same method which does the real task. Just put this line before a lengthy process:
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { /* Your code to display a waiting message */ }));
It'll process pending messages on the main dispatcher thread at the end of the Invoke().
Note: Reason for selecting Application.Current.Dispatcher but Dispatcher.CurrentDispatcher is explained here.
Option #2 Display a “Wait” screen and update UI (process pending messages).
To do it WinForms developers executed Application.DoEvents method. WPF offers two alternatives to achieve similar results:
Option #2.1 With using DispatcherFrame class.
Check a bit bulky example from MSDN:
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
public object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
Option #2.2 Invoke an empty Action
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, (Action)(() => { }));
See discussions which one (2.1 or 2.2) is better here. IMHO option #1 is still better than #2.
Option #3 Display a waiting message in a separate window.
It comes in handy when you display not a simple waiting message, but an animation. Rendering a loading animation at the same time that we are waiting for another long rendering operation to complete is a problem. Basically, we need two rendering threads. You can't have multiple rendering threads in a single window, but you can put your loading animation in a new window with its own rendering thread and make it look like it's not a separate window.
Download WpfLoadingOverlay.zip from this github (it was a sample from article "WPF Responsiveness: Asynchronous Loading Animations During Rendering", but I can't find it on the Web anymore) or have a look at the main idea below:
public partial class LoadingOverlayWindow : Window
{
/// <summary>
/// Launches a loading window in its own UI thread and positions it over <c>overlayedElement</c>.
/// </summary>
/// <param name="overlayedElement"> An element for overlaying by the waiting form/message </param>
/// <returns> A reference to the created window </returns>
public static LoadingOverlayWindow CreateAsync(FrameworkElement overlayedElement)
{
// Get the coordinates where the loading overlay should be shown
var locationFromScreen = overlayedElement.PointToScreen(new Point(0, 0));
// Launch window in its own thread with a specific size and position
var windowThread = new Thread(() =>
{
var window = new LoadingOverlayWindow
{
Left = locationFromScreen.X,
Top = locationFromScreen.Y,
Width = overlayedElement.ActualWidth,
Height = overlayedElement.ActualHeight
};
window.Show();
window.Closed += window.OnWindowClosed;
Dispatcher.Run();
});
windowThread.SetApartmentState(ApartmentState.STA);
windowThread.Start();
// Wait until the new thread has created the window
while (windowLauncher.Window == null) {}
// The window has been created, so return a reference to it
return windowLauncher.Window;
}
public LoadingOverlayWindow()
{
InitializeComponent();
}
private void OnWindowClosed(object sender, EventArgs args)
{
Dispatcher.InvokeShutdown();
}
}
I found a way! Thanks to this thread.
public static void ForceUIToUpdate()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Render, new DispatcherOperationCallback(delegate(object parameter)
{
frame.Continue = false;
return null;
}), null);
Dispatcher.PushFrame(frame);
}
That function needs to be called right before the long running operation. That will then Force the UI thread to update.
Doing it single threaded really is going to be a pain, and it'll never work as you'd like. The window will eventually go black in WPF, and the program will change to "Not Responding".
I would recommending using a BackgroundWorker to do your long running task.
It's not that complicated. Something like this would work.
private void DoWork(object sender, DoWorkEventArgs e)
{
//Do the long running process
}
private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Hide your wait dialog
}
private void StartWork()
{
//Show your wait dialog
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWork;
worker.RunWorkerCompleted += WorkerCompleted;
worker.RunWorkerAsync();
}
You can then look at the ProgressChanged event to display a progress if you like (remember to set WorkerReportsProgress to true). You can also pass a parameter to RunWorkerAsync if your DoWork methods needs an object (available in e.Argument).
This really is the simplest way, rather than trying to do it singled threaded.
Another option is to write your long-running routine as a function that returns IEnumerable<double>
to indicate progress, and just say:
yield return 30;
That would indicate 30% of the way through, for example. You can then use a WPF timer to execute it in the "background" as a co-operative coroutine.
It's described in some detail here, with sample code.