本文根据《Windows Shell扩展编程完全指南
来源:互联网 发布:js 监听动画结束 编辑:程序博客网 时间:2024/06/05 00:32
(本文根据《Windows Shell扩展编程完全指南》改写) 开始编写上下文菜单 – 它该做些什么? 在ATL Object Wizard里, 第一页默认已经选择了 Simple Object , 所以单击 Next 即可. HRESULT IShellExtInit::Initialize ( LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hProgID );
开头先让我们做简单一些, 只弹出一个对话框以表明当前的扩展能够正常地工作.
我们把扩展关联到 .TXT 文件, 因此当用户右键单击文本文件对象时扩展就会被调用.
使用 AppWizard 开始
好吧, 让我们开始吧! 什么? 我还没告诉你怎样使用那些神秘的 shell 扩展接口?
别着急, 我会边进行边解释的。
我觉得先解释一下一个概念再紧接着说明示例代码,对理解例子程序会更简单一些. 当然我也可以把所有的东西都先解释完,然后再解释代码, 但我觉得这样做不能吸引人的注意力。不管怎么样, 向 VC开火,开始!
运行AppWizard,生成一个名为SimpleExt 的 ATL COM 工程. 保留所有默认的设置选项,点击”完成”.
现在我们已经有了一个空的 ATL工程,它可以编译并生成一个 DLL, 但我们还需要添加Shell扩展的 COM 对象.
在 ClassView 中, 右击 SimpleExt classes 条目, 选择 New ATL Object.
在第二页中, 在Short Name 文本框里输入 SimpleShlExt ,点击 OK. (其余的文本框会自动填充完.)
这样就创建了一个名为 CSimpleShlExt 的类,其包含了实现COM对象最基本的代码. 我们将在这个类中加入我们自己的代码.
初始化接口
当我们的shell扩展被加载时, Explorer 将调用我们所实现的COM对象的 QueryInterface() 函数以取得一个 IShellExtInit 接口指针.
该接口仅有一个方法 Initialize(), 其函数原型为:
Explorer 使用该方法传递给我们各种各样的信息.
PidlFolder是用户所选择操作的文件所在的文件夹的 PIDL 变量. (一个 PIDL [指向ID 列表的指针] 是一个数据结构,它唯一地标识了在Shell命名空间的任何对象, 一个Shell命名空间中的对象可以是也可以不是真实的文件系统中的对象.)
pDataObj 是一个 IDataObject 接口指针,通过它我们可以获取用户所选择操作的文件名。
hProgID 是一个HKEY 注册表键变量,可以用它获取我们的DLL的注册数据.
在这个简单的扩展例子中, 我们将只使用到 pDataObj 参数.
要添加这个接口进 COM 对象, 先打开SimpleShlExt.h 文件, 然后加入下列标红的代码:
#include "shlobj.h"
#include "comdef.h"
class ATL_NO_VTABLE CSimpleShlExt :
public CComObjectRootEx,
public CComCoClass,
public IDispatchImpl,
public IShellExtInit
BEGIN_COM_MAP(CSimpleShlExt)
COM_INTERFACE_ENTRY(ISimpleShlExt)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IShellExtInit)
END_COM_MAP()
COM_MAP是ATL实现 QueryInterface()机制的宏,它包含的列表告诉ATL其它外部程序用QueryInterface()能从我们的 COM对象获取哪些接口.
接着,在类声明里, 加入Initialize()的函数原型.
另外我们需要一个变量来保存文件名:
protected:
TCHAR m_szFile [MAX_PATH];
public:
// IShellExtInit
STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY);
然后, 在 SimpleShlExt.cpp 文件中, 加入该函数方法的实现定义:
HRESULT CSimpleShlExt::Initialize ( LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hProgID )
我们要做的是取得当前鼠标所在的窗口,并把它和桌面上的ListView
做比较,如果二者不同,则鼠标是在其他Dictionary上点击,不添加
菜单,直接返回:
{
HWND Wnd;
Wnd=::GetDesktopWindow();
Wnd=FindWindowEx(Wnd, 0, "Progman", NULL);
Wnd = ::FindWindowEx(Wnd, 0, "SHELLDLL_DefView", NULL);
Wnd = ::FindWindowEx(Wnd, 0, "SysListView32", NULL);
POINT Point;
::GetCursorPos(&Point);
if(::WindowFromPoint(Point)!=Wnd)
return E_INVALIDARG;
return S_OK;
}
要是我们返回 E_INVALIDARG, Explorer 将不会继续调用以后的扩展代码.
要是返回 S_OK, Explorer 将再一次调用QueryInterface() 获取另一个我们下面就要添加的接口指针: IContextMenu.
与上下文菜单交互的接口
一旦 Explorer 初始化了扩展,它就会接着调用 IContextMenu 的方法让我们添加菜单项, 提供状态栏上的提示, 并响应执行用户的选择.
添加IContextMenu 接口到Shell扩展类似于上面IshellExtInit接口的添加 .打开 SimpleShlExt.h,添加下列标红的代码:
class ATL_NO_VTABLE CSimpleShlExt :
public CComObjectRootEx,
public CComCoClass,
public IDispatchImpl,
public IShellExtInit,
public IContextMenu
{
BEGIN_COM_MAP(CSimpleShlExt)
COM_INTERFACE_ENTRY(ISimpleShlExt)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IShellExtInit)
COM_INTERFACE_ENTRY(IContextMenu)
END_COM_MAP()
添加 IContextMenu 方法的函数原型:
public:
// IContextMenu
STDMETHOD(GetCommandString)(UINT, UINT, UINT*, LPSTR, UINT);
STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO);
STDMETHOD(QueryContextMenu)(HMENU, UINT, UINT, UINT, UINT);
修改上下文菜单 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("透明图标");
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(). 当用户点击我们添加的菜单项时该方法将被调用. 其函数原型是:
HRESULT IContextMenu::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo );
CMINVOKECOMMANDINFO 结构带有大量的信息, 但我们只关心 lpVerb 和 hwnd 这两个成员.
lpVerb参数有两个作用 – 它或是可被激发的verb(动作)名, 或是被点击的菜单项的索引值.
hwnd 是用户激活我们的菜单扩展时所在的浏览器窗口的句柄.
因为我们只有一个扩展的菜单项, 我们只要检查lpVerb 参数, 如果其值为0, 我们可以认定我们的菜单项被点击了.
我能想到的最简单的代码就是弹出一个信息框, 这里的代码也就做了这么多. 信息框显示所选的文件的文件名以证实代码正确地工作.
HRESULT CSimpleShlExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )
{
// 如果lpVerb 实际指向一个字符串, 忽略此次调用并退出.
if ( 0 != HIWORD( pCmdInfo->lpVerb ))
{
return E_INVALIDARG;
}
// 点击的命令索引 – 在这里,唯一合法的索引为0.
switch ( LOWORD( pCmdInfo->lpVerb ))
{
case 0:
{
HWND Wnd;
Wnd=::GetDesktopWindow();
Wnd=FindWindowEx(Wnd, 0, "Progman", NULL);
Wnd = ::FindWindowEx(Wnd, 0, "SHELLDLL_DefView", NULL);
Wnd = ::FindWindowEx(Wnd, 0, "SysListView32", NULL);
::SendMessage(Wnd, LVM_SETTEXTBKCOLOR, 0, 0xffffffff);
::InvalidateRect(Wnd, NULL, TRUE);
return S_OK;
}
break;
default:
return E_INVALIDARG;
break;
}
}
注册Shell扩展
现在我们已经实现了所有需要的COM接口. 可是我们怎样才能让浏览器使用我们的扩展呢?
ATL 自动生成注册COM DLL服务器的代码, 但这只是让其它程序可以使用我们的DLL.
最后,在shell版本 4.71+中, 你可以让上下文菜单在用户右击浏览器窗口(包括桌面)的背景时激发.
要让你的扩展在这种情况下被激发,需要在HKCR/Directory/Background/shellex/ContextMenuHandlers 键下进行注册.
使用该方法, 你可以添加定制菜单到桌面或任意目录上下文菜单.
这时传送到 IShellExtInit::Initialize()的参数有些不同,所以我将在以后的文章中讲述这方面的内容.
- 本文根据《Windows Shell扩展编程完全指南
- vs2005下Windows Shell扩展编程完全指南例子一中的问题解决
- Windows Shell编程-第十五章.SHELL扩展
- Windows Shell 扩展编程 第十五章
- Windows Shell 扩展编程 第十五章
- Windows shell 扩展编程教程详解
- 系统托盘编程完全指南
- 系统托盘编程完全指南
- 系统托盘编程完全指南
- 系统托盘编程完全指南
- 系统托盘编程完全指南
- 系统托盘编程完全指南
- Windows Shell编程-第十六章.命名空间扩展
- Windows Shell编程-第十六章.命名空间扩展
- Shell扩展编程入门
- Shell扩展编程入门
- SHELL编程指南
- shell编程入门指南
- file2
- 国内免费CMS汇总
- GCC 使用手册和常用功能介绍
- 买书。。。
- SQL Server 2008 案例之微软IT部门
- 本文根据《Windows Shell扩展编程完全指南
- J2EE Java2平台企业版(Java 2 Platform,Enterprise Edition)
- file3
- GetAsyncKeyState 与 GetKeyState
- POJ 1137 The New Villa
- 接受
- SQL Server 2008案例之奥地利广播公司
- 北京海淀区集体户口办理结婚登记手续的注意事项
- 写点什么呢