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

创建用于在 Developer Studio IDE 中托管 GUI 的插件

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.81/5 (8投票s)

2002年6月6日

7分钟阅读

viewsIcon

92233

downloadIcon

805

一篇关于在 Microsoft Developer Studio 中托管自己的自定义窗口的文章。

Sample Image - DevStudioHost.gif

引言

本文提供了一种可能的解决方案,用于在 MS Developer Studio 6.x 中托管您自己的 GUI,作为 Developer Studio 插件,而不会使应用程序崩溃并与 WndTabs 等插件良好配合。

我看到几个人问如何做到这一点,而我自己也想这样做,以便托管我一直在使用 VCF 开发的可视化开发工具。因此,前一阵子我花了几个周末与 Developer Studio 搏斗,并提出了这个解决方案,最初使其适用于托管 VCF Builder,然后对其进行了一些重写,使项目更通用,以用于本文。

虽然这可能不是唯一的解决方案,但这是我能够完成以下任务的唯一方法

  • 作为 Developer Studio 插件,在 Developer Studio 内部托管一个窗口,可以将其他子窗口添加到该窗口中,并将其大小调整专门映射到用于 MDI 子窗口的区域。
  • 不依赖于通常进行 Developer Studio 集成所需的任何高级插件库。这些库的会员费非常昂贵(我记得我查阅时,一年的会员费约为 50,000 美元,至少需要 5 年),并且需要签署 NDA。
  • 与现有的 GUI 插件(如 WndTabs)协作。

最初的问题

最初我以为我可以简单地添加一个新的文档类型,因为 Developer Studio 基于 MFC Doc-View。不幸的是,这无法安全地完成。你可以让文档类型注册,甚至可能成功地让你的 MDI 子窗口出现,但是一旦你关闭它所附加的工作区,所有问题都会爆发,并且 Developer Studio 会崩溃。哎……

然后我想我可以耍点花招,捕获一个现有的 MDI 子窗口,然后简单地将我自己的窗口添加到视图中——再一次,虽然我能够将我的子窗口附加到视图中,但当我关闭 MDI 子窗口或工作区时——砰——Developer Studio 崩溃了。唉。

所以我想也许我可以将一个子窗口附加到 MDI 客户端窗口——所有创建的 MDI 子窗口的父窗口。我在一个简单的 MFC MDI 应用程序中测试了这一点,果然它运行良好,没有崩溃。太酷了,所以修修补补,我准备用 Developer Studio 进行测试。再一次,不行——窗口会正确显示,我可以控制它,但是当我关闭一个工作区时,事情又会崩溃。

上述崩溃的原因与没有安全扩展 IDE GUI 所需的特殊扩展库有关,例如添加自定义文档模板。由于这些库在没有加入上面提到的特殊 Developer Studio“俱乐部”的情况下是不可用的,因此无法做到这一点。

所以最后要尝试的是创建一个子窗口,它的父窗口不是 MDI 客户端,也不是另一个 MDI 子窗口,而是直接是 Developer Studio 主窗口本身,就像所有的工具栏停靠区一样。本质上与 Oz Solomonovich 使用他的 WndTabs 插件所做的事情相同(该死——这时候你是不是有点觉得自己很笨!)。然后这个宿主窗口可以被打开和关闭,让你轻松地从 Developer Studio MDI 界面切换到宿主视图。

一旦主机工作,你几乎可以做任何你想做的事情。你甚至可以在主机中嵌入一个 `CMDIFrameWnd` 并拥有你自己的 MDI 子窗口!如果你捕获 `IApplicationEvents` 接口暴露的事件,你可以收到大多数主要的 GUI 事件的通知,比如打开一个新项目,以及保存或创建一个新文档。有了这个,你甚至可以镜像许多已经存在的编辑器类型或创建新的编辑器类型。或者做一些完全不同的事情,比如托管一个很酷的 Win-Diff 显示,或者在这个里面托管一个很酷的 UML 建模工具。

宿主的工作原理

宿主通过对 Developer Studio 主窗口进行子类化,并设置一个 Windows Hook 过程来捕获某些消息。一旦 Hook 建立,该过程会捕获发送给 MDI 客户端窗口的 WM_SIZE 和 WM_MOVE 消息,并相应地调整我们的宿主窗口。详细的细节如下。

当我构建插件时,我使用了默认的 Developer Studio 插件向导,并接受了默认设置。一个插件有几个部分,第一个部分管理一些启动和关闭例程,是 CDSAddIn。当 Developer Studio 第一次初始化插件时,会调用 CDSAddIn::OnConnection() 方法,正是在这里,窗口子类化和消息 Hook 安装发生了。

为了对窗口进行子类化,我使用了我在 WndTabs 源代码中发现的一个名为 CSubClassWnd 的小类,我只稍微修改了一点。基本上,您创建一个实例并将其传递一个 HWND,然后它有另一个方法,您可以调用该方法来连接其余的子类化魔术,其中一部分通过调用 CWnd::SubclassWindow() 完成。通过对现有窗口句柄进行子类化,您可以访问 WndProc 并用您自己的消息处理程序替换或增强它。

因此,在 `CDSAddIn::OnConnection()` 方法中,我们存储当前的 CWinApp 的 hinstance 和 threadID。然后使用 `AFX_MANAGE_STATE` 宏来切换状态,以便我们的插件对其资源的访问能够正常工作。请注意,管理状态宏是在 hinstance 和 threadID 存储之后完成的——如果我们在此之前切换了状态,那么值将不同——我们想要的是 Developer Studio 应用程序的 hinstance 和 threadID。

接下来是获取活动窗口,然后沿父子链(使用 `GetParent()`)向上遍历,直到我们到达 Developer Studio 主窗口。然后使用主窗口句柄,我们创建我们的 `CDevStudioMainWnd` 实例。这个类是执行主窗口子类化魔术的部分,从它我们可以获取并保留 MDI 客户端窗口的句柄(这在 `CDevStudioMainWnd::GetMDIClientHWND()` 方法中实现)。

一旦我们有了 MDI 客户端句柄,我们就可以继续创建宿主视图——这是一个从 CView 派生出来的简单类。宿主视图被创建并作为父级附加到我们封装在 CDevStudioMainWnd 实例中的主窗口。

所有这些的最后一步是创建我们的窗口钩子——这是通过调用 SetWindowsHookEx() 并传入回调类型(在我们的例子中是 WH_CALLWNDPROC 用于通用窗口消息),我们的回调过程(在 CDSAddIn 代码中),以及我们之前存储的 hinstance 和 threadID 来完成的。现在,这将把这些消息路由到我们的回调以及它们所针对的常规 WndProc,并允许我们“偷窥”消息循环。一旦消息到达我们的回调,我们可以检查消息所针对的窗口句柄,如果它是 MDI 客户端句柄,并且消息类型是 WM_SIZE 或 WM_MOVE,我们只需调整我们的宿主视图的大小,瞧!,我们就有了一个在 Developer Studio 中工作的宿主子窗口!

所有这些的清理工作在 CDSAddIn 的 CDSAddIn::OnDisconnection() 中进行,其中删除了 CDevStudioMainWnd 实例并调用了 UnhookWindowsHookEx() 方法以分离我们的窗口钩子回调。

插件本身有两个命令,允许在 Developer Studio 环境之间切换,换句话说,使宿主视图不可见并显示正常的 MDI 客户端区域,切换到宿主环境,这会使宿主视图重新定位,使其位于 MDI 客户端区域之上并可见。

任何关于如何改进或进一步开发此功能的建议将不胜感激!

致谢

原始的 CSubClassWnd 代码由 Oz Solomonovich 完成,获取 MDI 客户端窗口句柄的技术也一样。

© . All rights reserved.