Reputation: 31795
How can I display the Wait/Busy Cursor (usually the hourglass) to the user to let them know the program is doing something?
Upvotes: 318
Views: 546773
Reputation: 1226
What worked for me in a .Net Windows Application:
Cursor = Cursors.WaitCursor;
...
Cursor = Cursors.Default;
No need for Application.DoEvents();
.
Upvotes: 0
Reputation: 10260
A simple ScopeGuard
is a nice helper:
public record ScopeGuard(Action Action) : IDisposable
{
public void Dispose()
{
Action.Invoke();
}
}
and then
Enabled = false;
Application.UseWaitCursor = true;
Application.DoEvents();
using ScopeGuard sg = new(() =>
{
Application.UseWaitCursor = false;
Enabled = true;
});
// logic goes here
Upvotes: 0
Reputation: 11
You can use:
Mouse.OverrideCursor = Cursors.Wait;
&&
Mouse.OverrideCursor = Cursors.Arrow;
Upvotes: 0
Reputation: 101
For Windows Forms applications an optional disabling of a UI-Control can be very useful. So my suggestion looks like this:
public class AppWaitCursor : IDisposable
{
private readonly Control _eventControl;
public AppWaitCursor(object eventSender = null)
{
_eventControl = eventSender as Control;
if (_eventControl != null)
_eventControl.Enabled = false;
Application.UseWaitCursor = true;
Application.DoEvents();
}
public void Dispose()
{
if (_eventControl != null)
_eventControl.Enabled = true;
Cursor.Current = Cursors.Default;
Application.UseWaitCursor = false;
}
}
Usage:
private void UiControl_Click(object sender, EventArgs e)
{
using (new AppWaitCursor(sender))
{
LongRunningCall();
}
}
Upvotes: 10
Reputation: 1623
Use this with WPF:
Cursor = Cursors.Wait;
// Your Heavy work here
Cursor = Cursors.Arrow;
Upvotes: 3
Reputation: 51
Okey,Other people's view are very clear, but I would like to do some added, as follow:
Cursor tempCursor = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
//do Time-consuming Operations
Cursor.Current = tempCursor;
Upvotes: 5
Reputation: 8258
My approach would be to make all the calculations in a background worker.
Then change the cursor like this:
this.Cursor = Cursors.Wait;
And in the thread's finish event restore the cursor:
this.Cursor = Cursors.Default;
Note, this can also be done for specific controls, so the cursor will be the hourglass only when the mouse is above them.
Upvotes: 26
Reputation: 2096
With the class below you can make the suggestion of Donut "exception safe".
using (new CursorHandler())
{
// Execute your time-intensive hashing code here...
}
the class CursorHandler
public class CursorHandler
: IDisposable
{
public CursorHandler(Cursor cursor = null)
{
_saved = Cursor.Current;
Cursor.Current = cursor ?? Cursors.WaitCursor;
}
public void Dispose()
{
if (_saved != null)
{
Cursor.Current = _saved;
_saved = null;
}
}
private Cursor _saved;
}
Upvotes: 3
Reputation: 2470
OK so I created a static async method. That disabled the control that launches the action and changes the application cursor. It runs the action as a task and waits for to finish. Control returns to the caller while it waits. So the application remains responsive, even while the busy icon spins.
async public static void LengthyOperation(Control control, Action action)
{
try
{
control.Enabled = false;
Application.UseWaitCursor = true;
Task doWork = new Task(() => action(), TaskCreationOptions.LongRunning);
Log.Info("Task Start");
doWork.Start();
Log.Info("Before Await");
await doWork;
Log.Info("After await");
}
finally
{
Log.Info("Finally");
Application.UseWaitCursor = false;
control.Enabled = true;
}
Here's the code form the main form
private void btnSleep_Click(object sender, EventArgs e)
{
var control = sender as Control;
if (control != null)
{
Log.Info("Launching lengthy operation...");
CursorWait.LengthyOperation(control, () => DummyAction());
Log.Info("...Lengthy operation launched.");
}
}
private void DummyAction()
{
try
{
var _log = NLog.LogManager.GetLogger("TmpLogger");
_log.Info("Action - Sleep");
TimeSpan sleep = new TimeSpan(0, 0, 16);
Thread.Sleep(sleep);
_log.Info("Action - Wakeup");
}
finally
{
}
}
I had to use a separate logger for the dummy action (I am using Nlog) and my main logger is writing to the UI (a rich text box). I wasn't able to get the busy cursor show only when over a particular container on the form (but I didn't try very hard.) All controls have a UseWaitCursor property, but it doesn't seem have any effect on the controls I tried (maybe because they weren't on top?)
Here's the main log, which shows things happening in the order we expect:
16:51:33.1064 Launching lengthy operation...
16:51:33.1215 Task Start
16:51:33.1215 Before Await
16:51:33.1215 ...Lengthy operation launched.
16:51:49.1276 After await
16:51:49.1537 Finally
Upvotes: 6
Reputation: 1133
Building on the previous, my preferred approach (since this is a frequently performed action) is to wrap the wait cursor code in an IDisposable helper class so it can be used with using() (one line of code), take optional parameters, run the code within, then clean up (restore cursor) afterwards.
public class CursorWait : IDisposable
{
public CursorWait(bool appStarting = false, bool applicationCursor = false)
{
// Wait
Cursor.Current = appStarting ? Cursors.AppStarting : Cursors.WaitCursor;
if (applicationCursor) Application.UseWaitCursor = true;
}
public void Dispose()
{
// Reset
Cursor.Current = Cursors.Default;
Application.UseWaitCursor = false;
}
}
Usage:
using (new CursorWait())
{
// Perform some code that shows cursor
}
Upvotes: 44
Reputation: 112815
You can use Cursor.Current
.
// Set cursor as hourglass
Cursor.Current = Cursors.WaitCursor;
// Execute your time-intensive hashing code here...
// Set cursor as default arrow
Cursor.Current = Cursors.Default;
However, if the hashing operation is really lengthy (MSDN defines this as more than 2-7 seconds), you should probably use a visual feedback indicator other than the cursor to notify the user of the progress. For a more in-depth set of guidelines, see this article.
Edit:
As @Am pointed out, you may need to call Application.DoEvents();
after Cursor.Current = Cursors.WaitCursor;
to ensure that the hourglass is actually displayed.
Upvotes: 558
Reputation: 1643
It is easier to use UseWaitCursor at the Form or Window level. A typical use case can look like below:
private void button1_Click(object sender, EventArgs e)
{
try
{
this.Enabled = false;//optional, better target a panel or specific controls
this.UseWaitCursor = true;//from the Form/Window instance
Application.DoEvents();//messages pumped to update controls
//execute a lengthy blocking operation here,
//bla bla ....
}
finally
{
this.Enabled = true;//optional
this.UseWaitCursor = false;
}
}
For a better UI experience you should use Asynchrony from a different thread.
Upvotes: 36
Reputation: 5426
Actually,
Cursor.Current = Cursors.WaitCursor;
temporarily sets the Wait cursor, but doesn’t ensure that the Wait cursor shows until the end of your operation. Other programs or controls within your program can easily reset the cursor back to the default arrow as in fact happens when you move mouse while operation is still running.
A much better way to show the Wait cursor is to set the UseWaitCursor property in a form to true:
form.UseWaitCursor = true;
This will display wait cursor for all controls on the form until you set this property to false. If you want wait cursor to be shown on Application level you should use:
Application.UseWaitCursor = true;
Upvotes: 201