我如何向用户显示等待/忙碌游标(通常是沙漏),让他们知道程序正在做什么?
你可以使用Cursor.Current。
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秒),你可能应该使用一个视觉反馈指示器而不是游标来通知用户进度。要了解更深入的指导原则,请参见这篇文章。
Cursor.Current = Cursors.WaitCursor;
Application.DoEvents();
我的方法是在后台工作程序中进行所有的计算。
然后像这样改变光标:
this.Cursor = Cursors.Wait;
并且在线程的finish事件中恢复游标:
this.Cursor = Cursors.Default;
注意,这也可以用于特定的控件,所以只有当鼠标在沙漏上方时,光标才会是沙漏。
实际上,
暂时设置等待游标,但不确保等待游标直到操作结束才显示。程序中的其他程序或控件可以很容易地将光标重置回默认箭头,就像在操作仍在运行时移动鼠标一样。
一个更好的显示等待游标的方法是在表单中设置UseWaitCursor属性为true:
form.UseWaitCursor = true;
Application.UseWaitCursor = true;
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体验,你应该在不同的线程中使用异步。
在前面的基础上,我的首选方法(因为这是一个经常执行的操作)是将等待游标代码包装在一个IDisposable助手类中,这样它就可以与using()(一行代码)一起使用,接受可选参数,在其中运行代码,然后清理(恢复游标)。
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 }
我创建了一个静态异步方法。这将禁用启动操作并更改应用程序游标的控件。它将操作作为任务运行,并等待完成。控件在等待时返回给调用者。因此,即使在忙碌的图标旋转时,应用程序也能保持响应。
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)和我的主日志记录器写入用户界面(一个富文本框)。只有在表单上的特定容器上才能显示繁忙的游标(但我没有很努力地尝试)。所有控件都有一个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
使用下面的类,您可以使Donut的建议“异常安全”。
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; }
好的,其他人的观点很清楚,但我想补充一些,如下:
Cursor tempCursor = Cursor.Current; Cursor.Current = Cursors.WaitCursor; //do Time-consuming Operations Cursor.Current = tempCursor;
在WPF中使用:
Cursor = Cursors.Wait; // Your Heavy work here Cursor = Cursors.Arrow;
对于Windows窗体应用程序,可选的禁用ui控件是非常有用的。所以我的建议是这样的:
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(); } }
你可以使用:
Mouse.OverrideCursor = Cursors.Wait;
,,
Mouse.OverrideCursor = Cursors.Arrow;