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

在多窗格状态栏中放置进度条、位图、动画或对话框(简单方法)。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (15投票s)

2004年3月4日

GPL3

13分钟阅读

viewsIcon

164405

downloadIcon

5549

只需<b>三行代码</b>即可在状态栏的窗格中放置一个进度条,或只需<b>一行代码</b>即可放置位图、动画或<b>任何具有 HWND 的控件</b>。

Sample application:
The red arrow points to an AVI control, 
the blue one to a progress bar, 
and the green one to a bitmap. 
The first and last panes show icons.

红箭头指向 AVI 控件,蓝箭头指向进度条,绿箭头指向位图。第一个和最后一个窗格显示图标。

引言

三行代码,您只需用三行代码即可在状态栏的一个窗格中显示进度条:一行用于创建它,一行用于更新其位置,一行用于关闭它。

调用 ProgCreate() 将在您选择的状态栏窗格中创建一个进度条。

调用 ProgSetPos()(或其许多等效函数之一)将在您喜欢的位置设置进度条的位置。调用 ProgDestroyWindow() 将关闭进度条,使状态栏恢复原状。

或者,如果您想在窗格中显示位图,只需<b>一行代码</b>即可完成:SetBitmap() 就是您需要的一切。如果您想要动画,只需<b>一行代码</b>:调用 AnimCreate() 即可。

而且,为了灵活性,只需<b>一行代码</b>即可将<b>任何具有 HWND 的控件</b>放入窗格:调用 AddAny() 即可。

当然,您必须包含相关的头文件,并声明一个成员变量……但这不算代码,对吧?

背景

问题

您的程序偶尔会执行一项耗时操作,因此您决定在其中使用进度条(或者,如果操作时长未知且难以猜测,例如连接到服务器或数据库,则显示动画可能是您的选择)。其他人也这样做,对吧?如果您的应用程序有状态栏,您就会想把它放在那里。由于您的 Internet 浏览器和文字处理器使用多窗格状态栏来执行此操作,并在进度条旁边显示“正在加载…”或“正在保存…”消息,因此您决定也这样做,顺便说一句,在另一个窗格中将您的公司徽标添加为位图,因为您的老板在看到一个在其状态栏中显示其供应商徽标的应用程序后,喊道:“我也要!”(这意味着您)。

一切看起来都很好……直到您(或您的 QA 人员,或用户)在显示进度条时调整窗口大小,进度条会滑出其窗格,而位图则完全脱离状态栏的边界。或者,用户浏览菜单,状态栏中解释当前菜单选项作用的字符串被进度条和/或位图遮挡。
而且您还打算编写更多 WTL 应用程序……

解决方案

让状态栏拥有一个进度条,也许还有几个位图,时不时还有一个动画,它们都作为子窗口和 C++ 成员,并在处理 WM_SIZE 时移动它们,并在 SB_SIMPLE 时隐藏或显示它们。

ATL/WTL 向现有类添加功能的方法是编写一个混合类来实现所述功能,并将其混入其中。因此,在头文件中定义了四个实现类

  • CProgressBarInPaneImpl<T> 包含一个 WTL::CProgressBarCtrl,在其姐妹 WTL::CMultiPaneStatusBarImpl 的窗格中,从而为混合体添加了进度条功能。
  • CBitmapInPaneImpl<T> 处理一个包含 **十五个** 结构的数组,每个结构管理一个与窗格关联的位图(使用 WTL::CStatic)。您的状态栏可以变成一个微型美术馆。
  • CAnimationInPaneImpl<T> 包含一个 WTL::CAnimateCtrl,连接到一个窗格。
  • CAnythingInPaneImpl<T> 将接受您提供的任何 HWND(最多 15 个),将其移动到窗格上并保持在那里。在示例应用程序中,您可以看到一个无模式对话框。

这些类与 WTL::CMultiPaneStatusBarImpl 一起,被非模板类以多种组合继承。示例应用程序使用 CMPSBarWithAll,正如机敏的读者可能已经巧妙地猜测到的那样,它使用了本文所述的所有模板类的功能。

实现类 **包含** 负责状态栏、位图和动画的 WTL 类,从而让 WTL 处理大部分功能,并保持与未来版本的兼容性。

包含(“拥有另一个类的成员,并且不将其公开”的流行语)允许只公开成员的一部分功能:例如,没有理由公开创建 CProgressBarCtrl 的选项而不带父级状态栏,或者允许在已打开一个 CProgressBarCtrl 的情况下打开第二个 CProgressBarCtrl:大多数应用程序同时显示的进度条不会超过一个,并且该类符合这一事实标准。否则,就需要管理一个包含 CProgressBarCtrl 的容器,检查它们不共享窗格,总之,有很多工作(谨慎避免双关语)但收益甚微。

相应的容器类公开了 CProgressBarCtrl 的 **大部分** 函数(带有“Prog”前缀:例如,SetPos(int nPos) 变为 ProgSetPos(int nPos)),但 CreateDestroy 被完全替换。

此外,类还处理 SB_SIMPLE,以便与它们显示的窗格同步隐藏和显示进度条/动画/位图。

工作原理

有兴趣了解实际代码的读者可以阅读 MultiPaneStatusBarEx.h,代码中有相当多的注释,我已尽力做到清晰明了。

对于那些满足于重点介绍的人来说,可以说前三个实现类是混合类,它们聚合了一个或多个提供类似功能的 WTL 类的成员,具有 Create()Destroy() 函数,这些函数以窗格的序号作为第一个参数(如果需要),通过隐藏或显示其内容来处理 SB_SIMPLE,并将 bHandled 设置为 false 以便 WTL::CMultiPaneStatusBarImpl 有机会处理该消息,并且每个类都实现一个版本的 UpdatePanesLayout(),该版本移动其包含的任何内容以与姐妹 WTL::CMultiPaneStatusBarImpl 同步。
CAnythingInPaneImpl 在这方面有所不同:它不创建它所持有的 HWND(尽管它可以选择销毁它们),也不让状态栏成为它们的父级,因此通知(如果有)会被发送到原始父级。它主要用于容纳无模式对话框和控件:程序员会将组合框放在最不寻常的地方……

使用代码

假设您熟悉 WTL 的 CMultiPaneStatusBarCtrl,如果不熟悉,请参阅底部的《参考文献》部分引用的文章,特别是 Ed Gadziemski 的 如何使用 WTL 多窗格状态栏控件

首先,#include <MultiPaneStatusBarEx.h>。请注意,您必须包含 atlctrlx.h 头文件,它定义了 CMultiPaneStatusBarCtrl 类,以及 atlctrls.h,用于 WTL::CProgressBarCtrlWTL::CStaticWTL::CAnimateCtrl

在您原本使用 CMultiPaneStatusBarCtrl 的地方,请改用 MultiPaneStatusBarEx.h 中定义的非模板类之一。通常,它将是您框架类的成员。

添加进度条

A status bar with an animation in its second pane, 
a progress bar in the third,
and a bitmap in the fourth

CMPSBarWithProgressCMPSBarWithProgressAndBMPCMPSBarWithAll 附加到状态栏的 HWND 并使用字符串资源 ID 数组设置窗格后,您就可以开始使用进度条了:要创建一个,请调用 ProgCreate()。第一个参数是要创建进度条的窗格的零基序号,第二个和第三个参数是进度条范围的上限和下限(默认值 1 到 100 假定百分比被广泛使用),所有其他参数都具有合理的默认值(一种礼貌的说法,意思是“最好不要动”)。

您可以通过调用 ProgSetPos()ProgOffsetPos()ProgStepIt() 来设置进度条的位置,这些函数分别解析为 SetPos()OffsetPos()StepIt()。如果您决定使用 ProgStepIt(),您很可能想在之前调用 ProgSetStep()

完成进度后,调用 ProgDestroyWindow() 来关闭进度条。

    // Function declarations, for opening and closing 

    // a progress bar, from class CProgressBarInPaneImpl

    BOOL ProgCreate(int iPane, // Status pane where 

      // we'll create the progress bar, zero-based.

      int nMin = 0, int nMax = 100,  // Progress bar initial range

      DWORD dwStyle = WS_CHILD | WS_VISIBLE | PBS_SMOOTH, 
                                     // Progress bar styles

      DWORD dwExStyle = 0
    ); 
    void ProgDestroyWindow(void);

    // Contained accessors, all of them inline (there's a dozen):

    int ProgSetPos(int nPos)        { return m_Progress.SetPos(nPos); }
    int ProgStepIt()                { return m_Progress.StepIt(); }

    // Member accessors

    int   ProgGetPane() const       { return m_iProgressPane; }

    // Data members

protected:
    CProgressBarCtrl m_Progress;  // This is the contained control, 

              // which does the 'real work'.

    int m_iProgressPane;   // Pane ordinal where the progress 

              // bar resides, or -1 when off.

您显示进度条、位图或动画的窗格会被它们隐藏,因此如果其中有文本,当您销毁进度条时,它将被显示出来。

注意事项

正如 Erik Johnson 友善地指出的那样,如果显示了进度条,并且用户打开了一个菜单,然后开始使用左右键浏览菜单,进度条似乎会闪烁。这是处理这些按键方式的结果:状态栏在离开菜单时进入多窗格模式,然后在进入下一个菜单时进入单窗格模式。
在 Internet Explorer 中加载页面时,这种行为可以重现,特别是如果您的*收藏夹*文件夹相当大的话。
要避免这种情况,只能修改 WTL::CFrameWindowImpl。为了最小化这种情况,请考虑不要过于频繁地调用 ProgSetPos():例如,在一个 50 像素宽的状态栏中更新 500 次是没有意义的。

添加位图

如果位图是您的选择,**一行代码**即可完成

m_stat.SetBitmap(int nPane, HBITMAP hb, bool bManage)

正如您所料,参数是所需窗格的序号、您的位图的句柄以及一个 bool 值,该值告诉类是否希望它在完成后销毁位图。您还可以调用 DestroyBitmap(int iPane) 来销毁显示位图的静态窗口。

有些读者可能会想:“嘿,等一下!如果我必须自己编写加载位图的代码,那将不止一行代码!”是的,您说得对。因此,该函数有一个重载版本,其第二个参数是位图的资源 ID。这个版本不接受 bool,因为您无法访问 HBITMAP

注意事项

位图会被 CStatic 拉伸或压缩,直到达到适合窗格的大小。这意味着,如果您加载的位图相对于其窗格过大,或者比例差异很大,它可能会看起来像被大象踩踏过的青蛙。(本文写作过程中没有动物受到伤害。)

添加动画

如果您的状态栏成员变量继承自 CAnimationInPaneImpl,您可以通过调用以下函数在其中显示 AVI 文件或资源:

AnimCreate(int iPane, ATL::_U_STRINGorID FileName, 
  DWORD dwStyle,  DWORD dwExStyle)
//  The last two parameters have reasonable defaults

要隐藏动画,请使用 AnimDestroyWindow(void)

ATL::_U_STRINGorID 是一个 ATL 辅助类,它在构造函数中接受字符串(包含文件名)或整数(包含资源的 ID),从而使以其为参数的函数更加通用。

注意事项

动画控件在创建时不会自动调整到传递给它的矩形大小,因此您需要一个大小合适的 AVI 文件(或资源)。示例应用程序中显示的动画(由 Microsoft 与 Visual Studio 一起提供)展示了事物可能变得多么丑陋,但如果您自己构建 AVI,则可以获得很多收益。据我模糊的记忆,动画控件本身对它愿意处理的动画格式相当挑剔:实际上只支持未压缩的 AVI,所以不要尝试在那里放置 GIF!

添加任何具有 HWND 的控件。

A status bar with a modeless dialog in its third pane

继承自 CAnythingInPaneImpl 的类(目前只有 CMPSBarWithAll)允许您在窗格中放置控件或无模式对话框。作为用户,我更喜欢在 rebar 带中看到交互式控件,这是我们大多数人习惯的,但选择权在您。

**一行代码**,调用 AddAny(),即可完成。

    // Add a window to the status bar, in a pane chosen by ordinal.

    BOOL AddAny(int iPane, // Zero based ordinal 

            // (not resource ID) of the chosen pane.

            HWND hw, // Handle to a window, whatever control 

                //you like (or a dialog of yours)...

            bool bManage = false) // If true, DestroyWindow() 

                     // will be called on the HWND.

在示例应用程序中,有一个无模式对话框,其中包含一个 WTL::CHyperLink,它根据组合框中的选择,指向本文或“作者的文章”页面。
至少,这可能有助于提高点击量……

添加以上任意组合。

  • CMPSBarWithProgress 使用 WTL::CProgressBarCtrlCMultiPaneStatusBarCtrl 添加了进度条支持。
  • CMPSBarWithBitmaps 使用 WTL::CStaticCMultiPaneStatusBarCtrl 添加了位图(最多十五个)支持。
  • CMPSBarWithProgressAndBMPCMultiPaneStatusBarCtrl 添加了进度条和位图支持。
  • CMPSBarWithAnimation 使用 WTL::CAnimateCtrlCMultiPaneStatusBarCtrl 添加了 AVI 动画支持。
  • CMPSBarWithPrg_BMP_AnimCMultiPaneStatusBarCtrl 添加了进度条、位图和动画支持。
  • CMPSBarWithAllCMultiPaneStatusBarCtrl 添加了进度条、位图、动画和“任何具有 HWND 的控件”支持。

如果您读到这里,您很容易猜到,如果实现了 MultiPaneStatusBarEx.h 中的其他类,它们会提供什么样的扩展。要做的事情太多,时间太少!

动态添加和删除窗格。

如果您阅读了 CExtStatusControlBar - 管理状态栏窗格越来越容易,您可能会问:嘿,我们如何让用户添加和删除窗格?MFC 用户可以做到!

WTL::CMultiPaneStatusBarCtrlImpl<class T, class TBase = CStatusBarCtrl>::SetPanes(int* pPanes, int nPanes, bool bSetText = true) 实现所需的功能,因此这里不再重复。您只需将 ID 数组放在手边。只需清理右侧添加/删除的窗格的所有内容,然后稍后恢复。那里可能还有其他有用的函数,如 GetPaneTextLength()SetPaneWidth()GetPaneIndexFromID(),它们都由 WTL 提供。

示例应用程序

屏幕显示

A status bar with a distorted icon in its second pane
and amodeless dialog in the third

在第一个窗格中,您可以看到应用程序的图标,将其放大使其看起来更大。您甚至可以看到其中的“A、T、L”。第二个窗格显示一个被拉伸的图标,以至于原本写在上面的“icon”一词完全模糊了,这说明了为什么我们需要位图。

第三个窗格包含一个无模式对话框,带有一个组合框和一个超链接。在组合框中,您可以选择链接指向的位置:本文,或“作者的文章”页面。
您可以在第四个窗格中看到一个位图,从四个随机选择的位图中选取,它们被添加为项目资源。如果您下载示例项目并仔细查看图片,您会注意到其中一张(此处显示)是一辆公共汽车的图像,当它被压缩到一个窗格时,它会被压得面目全非。即使是下面图片中显示的“WTL rocks”位图看起来也不是很好。最后一个窗格包含另一个图标,被扭曲以使其看起来更宽,但仍然可读。

A status bar with an animation in its second pane, 
a progress bar in the third,
and a bitmap in the fourth

为了使一切完整,您可以在第二个窗格中看到一个动画(这个动画随 Visual Studio 一起提供),并在第三个窗格中看到一个进度条。

使用应用程序

在对话框中,您可以设置 WTL::CProgressBarCtrl 的下限和上限以及选定的窗格,它将在计时器上运行。为了增加一些趣味性,每当显示进度条时,左侧窗格中会播放一个 AVI 动画。AVI 文件名为 THIS.AVI,假定它与应用程序位于同一目录。这让我在无需重新编译的情况下轻松测试了几个 AVI 文件。

在第三个窗格中显示的无模式对话框可用于访问本文或作者的文章列表。您可以在组合框中选择目的地。
在状态栏中放置交互式控件不是我喜欢的,因为在我看来,只读控件在那里看起来更好,但如果 MFC 程序员可以做到(您是否阅读了 1 月份的*《月度文章》*?它真的很棒!),WTL 程序员也应该能够做到。

设计

为了共享属于框架的状态栏,我只是通过指针传递给视图。有点粗糙,但有效,因为这个程序的目的是不展示类解耦。

示例应用程序绝非设计杰作,它只是展示了我希望您觉得有用的几个类,就像我在 CodeProject 的许多文章中找到的那些一样。

参考文献

历史

  • 2004 年 3 月:创建。
  • 2004 年 4 月:添加了对 WTL 7.0 的支持(当时 _U_STRINGorID 在 WTL 命名空间下)
    修复了 CMPSBarWithProgressAndBMP 消息映射中引用错误类的 bug。
© . All rights reserved.