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

ATL 停靠框架 (类似 VC.NET)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (10投票s)

2002年4月8日

13分钟阅读

viewsIcon

161669

downloadIcon

1716

ATL/WTL 中 .NET IDE 停靠窗口的完整实现

引言

促使我编写这个(相当庞大)项目的是 Jens Nilsson 关于可停靠窗口的文章。事实上,本文包含的演示项目基本上是 Jens 文章中演示项目的修改版。(希望我没有侵犯任何版权……我只是懒得写自己的演示。)

那么 Jens 的文章有什么问题呢?

Jens 创建的用户界面有一个特性让我很不爽。那就是,即使窗口处于自动隐藏状态,停靠的窗口实际上也会遮挡客户端窗口的部分区域。对我来说,这是不可接受的——如果我有一个控件,我希望用户能看到它,而它却被一个本应“隐藏”的窗口遮挡了,这根本就失去了意义。

我想,VC.NET 为这个问题提供了一个绝佳的解决方案——停靠条(snap bars),它们位于客户端区域的外部。所以,我决定模拟 VC.NET 的做法,但有一个主要区别。我不喜欢 VC.NET 实现的一部分是,你必须固定(pin)一个窗口才能将其拖动到另一个停靠条,或者将其浮动。这相当烦人,而且毫无意义。所以我改变了窗口的拖动行为,消除了这个限制。

请注意,这个用户界面实际上是为了我正在构建的一个项目而创建的(我想把它放在这里,可以小小地回报一下我从 codeproject 上用过的所有代码 :-)) 这意味着,虽然某些设计/UI 决策可能完全适用于我的项目,但它们可能不太适用于你们。我已经尽力使这个框架尽可能通用,但如果仍然存在任何瑕疵,请通知我,我会进行修正。

最后,这是我的第一篇文章。所以请原谅任何不一致/问题——我会尽力在它们出现时纠正。

这个框架能为你做什么

  • 一个完整的解决方案,用于在一款应用程序中管理超过 3 个窗口。如果你注意到,窗口菜单并不是一个很好的切换窗口的解决方案,停靠(docking)也不是(你能一次停靠 16 个窗口吗?)目前来看,停靠(snapping)似乎是唯一的办法。
  • 一个完整、易于使用的解决方案。你将窗口句柄交给框架,然后就可以不管它了。框架将负责窗口的销毁、大小调整、激活等操作。
  • 支持停靠(snapped)、停靠(docked)和浮动(floating)窗口。也就是说,用户可以在这三者之间切换。
  • 支持使用注册表加载/保存窗口状态。
  • 标准的窗口停靠行为(由 James R. Twine 提出)
  • 可选的窗口动画。(取代普通的显示/隐藏)。智能地仅在 2000/XP 上工作(ME?不确定),而在其他系统上默认为显示/隐藏,这要归功于 Bjarke Viksoe。智能地从适当的一侧滑动。(感谢 Simon Steele 的建议。)
  • 处理多线程 UI——即,在需要时调用 `AttachThreadInput`,并在不需要时取消调用。

警告

这段代码超过 3000 行,全部在一个 .h 文件中。这意味着几件事。一,你会对一个 120KB 以上的 .h 文件感到厌烦,并想知道我为什么不把代码分成许多不同的文件。答案:方便。这样你只需要包含一个文件。我不知道,虽然我讨厌将实现放在 .h 文件中,但我喜欢在我想使用的第三方项目中看到它。这意味着我无需修改我的项目文件,并且代码是方便地自包含的。

你还会注意到,我所有的类都嵌套在主类中。这是出于同样的原因——我不想用你不在乎的内部类弄乱 ClassView 窗口。我想这就是类嵌套的用途。但是,我没有嵌套第三方类——将它们放入我的 .h 文件已经足够了,我不想把它们据为己有 :)

最后,由于代码量如此之大,我几乎可以保证其中会有 bug。我不是比尔·盖茨(嘿嘿),写这么多代码而不产生一些 bug 是不可能的。虽然我在 WinXP 上对代码进行了压力测试并修复了所有我找到的 bug,但我没有在任何其他平台上进行过测试。(主要是因为我目前无法方便地访问其他平台。)

更新:我在 Win98 上进行了测试,它确实不起作用——好吧,垂直文本不起作用,这基本上毁了整个外观;不过其他一切似乎都可以正常工作。我也没经历过行业代码所经历的 QA 流程——我想那是你们这些用户的功劳。我认为这就是代码共享网站的好处——网站用户获得现成的代码,开发者获得免费的 QA。

总之,请自行承担风险。

用法

使用这个框架非常简单。你只需要从 `CSnapFramework` 类派生,如下所示:

class CUitest3View : public CWindowImpl< CUitest3View >, public CSnapFramework< CUitest3View >

如果你想使用窗口的保存/加载功能,你只需要用一个注册表键初始化停靠框架类,如下所示:

CUitest3View() : CSnapFramework<CUitest3View>("SOFTWARE\\<your registry key here>")

停靠框架将在你指定的键下创建一个子键,名为 *WindowPositions*(在 HKCU 中)。

完成此操作后,请确保正确地链式调用消息映射,如下所示:

BEGIN_MSG_MAP(CUitest3View)
CHAIN_MSG_MAP(CSnapFramework< CUitest3View >)
... your messages go here....
END_MSG_MAP()
        

请注意,如果你想处理一些销毁相关的工作,可以将 `OnDestroy` 处理程序放在 `CHAIN_MSG_MAP` 之上,只需确保将 bHandled 设置为 `FALSE`,以便框架有机会处理 `OnDestroy`。(框架本身也会将 bHandled 设置为 false,以防你的 `OnDestroy` 在 `CHAIN_MSG_MAP` 之后。)

要将一个窗口添加到停靠框架,请执行此操作:

// ok this dude attaches a new window with the specified 

// options to the framework. 

// Arguments:

//                 hWnd: duh

//                state: how you want the window attached (see enum above)

//              szSize : the dimensions of the window you want

//                       notice that if the window is docked top 

//                       or bottom, the cx will be ignored,

//                       likewise, if the window is docked left 

//                       or right, the cy will be ignored.

//    bHasCloseCheckBox: can this window be closed by the user?

//ptFloatingWinPosition: the initial position of the floating window. 

//                       Leave at 0,0 to have the floating window start

//                       at the upper left hand corner of the client

//                bLoad: Do we want to load settings from the registry 

//                       for this window? (If so, you must've initialized

//                       the framework with a registry key for 

//                       this to work....

void        AttachWindow(IN HWND hWnd, IN eWinState state, IN CSize szSize, 
                            IN bool bHasCloseCheckBox, 
                            IN CPoint ptFloatingWinPosition = CPoint(0,0), 
                            IN bool bLoad = true);
		

最后,如果你想要窗口动画(你知道的,窗口凭空出现那种),请调用此函数:

// This guy turns on/off the animation for the snapping windows. 

// (Floaters do NOT use animation.) 

//    Arguments:

//      dwFlags:    Animation flags. Look at help for 

//                  AnimateWindow for list of values

//                  (do NOT use AW_HIDE as one of the flags,

//                  I'll remove it anyway.)

//       dwTime:    Duration of the animation. 200ms is default.

void        SetWindowAnimation(DWORD dwFlags = AW_CENTER, DWORD dwTime = 200)
	

就是这样。我没有提供 `RemoveWindow` 函数——在我看来(可能我一厢情愿),没有人会想要它。在我的项目中,我肯定不需要。

有件事需要记住。不要从顶层窗口派生 `CSnapFramework`。什么?好吧,这是怎么回事。假设你有一个 SDI 应用程序。你不想让 MainFrame 窗口从这个框架派生。你想让 View 从它派生。为什么?两个原因:

  1. 对开发者来说更方便:客户端会自动考虑主框架可能拥有的任何工具栏/菜单/等,因此通过让 View 从框架派生,我可以确保不破坏任何人的非客户端区域。
  2. 对用户来说更方便。让 View 派生意味着你可以让任何 CWindow 派生的窗口从这个框架派生。也就是说,它可以任意嵌套,按照你觉得方便的方式。这让你对放置框架有更多控制。

实现

这个框架中有三个主要类。我将从下往上解释它们。请原谅实现细节的草率之处,更多信息请查看源代码。我已经尽力对其进行了详细注释。

  1. CSnapWindow。这是一个基于状态的类,这意味着该类嵌套了三个从基类派生的状态类。状态类是 `CSnapWindowStateAuto`、`CSnapWindowStateFloating` 和 `CSnapWindowStatePinned`。窗口将收到的所有消息传递给当前状态,然后由状态执行必要的操作。啊,拥有设计模式难道不是很美好吗?
  2. CSnapBarWindow。这个类是停靠条(snapbar)的实现。它包含一个属于该停靠条的 `CSnapWindow` 实例的 `std::list`。它知道如何绘制自己,并且在特定 `CSnapWindow` 的帮助下进行窗口矩形计算和绘制。处理部分窗口跟踪。
  3. CSnapFramework。嵌套了前面提到的所有类。处理 `WM_NCSIZE` 来控制客户端区域,以及 `CSnapBarWindow` 的放置计算。还处理拖动、动画以及窗口跟踪的主要部分。

请注意,`CMouseHover` 存在于这个类层次结构之外。这是因为它不是我的类(它是 Bjarke 的——参见致谢),而且我认为其他人可能想在他们的代码中使用它。我想我本可以用 **public** 权限将其嵌套起来,但我不想混淆我的代码和 Bjarke 的。

要求

你需要安装 WTL7Microsoft Platform SDK 才能编译演示和框架。我现在已经成功地用 VC6 和 VC7 编译了代码。代码在 Win98 上不能正常工作,因为出于某种原因 Win98 不想绘制垂直文本。也许代码有问题……我不确定,因为我似乎找不到声称 Win98 不支持垂直文本的文档……算了。它应该在 WinME 上正常工作,当然,在 Win2000 和 XP 上也可以。

待办事项

  1. 停靠到条时显示窗口大小轮廓——有点像 VC.NET,我想——当你将一个窗口停靠到一个停靠条时,它会显示该窗口将占用的屏幕空间。它是通过 dragrects 来实现的。我认为这值得去做,只是还没时间做。
  2. [可能不会?] 停靠条图标,也像 VC.NET 那样。我实际上喜欢隐藏窗口文本的事实,所以你只看到一个图标——我认为这有点令人困惑,但有图标在那里肯定会让它看起来更好。
  3. 让这段代码在 Win98/95 下工作。基本上,我用 Win98 测试过,唯一不起作用的是垂直文本。我看到了两种解决方案。一种是使用 DC 旋转代码,有点像这个。另一种是使用 GdiPlus,我认为它适用于任何操作系统。(我错了?)我其实想使用 GdiPlus——我已经用过它了,它非常漂亮,但是将所有这些代码转换为 GdiPlus 的前景 somehow 并不吸引我……到现在为止。任何人都可以随意去做。:)

版本历史

  • 2002/4/8:最初的文章提交
  • 2002/4/9:文章修改,增加了要求、待办事项,并纠正了 Bjarke 的名字。(如果你拼错了别人的名字,那就失去了赞扬别人的目的……:)。
  • 2002/4/11 提前为这次相当……呃……狂热的发布频率道歉。我倾向于忘记小的细节……
    • 代码已修改,使其与 VC6 兼容/可编译。
    • 对大小调整窗口的百分比用法进行了相当大的修改。在对 UI 进行了一段时间的调整后,我发现百分比在很大程度上是反直觉的。
    • <bug 修复> 修正了 `::IsWindow` 的用法——我以为它返回 TRUE 或 FALSE,结果它返回非常大的非零值,或 false。没多大关系。
    • <bug 修复> 修正了将一个已停靠的窗口转换为固定(pinned)状态的大小计算。
    • <bug 修复> 修正了许多关于窗口一开始就处于浮动状态的问题。
    • <bug 修复> 修正了其他一些小问题。
    • 为停靠和固定窗口添加了大小限制。现在你无法将窗口调整到占据整个客户端区域。啊,`WM_GETMINMAXINFO` 的微妙乐趣 :).
  • 2002/4/15:处理了一些待办事项,并整合了一些请求的功能
    • 按住 Ctrl 键将在拖动窗口时禁用停靠-固定行为。
    • <bug 修复> 修正了当派生自 Framework 的类有 WM_DESTROY 处理程序时发生的问题。
    • <bug 修复> 修正了在连续取消固定两个固定窗口时发生的跟踪错误。(第二个窗口跟踪不正确)
    • <bug 修复> 修正了一个(用户创建的?)问题,该问题导致几个停靠窗口可以占据整个客户端区域。现在固定窗口将自动调整大小,以确保始终保留一些客户端区域。请注意,停靠窗口仍然可以遮挡整个客户端区域,但我认为这是用户需要处理的情况。
  • 2002/4/23:一些功能性更改
    • 添加了窗口动画
    • <bug 修复> 修正了启动时固定窗口大小的一个小问题
    • 添加了多线程 UI 行为——智能地调用 AttachThreadInput。
  • 2002/4/23:整合了一些建议,并修复了另一个 bug
    • 添加了智能窗口动画——即,如果你将 `AW_INTELLIGENT_SIDE` 标志作为动画标志(与 `AW_SLIDE` 组合)指定,窗口将从其停靠的一侧滑动出来,并在正确的一侧滑入。(感谢 **Simon Steele** 的建议。)
    • <bug 修复> 修正了糟糕的停靠行为:如果你弹出一个窗口,然后拖动另一个窗口,第一个窗口将永远保持弹出状态。气死我了。
    • 添加了行为:点击停靠条上的停靠窗口按钮将使它们出现。(感谢 **Simon Steele** 的建议。)
  • 2002/5/6:bug 修复
    • <bug 修复> 修复了焦点处理:当一个已停靠的窗口被隐藏时——如果该窗口的子窗口拥有焦点,我们将焦点重置为主客户端窗口。
    • <bug 修复> 将 `SendMessage(WM_PAINT)` 调用更改为 `RedrawWindow` 调用,感谢 Christian Andritzky 的建议。
    • <bug 修复> 修复了多线程 UI 处理中的一个拼写错误,该错误会导致 AttachThreadInput 在关闭时无法解除。
  • 2002/5/24:bug 修复
    • <bug 修复> 修正了浮动窗口的处理。它们现在不能再被主框架隐藏。
    • <bug 修复> 修正了停靠窗口的处理。有一个小 bug 导致它们被错误地隐藏。
    • UI 增强:改进了停靠窗口标题栏的外观。
    • UI 增强:改进了固定窗口抓手的外观。

致谢

如前所述,非常感谢 **Jens Nilsson** 的原创文章。我从中用了一些代码:

  • 完整使用了他提供的演示项目,当然除了断开他的停靠框架并连接我的。
  • 使用了他停靠框架中的一个函数来绘制图钉。代码中有说明。

使用了 **Bjarke Viksoe** 出色的 CMouseHover 类,并进行了一些小的修改。我相信在那里使用它符合 Bjarke 的版权。

还使用了 Bjarke 的 AnimateWindow 实现(实际上是 4 行代码,但功劳归功于它 :) 

说到版权

(你知道它要来了……)

代码是完全、彻底、绝对免费的。你可以随意在私人或商业应用程序中使用它,修改它,坐上去,或者打印出来用作卫生纸。老天知道我从 CodeProject 忽略了多少版权,以至于我敢制定自己的版权。:) 不过有一点——如果这段代码搞砸了你的电脑或者让你的项目延迟了七个月,那不是我的错。这些都不是我的错。甚至连那句“format c:”也不算。呃,开玩笑的。:) 哦,不用说,你不能声称这段代码是你自己的。(也就是说,我不想看到有人重印这段代码,然后为其加上版权。不确定这是否可能,但这会让人很不爽。非常不爽。)

P.S. 也许说这句很没必要,但如果你要给这篇文章打低分,至少花点时间写个评论,告诉我如何改进,或者你觉得哪里不足……

© . All rights reserved.