天天看点

IContextMenu第十一部分:组合扩展的实现

现在,我们已经有两个菜单处理器了,一个是来自外壳的上下文菜单,另一个是我们自定义的菜单(包含两个自定义菜单项),下面我们看看如何将它们组合在一起。具体来说,我们会使用到一个组合上下文处理器来实现。

组合上下文菜单的核心思想是将多个上下文菜单组合到一个处理器中,并使用菜单标识符的偏移来对菜单项进行路由和定位。

请看下面的代码。

IContextMenu第十一部分:组合扩展的实现
IContextMenu第十一部分:组合扩展的实现

在上面的代码中,我们使用到了一个结构体CONTEXTMENUINFO,它包含了菜单中的一些信息。我们不仅需要上下文菜单指针,而且也需要被

IContextMenu::QueryContextMenu使用到的菜单项的数量。

下面的代码演示了我们如何实现这个类。

IContextMenu第十一部分:组合扩展的实现

因为对于一个C++对象来说,它的构造函数不会失败,因此在构造过程中如何处理失败有多种设计惯例。我在这里使用的一个设计是,将大部分工作放在Initialize方法中,如果初始化失败,该方法可以返回适当的错误代码。(请注意,这里我假设new操作符不会抛出异常)

我们的初始化函数分配了一堆CONTEXTMENUINFO结构并复制IContextMenu指针以妥善保管。(注意 m_ccmi 成员直到我们知道内存分配成功后才会设置。)

下面是类的析构函数实现:

IContextMenu第十一部分:组合扩展的实现

接下来是Create方法的实现:

IContextMenu第十一部分:组合扩展的实现

因为我们的接口继承自IUnknown接口,所以还需要实现IUnknown接口的三个方法,如下图所示:

IContextMenu第十一部分:组合扩展的实现

接下来,是一个我们比较感兴趣的方法实现:

IContextMenu::QueryContextMenu。

IContextMenu第十一部分:组合扩展的实现

我们依次要求每个包含的上下文菜单将其命令添加到上下文菜单中。你可以在此处看到

IContextMenu::QueryContextMenu 方法返回值的原因之一。 通过告诉容器使用了多少个菜单标识符,容器知道还剩下多少给其他人使用。 然后容器返回所有上下文菜单使用的菜单标识符总数。

IContextMenu::QueryContextMenu 方法返回值的解释请看下面的帮助函数:

IContextMenu第十一部分:组合扩展的实现

此方法采用菜单偏移量并计算出它属于哪个包含的上下文菜单,使用来自

IContextMenu::QueryContextMenu 的返回值来决定如何划分标识符空间。 pidCmd 参数是输入/输出。 进入函数时,它是复合上下文菜单的菜单偏移量; 退出函数时,它是包含的上下文菜单的菜单偏移量,通过 ppcmi 参数返回。

IContextMenu::InvokeCommand 可能是最复杂的,因为它需要支持四种不同的命令调度方式,请看下图:

IContextMenu第十一部分:组合扩展的实现

经过一些初步调整后我们找到了命令标识符,然后我们分三个步骤进行调用。

首先,如果命令是作为字符串发送的,那么这是最简单的情况。我们遍历所有包含的上下文菜单,询问每个菜单是否识别命令。一旦确定,我们就可以执行对应的操作了。如果没有人知道,那么我们耸耸肩说我们也不知道。

其次,如果分派的命令是序数,我们要求 ReduceOrdinal 找出它属于哪个包含的上下文菜单处理程序。

第三,我们重写了 CMINVOKECOMMANDINFO 结构,使其适用于包含的上下文菜单处理程序。这意味着更改 lpVerb 成员和可能的 lpVerbW 成员以包含相对于包含的上下文菜单处理程序而不是相对于容器的新菜单偏移。由于 Unicode 动词 lpVerbW 可能不存在,因此这会稍微复杂一些。我们将其隐藏在 pszVerbWFake 局部变量后面,如果没有真正的 lpVerbW,它就会出现。

好的,现在你已经了解了将方法调用分发到相应包含的上下文菜单背后的基本思想,其余的应该相对容易一些了。

下面是GetCommandString的参考实现:

IContextMenu第十一部分:组合扩展的实现

GetCommandString 方法遵循与 InvokeCommand 相同的三步模式。

首先,通过调用每个包含的上下文菜单处理程序来分派任何基于字符串的命令,直到有人接受它。 如果没有人这样做,则拒绝该命令。 (注意 GCS_VALIDATE 的特殊处理,它需要 S_FALSE 而不是错误代码。)

其次,如果命令由 ordinal 指定,则要求 ReduceOrdinal 找出它属于哪个包含的上下文菜单处理程序。

第三,将缩减的命令传递给适用的包含上下文菜单处理程序。

最后的方法通过一个小的帮助函数来实现:

IContextMenu第十一部分:组合扩展的实现

此辅助函数采用 IContextMenu 接口指针并尝试调用

IContextMenu3::HandleMenuMsg2; 如果失败,则尝试 IContextMenu2::HandleMenuMsg; 如果这也失败了,那么它就放弃了。

有了这个辅助函数,最后两个方法小菜一碟。

IContextMenu第十一部分:组合扩展的实现

IContextMenu2::HandleMenuMsg 方法只是 IContextMenu3::HandleMenuMsg2 方法的转发器:

IContextMenu第十一部分:组合扩展的实现

而IContextMenu3::HandleMenuMsg2 方法只是遍历上下文菜单处理程序列表,询问每个处理程序是否希望处理该命令,并在最终执行时停止。

有了这个复合菜单类,我们可以在我们的示例程序中通过将“真实”上下文菜单与我们的 CTopContextMenu 组合在一起来展示它,从而展示如何将多个上下文菜单组合成一个大的上下文菜单,如下图所示:

IContextMenu第十一部分:组合扩展的实现

此函数通过创建两个包含的上下文菜单处理程序来构建复合,然后创建一个包含它们的复合上下文菜单。 我们可以通过对我们上次调整的 OnContextMenu 函数进行相同的一行调整来使用这个函数:

IContextMenu第十一部分:组合扩展的实现

请注意,使用此复合上下文菜单,我们在窗口标题中更新的菜单帮助文本跨越原始文件上下文菜单和“顶部”上下文菜单。来自任何一方的命令也被成功调用。

与第九部分中的方法相比,此方法的价值在于你不再需要在两段代码之间协调上下文菜单的自定义。根据之前的技术,你必须确保更新菜单帮助文本的代码与添加自定义命令的代码同步。

在新方法下,所有自定义都保存在一个地方(在复合上下文菜单内的“顶部”上下文菜单中),因此窗口过程不需要知道发生了哪些自定义。如果有多个显示上下文菜单的点,一些未自定义,另一些以不同方式自定义,则会变得更有价值。

好的,我认为现在在上下文菜单这个主题已经讲得差不多了。我希望你已经更好地了解它们的工作原理,如何利用它们,最重要的是,如何使用组合等技术对它们执行元操作(meta-operations)。

你还可以使用上下文菜单执行其他一些操作,你可以自行进行研究。例如,可以使用

IContextMenu::GetCommandString 方法遍历菜单并为每个项目获取与语言无关的命令。如果你想删除“删除”选项,这很方便:可以查找与语言无关的名称为“删除”的命令。当用户更改语言时,此名称不会更改;它将永远是英文的。

正如我们之前所注意到的,你需要注意许多上下文菜单处理程序并没有完全实现

IContextMenu::GetCommandString 方法,因此可能会有一些你根本无法获得名称的命令。这个要特别注意。

总结

总算是完成这个系列了,我可以去开一瓶香槟休息一下了。

感谢各位老哥的一路陪伴。

最后

最近我写了个东西

继续阅读