分享
 
 
 

Shell扩展编程实现Windows2000桌面图标透明

王朝vc·作者佚名  2006-01-31
窄屏简体版  字體: |||超大  

(本文根据《Windows Shell扩展编程完全指南》改写)

开始编写上下文菜单 – 它该做些什么?

开头先让我们做简单一些, 只弹出一个对话框以表明当前的扩展能够正常地工作.

我们把扩展关联到 .TXT 文件, 因此当用户右键单击文本文件对象时扩展就会被调用.

使用 AppWizard 开始

好吧, 让我们开始吧! 什么? 我还没告诉你怎样使用那些神秘的 shell 扩展接口?

别着急, 我会边进行边解释的。

我觉得先解释一下一个概念再紧接着说明示例代码,对理解例子程序会更简单一些. 当然我也可以把所有的东西都先解释完,然后再解释代码, 但我觉得这样做不能吸引人的注意力。不管怎么样, 向 VC开火,开始!

运行AppWizard,生成一个名为SimpleExt 的 ATL COM 工程. 保留所有默认的设置选项,点击”完成”.

现在我们已经有了一个空的 ATL工程,它可以编译并生成一个 DLL, 但我们还需要添加Shell扩展的 COM 对象.

在 ClassView 中, 右击 SimpleExt classes 条目, 选择 New ATL Object.

在ATL Object Wizard里, 第一页默认已经选择了 Simple Object , 所以单击 Next 即可.

在第二页中, 在Short Name 文本框里输入 SimpleShlExt ,点击 OK. (其余的文本框会自动填充完.)

这样就创建了一个名为 CSimpleShlExt 的类,其包含了实现COM对象最基本的代码. 我们将在这个类中加入我们自己的代码.

初始化接口

当我们的shell扩展被加载时, Explorer 将调用我们所实现的COM对象的 QueryInterface() 函数以取得一个 IShellExtInit 接口指针.

该接口仅有一个方法 Initialize(), 其函数原型为:

HRESULT IShellExtInit::Initialize ( LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hProgID );

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()的参数有些不同,所以我将在以后的文章中讲述这方面的内容.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有