65.9K
CodeProject 正在变化。 阅读更多。
Home

应用程序自动等待光标

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (76投票s)

2005 年 3 月 14 日

公共领域

5分钟阅读

viewsIcon

379440

downloadIcon

3290

使用此库可以非常轻松地在应用程序工作时添加应用程序范围的等待光标。

Sample Screen Shot from Demo

引言

让您的 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 的类(请参见源代码),这是一个泛型类,一旦实例化,它将等待指定的时间,然后调用 IDelayedCallbackHandlerStart 方法,并且*如果*调用了 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** 指出这一点)。
© . All rights reserved.