For a view constructed using WPF, I want to change the mouse cursor to a hourglass when the application is busy and nonresponsive.
One solution is to add
I am simply doing
Mouse.OverrideCursor = Cursors.Wait;
try {
// Long lasting stuff ...
} finally {
Mouse.OverrideCursor = null;
}
According to the documentation of Mouse.OverrideCursor Property
To clear the override Cursor, set OverrideCursor to null.
The try-finally statement ensures that the default cursor is restored in any case, even when an exception occurs or the try-part is left with return
or break
(if inside a loop).
The best way would be to not cause the UI to become non-responsive ever, offloading all of the work to other threads/tasks as appropriate.
Other than that, you're kindof in a catch-22: if you did add a way to detect that the ui is non-responsive, there's no good way to change the cursor, as the place you'd need to do that (the even thread) is non-responsive... You might be able to pinvoke out to standard win32 code to change the cursor for the whole window, though?
Otherwise, you'd have to do it pre-emptively, like your question suggests.
I used Olivier Jacot-Descombes's solution, it's very simple and working well. thanks. update: it even works well without using a different threads/background worker.
I use it with backgroudworker, mouse cursor looks great when it's busy working and return to normal when the work is done.
public void pressButtonToDoSomeLongTimeWork()
{
Mouse.OverrideCursor = Cursors.Wait;
// before the long time work, change mouse cursor to wait cursor
worker.DoWork += doWorkLongTimeAsync;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync(); //start doing some long long time work but GUI can update
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
//long time work is done();
updateGuiToShowTheLongTimeWorkResult();
Mouse.OverrideCursor = null; //return mouse cursor to normal
}
Be careful here because fiddling with the Wait Cursor can cause some problems with STA threads. Make sure that if you use this thing that you are doing it within its own thread. I posted an example here Run inside an STA which uses this to show a WaitCursor while the spawned file is starting up, and does not blow up (the main application) AFAICT.
Changing the cursor doesn't means the application will not respond to mouse and keyboard events after the long running task has finished. To avoid user missleading, I use the class below that removes all the keyboard and mouse messages from the application message queue.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Input;
public class WpfHourGlass : IDisposable
{
[StructLayout(LayoutKind.Sequential)]
private struct POINTAPI
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSG
{
public int hwnd;
public int message;
public int wParam;
public int lParam;
public int time;
public POINTAPI pt;
}
private const short PM_REMOVE = 0x1;
private const short WM_MOUSELAST = 0x209;
private const short WM_MOUSEFIRST = 0x200;
private const short WM_KEYFIRST = 0x100;
private const short WM_KEYLAST = 0x108;
[DllImport("user32", EntryPoint = "PeekMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int PeekMessage([MarshalAs(UnmanagedType.Struct)]
ref MSG lpMsg, int hwnd, int wMsgFilterMin, int wMsgFilterMax, int wRemoveMsg);
public WpfHourGlass()
{
Mouse.OverrideCursor = Cursors.Wait;
bActivated = true;
}
public void Show(bool Action = true)
{
if (Action)
{
Mouse.OverrideCursor = Cursors.Wait;
}
else
{
Mouse.OverrideCursor = Cursors.Arrow;
}
bActivated = Action;
}
#region "IDisposable Support"
// To detect redundant calls
private bool disposedValue;
private bool bActivated;
// IDisposable
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
//remove todas as mensagens de mouse
//e teclado que tenham sido produzidas
//durante o processamento e estejam
//enfileiradas
if (bActivated)
{
MSG pMSG = new MSG();
while (Convert.ToBoolean(PeekMessage(ref pMSG, 0, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)))
{
}
while (Convert.ToBoolean(PeekMessage(ref pMSG, 0, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)))
{
}
Mouse.OverrideCursor = Cursors.Arrow;
}
}
// TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
// TODO: set large fields to null.
}
this.disposedValue = true;
}
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
I used the answers here to build something that worked better for me. The problem is that when the using block in Carlo's answer finishes, the UI might actually still be busy databinding. There might be lazy-loaded data or events firing as a result of what was done in the block. In my case it sometimes took several seconds from the waitcursor disappeared until the UI was actually ready. I solved it by creating a helper method that sets the waitcursor and also takes care of setting up a timer that will automatically set the cursor back when the UI is ready. I can't be sure that this design will work in all cases, but it worked for me:
/// <summary>
/// Contains helper methods for UI, so far just one for showing a waitcursor
/// </summary>
public static class UiServices
{
/// <summary>
/// A value indicating whether the UI is currently busy
/// </summary>
private static bool IsBusy;
/// <summary>
/// Sets the busystate as busy.
/// </summary>
public static void SetBusyState()
{
SetBusyState(true);
}
/// <summary>
/// Sets the busystate to busy or not busy.
/// </summary>
/// <param name="busy">if set to <c>true</c> the application is now busy.</param>
private static void SetBusyState(bool busy)
{
if (busy != IsBusy)
{
IsBusy = busy;
Mouse.OverrideCursor = busy ? Cursors.Wait : null;
if (IsBusy)
{
new DispatcherTimer(TimeSpan.FromSeconds(0), DispatcherPriority.ApplicationIdle, dispatcherTimer_Tick, Application.Current.Dispatcher);
}
}
}
/// <summary>
/// Handles the Tick event of the dispatcherTimer control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private static void dispatcherTimer_Tick(object sender, EventArgs e)
{
var dispatcherTimer = sender as DispatcherTimer;
if (dispatcherTimer != null)
{
SetBusyState(false);
dispatcherTimer.Stop();
}
}
}