应用程序自动等待光标






4.86/5 (76投票s)
使用此库可以非常轻松地在应用程序工作时添加应用程序范围的等待光标。
引言
让您的 WinForm 应用程序更具用户友好性非常重要。良好的用户体验的一个方面是告知用户您的应用程序在短时间内无响应。本文介绍了一种使用一行启动代码添加应用程序范围等待光标的有效且简单的方法。
背景
最近,我在家处理一个 WinForms 项目,该项目有时会执行一些简短(少于 5 秒)的任务。我希望用户知道在此短时间内 UI 是无响应的,因此我选择使用 Cursors.WaitCursor
来指示这一点。本文展示了我如何最终实现 WaitCursor 库,我相信这是一个完全可重用的库。
使用代码
从开发者的角度来看,使用 WaitCursor 库再简单不过了。添加 WaitCursor 程序集的引用,然后在应用程序的启动代码中添加以下行
ApplicationWaitCursor.Cursor = Cursors.WaitCursor;
就是这样!
当然,您可以使用您喜欢的任何 Cursor
,您可以使用预定义的 Cursor
之一,或者您可以创建一个新的光标并使用它。您还可以微调在显示 Cursor
之前将经过的工作时间
ApplicationWaitCursor.Delay =
new TimeSpan(0, 0, 0, 1, 0); // Delay of 1 second
库的由来
在最近在家开发一个 WinForms 项目时,我决定像这样使用光标来指示用户正在运行的短任务
private void DoShortRunningTask()
{
Cursor.Current = Cursors.WaitCursor;
.. do some work ..
Cursor.Current = Cursors.Default;
}
现在,在你说“异常处理代码在哪里?”之前,我正试图说明我最终如何得到 WaitCursor 库的最终版本。
上面的代码可以工作,或者说,至少在大多数情况下是可以工作的。我当然发现,如果没有异常处理,我可能会永远处于 WaitCursor
**开启**状态,所以我想到了
private void DoShortRunningTask() { Cursor.Current = Cursors.WaitCursor; try { .. do some work .. } finally { Cursor.Current = Cursors.Default; } }
现在好多了,我确保即使发生异常,Cursor
也会始终返回到 Default
光标。然而,我发现将这段代码包装在我开发的每个短任务周围很麻烦。
然后我回想起我以前如何使用 C++ 基于堆栈的析构函数在退出方法时执行清理工作
void DoShortRunningTask() { StWaitCursor cursor = new StWaitCursor(); .. do some work .. // Implicitly called ~StWaitCursor returns the Cursor to Default }
但我不能像使用 C# 析构函数那样使用它们,因为不能保证 C# 析构函数何时被调用,因为垃圾回收线程负责这一点。
相反,C# 使用了一个不同的语言特性,即 using
语句,它会隐式调用实现 IDisposable
的对象的 Dispose
方法。虽然(我认为)它不像 C++ 析构函数那样易于使用,但您可以使用 using
语句达到相同的效果
private void DoShortRunningTask()
{
using (new StWaitCursor())
{
.. do some work ..
}
}
这段代码当然需要 StWaitCursor
类
public class StWaitCursor : IDisposable
{
public StWaitCursor()
{
Cursor.Current = Cursors.WaitCursor;
}
public void Dispose()
{
Cursor.Current = Cursors.Default;
}
}
好了,干净利落。然而,我发现如果我的短任务太短,用户就会看到光标快速闪烁来回于 WaitCursor
,这很烦人。因此,我决定需要一种方法可以在任务期间在预定时间后打开 WaitCursor
。
经过多次迭代和重构,我得到了一个名为 StDelayedCallback
的类(请参见源代码),这是一个泛型类,一旦实例化,它将等待指定的时间,然后调用 IDelayedCallbackHandler
的 Start
方法,并且*如果*调用了 Start
,它*保证*会调用同一接口的 Finish
方法。一旦有了这个,我就可以轻松地实现接口,使得在调用 Start
时打开 WaitCursor
,并在调用 Finish
时返回 Default
光标。
我发现的一些问题
在开发 WaitCursor 库的过程中,我发现我无法在 GUI 线程以外的任何线程上设置 Cursor.Current
。这时可以使用 Win32 的 AttachThreadInput
方法来有效地解决这个问题。因此,我开发了 StThreadAttachedDelayedCallback
类,它封装了对 AttachThreadInput
的调用。
所以最终我得到了一个通用的 StDelayedCallback
类,一个带有线程输入附加功能的版本 StThreadAttachedDelayedCallback
,以及一个特定的 Cursor
实现 StWaitCursor
。顺便说一句,我使用前缀 **St** 是因为我最初打算以类似 C++ **S**tack 的方式使用这些类,就像我过去使用的那样。到目前为止,我打算使用以下方式:
private void DoShortRunningTask()
{
using (new StWaitCursor(new TimeSpan(0, 0, 0, 0, 500))
{
.. do some work ..
}
}
但是,我仍然需要明确说明我想要在哪里显示 WaitCursor
。
然后我灵光一闪,想到了 ApplicationWaitCursor
单例类,它巧妙地封装了 StWaitCursor
,使得每当应用程序正在工作时,或者更确切地说,当它不定期调用 OnApplicationIdle
时,StWaitCursor
就会启动并显示 WaitCursor
。
我发现的唯一一个例外是当我拖动一个窗口时,这会阻止 OnApplicationIdle
调用。因此,我还拦截了 WM_NCLBUTTONDOWN
消息(在窗口拖动开始时发送),以暂时禁用 StWaitCursor
。
这就是我目前 WaitCursor 实现的进展,以及我如何实现的简要概述。您可以轻松地从 StDelayedCallback
类派生以创建类似的效果。也许您想在长时间运行的任务中显示一个无限进度条,或者在 StatusBar
控件中指示“正在保存更改…”
private void SaveChanges()
{
using (new StStatusBar(stbMain, "Saving Changes...")
{
.. do some work ..
}
// StStatusBar.IDispose could return the Text
// of the StatusBar to its original value
}
请注意,源代码不包含 StStatusBar
类,我只是用它作为示例,说明您还可以如何使用 StDelayedCallback
类。
历史
版本 1.0.0.0 (2005 年 3 月 12 日)
- 初始版本。
版本 1.0.1.0 (2005 年 3 月 16 日)
- 更新为符合 CLS 标准(感谢 **C a r l** 和 **Mathew Hall** 指出这一点)。