如何向用户显示等待/忙碌光标(通常是沙漏),让他们知道程序正在执行某些操作?
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;
但是,如果哈希操作确实很长(MSDN 将其定义为超过 2-7 秒),您可能应该使用除光标之外的视觉反馈指示器来通知用户进度。如需更深入的指南,请参阅本文。
编辑:
正如@Am指出的,您可能需要在 Application.DoEvents();
之后调用
Cursor.Current = Cursors.WaitCursor;
以确保沙漏实际显示。
Cursor.Current = Cursors.WaitCursor;
暂时设置等待光标,但不确保等待光标显示直到操作结束。程序中的其他程序或控件可以轻松地将光标重置回默认箭头,就像在操作仍在运行时移动鼠标时发生的情况一样。
显示等待光标的更好方法是将表单中的 UseWaitCursor 属性设置为 true:
form.UseWaitCursor = true;
这将为表单上的所有控件显示等待光标,直到您将此属性设置为 false。 如果您希望等待光标显示在应用程序级别,您应该使用:
Application.UseWaitCursor = true;
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;
}
}
用途:
using (new CursorWait())
{
// Perform some code that shows cursor
}
UseWaitCursor 更容易。 典型的用例如下所示:
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;
}
}
为了获得更好的 UI 体验,您应该从不同的线程使用异步。
然后像这样改变光标:
this.Cursor = Cursors.Wait;
并在线程的完成事件中恢复光标:
this.Cursor = Cursors.Default;
注意,这也可以针对特定控件执行,因此仅当鼠标位于其上方时光标才会是沙漏。
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;
}
}
用途:
private void UiControl_Click(object sender, EventArgs e)
{
using (new AppWaitCursor(sender))
{
LongRunningCall();
}
}
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;
}
这是主窗体的代码
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
{
}
}
我必须使用单独的记录器来执行虚拟操作(我正在使用 Nlog),而我的主记录器正在写入 UI(富文本框)。仅当在表单上的特定容器上时,我无法显示繁忙的光标(但我没有努力尝试。)所有控件都有 UseWaitCursor 属性,但它似乎对控件没有任何影响我尝试过(也许是因为他们不在上面?)
这是主日志,它显示了按照我们期望的顺序发生的事情:
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
Cursor tempCursor = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
//do Time-consuming Operations
Cursor.Current = tempCursor;
using (new CursorHandler())
{
// Execute your time-intensive hashing code here...
}
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;
}
与 WPF 一起使用:
Cursor = Cursors.Wait;
// Your Heavy work here
Cursor = Cursors.Arrow;
Mouse.OverrideCursor = Cursors.Wait;
&&
Mouse.OverrideCursor = Cursors.Arrow;
ScopeGuard
就是一个好帮手:
public record ScopeGuard(Action Action) : IDisposable
{
public void Dispose()
{
Action.Invoke();
}
}
然后
Enabled = false;
Application.UseWaitCursor = true;
Application.DoEvents();
using ScopeGuard sg = new(() =>
{
Application.UseWaitCursor = false;
Enabled = true;
});
// logic goes here
Cursor = Cursors.WaitCursor;
...
Cursor = Cursors.Default;
不需要Application.DoEvents();
。