修改上下文菜单 IContextMenu 有三个方法.
第一个是 QueryContextMenu(), 它让我们可以修改上下文菜单. 其原型为:
| HRESULT IContextMenu::QueryContextMenu ( HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags ); |
hmenu 上下文菜单句柄.
uMenuIndex 是我们应该添加菜单项的起始位置.
uidFirstCmd 和 uidLastCmd 是我们可以使用的菜单命令ID值的范围.
uFlags 标识了Explorer 调用QueryContextMenu()的原因,
这我以后会说到的.
而返回值根据你所查阅的文档的不同而不同.
Dino Esposito 的书中说返回值是你所添加的菜单项的个数.
而 VC6.0所带的MSDN 又说它是我们添加的最后一个菜单项的命令ID加上 1.
而最新的 MSDN 又说:
将返回值设为你为各菜单项分配的命令ID的最大差值,加上1.
例如, 假设 idCmdFirst 设为5,而你添加了三个菜单项 ,命令ID分别为 5, 7, 和 8.
这时返回值就应该是: MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1).
我是一直按 Dino 的解释来做的, 而且工作得很好.
实际上, 他的方法与最新的 MSDN 是一致的, 只要你严格地使用 uidFirstCmd作为第一个菜单项的ID,再对接续的菜单项ID每次加1.
我们暂时的扩展仅加入一个菜单项,所以 QueryContextMenu() 非常简单:
HRESULT CSimpleShlExt::QueryContextMenu ( HMENU hmenu,UINT uMenuIndex, UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags )
{
// 如果标志包含 CMF_DEFAULTONLY 我们不作任何事情.
if ( uFlags & CMF_DEFAULTONLY )
{
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );
}
InsertMenu ( hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, _T("SimpleShlExt Test Item") );
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 1 );
}
|
首先我们检查 uFlags.
你可以在 MSDN中找到所有标志的解释, 但对于上下文菜单扩展而言, 只有一个值是重要的: CMF_DEFAULTONLY.
该标志告诉Shell命名空间扩展保留默认的菜单项,这时我们的Shell扩展就不应该加入任何定制的菜单项,这也是为什么此时我们要返回 0 的原因.
如果该标志没有被设置, 我们就可以修改菜单了 (使用 hmenu 句柄), 并返回 1 告诉Shell我们添加了一个菜单项.
在状态栏上显示提示帮助
下一个要被调用的IContextMenu 方法是 GetCommandString(). 如果用户是在浏览器窗口中右击文本文件,或选中一个文本文件后单击文件菜单时,状态栏会显示提示帮助.
我们的 GetCommandString() 函数将返回一个帮助字符串供浏览器显示.
GetCommandString() 的原型是:
| HRESULT IContextMenu::GetCommandString ( UINT idCmd, UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax ); |
idCmd 是一个以0为基数的计数器,标识了哪个菜单项被选择.
因为我们只有一个菜单项, 所以idCmd 总是0. 但如果我们添加了3个菜单项, idCmd 可能是 0, 1, 或 2.
uFlags 是另一组标志(我以后会讨论到的).
PwReserved 可以被忽略.
pszName 指向一个由Shell拥有的缓冲区,我们将把帮助字符串拷贝进该缓冲区.
cchMax 是该缓冲区的大小.
返回值是S_OK 或 E_FAIL.
GetCommandString() 也可以被调用以获取菜单项的动作( "verb") .
verb 是个语言无关性字符串,它标识一个可以加于文件对象的操作。
ShellExecute()的文档中有详细的解释, 而有关verb的内容足以再写一篇文章, 简单的解释是:verb 可以直接列在注册表中(如 "open" 和 "print"等字符串), 也可以由上下文菜单扩展创建. 这样就可以通过调用ShellExecute()执行实现在Shell扩展中的代码.
不管怎样, 我说了这多只是为了解释清楚GetCommandString() 的作用.
如果 Explorer 要求一个帮助字符串,我们就提供给它. 如果 Explorer 要求一个verb, 我们就忽略它. 这就是 uFlags 参数的作用.
如果 uFlags 设置了GCS_HELPTEXT 位, 则 Explorer 是在要求帮助字符串. 而且如果 GCS_UNICODE 被设置, 我们就必须返回一个Unicode字符串.
我们的 GetCommandString() 如下:
#include "atlconv.h"
// 为使用 ATL 字符串转换宏而包含的头文件
HRESULT CSimpleShlExt::GetCommandString( UINT idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax )
{
USES_CONVERSION;
//检查 idCmd, 它必须是0,因为我们仅有一个添加的菜单项.
if ( 0 != idCmd )
return E_INVALIDARG;
// 如果 Explorer 要求帮助字符串,就将它拷贝到提供的缓冲区中.
if ( uFlags & GCS_HELPTEXT )
{
LPCTSTR szText = _T("This is the simple shell extension's help");
if ( uFlags & GCS_UNICODE )
{
// 我们需要将 pszName 转化为一个 Unicode 字符串, 接着使用Unicode字符串拷贝 API.
lstrcpynW ( (LPWSTR) pszName, T2CW(szText), cchMax );
}
else
{
// 使用 ANSI 字符串拷贝API 来返回帮助字符串.
lstrcpynA ( pszName, T2CA(szText), cchMax );
}
return S_OK;
}
return E_INVALIDARG;
}
|
这里没有什么特别的代码; 我用了硬编码的字符串并把它转换为相应的字符集.
如果你从未使用过ATL字符串转化宏,你一定要学一下,因为当你传递Unicode字符串到COM和OLE函数时,使用转化宏会很有帮助的.
我在上面的代码中使用了T2CW 和 T2CA 将TCHAR 字符串分别转化为Unicode 和 ANSI字符串.
函数开头处的USES_CONVERSION 宏其实声明了一个将被转化宏使用的局部变量.
要注意的一个问题是: lstrcpyn() 保证了目标字符串将以null为结束符.
这与C运行时(CRT)函 数strncpy()不同. 当要拷贝的源字符串的长度大于或等于cchMax 时 strncpy()不会添加一个 null 结束符.
我建议总使用lstrcpyn(), 这样你就不必在每一个strncpy()后加入检查保证字符 串以 null为结束符的代码.
执行用户的选择
IContextMenu 接口的最后一个方法是 InvokeCommand(). 当用户点击我们添加的菜单项时该方法将被调用. 其函数原型是: