打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Shell扩展编程实现Windows2000桌面图标透明
Shell扩展编程实现Windows2000桌面图标透明
发布于:软件开发网 来源:互联网 作者:佚名 时间:2009-02-26 00:02


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

开始编写上下文菜单–它该做些什么?
开头先让我们做简单一些,只弹出一个对话框以表明当前的扩展能够正常地工作.
我们把扩展关联到.TXT文件,因此当用户右键单击文本文件对象时扩展就会被调用.

使用AppWizard开始
好吧,让我们开始吧!什么?我还没告诉你怎样使用那些神秘的shell扩展接口?
别着急,我会边进行边解释的。
我觉得先解释一下一个概念再紧接着说明示例代码,对理解例子程序会更简单一些.当然我也可以把所有的东西都先解释完,然后再解释代码,但我觉得这样做不能吸引人的注意力。不管怎么样,向VC开火,开始!

运行AppWizard,生成一个名为SimpleExt的ATLCOM工程.保留所有默认的设置选项,点击”完成”.
现在我们已经有了一个空的ATL工程,它可以编译并生成一个DLL,但我们还需要添加Shell扩展的COM对象.
在ClassView中,右击SimpleExtclasses条目,选择NewATLObject.

在ATLObjectWizard里,第一页默认已经选择了SimpleObject,所以单击Next即可.
在第二页中,在ShortName文本框里输入SimpleShlExt,点击OK.(其余的文本框会自动填充完.)
这样就创建了一个名为CSimpleShlExt的类,其包含了实现COM对象最基本的代码.我们将在这个类中加入我们自己的代码.

初始化接口
当我们的shell扩展被加载时,Explorer将调用我们所实现的COM对象的QueryInterface()函数以取得一个IShellExtInit接口指针.
该接口仅有一个方法Initialize(),其函数原型为:

HRESULTIShellExtInit::Initialize(LPCITEMIDLISTpidlFolder,LPDATAOBJECTpDataObj,HKEYhProgID);


Explorer使用该方法传递给我们各种各样的信息.
PidlFolder是用户所选择操作的文件所在的文件夹的PIDL变量.(一个PIDL[指向ID列表的指针]是一个数据结构,它唯一地标识了在Shell命名空间的任何对象,一个Shell命名空间中的对象可以是也可以不是真实的文件系统中的对象.)
pDataObj是一个IDataObject接口指针,通过它我们可以获取用户所选择操作的文件名。
hProgID是一个HKEY注册表键变量,可以用它获取我们的DLL的注册数据.
在这个简单的扩展例子中,我们将只使用到pDataObj参数.

要添加这个接口进COM对象,先打开SimpleShlExt.h文件,然后加入下列标红的代码:

#include"shlobj.h"
#include"comdef.h"
classATL_NO_VTABLECSimpleShlExt:
publicCComObjectRootEx,
publicCComCoClass,
publicIDispatchImpl,
publicIShellExtInit

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:
TCHARm_szFile[MAX_PATH];
public:
//IShellExtInit
STDMETHOD(Initialize)(LPCITEMIDLIST,LPDATAOBJECT,HKEY);

然后,在SimpleShlExt.cpp文件中,加入该函数方法的实现定义:

HRESULTCSimpleShlExt::Initialize(LPCITEMIDLISTpidlFolder,LPDATAOBJECTpDataObj,HKEYhProgID)

我们要做的是取得当前鼠标所在的窗口,并把它和桌面上的ListView

做比较,如果二者不同,则鼠标是在其他Dictionary上点击,不添加

菜单,直接返回:

{
HWNDWnd;

Wnd=::GetDesktopWindow();

Wnd=FindWindowEx(Wnd,0,"Progman",NULL);

Wnd=::FindWindowEx(Wnd,0,"SHELLDLL_DefView",NULL);

Wnd=::FindWindowEx(Wnd,0,"SysListView32",NULL);

POINTPoint;

::GetCursorPos(&Point);

if(::WindowFromPoint(Point)!=Wnd)

returnE_INVALIDARG;

returnS_OK;

}


要是我们返回E_INVALIDARG,Explorer将不会继续调用以后的扩展代码.
要是返回S_OK,Explorer将再一次调用QueryInterface()获取另一个我们下面就要添加的接口指针:IContextMenu.

与上下文菜单交互的接口

一旦Explorer初始化了扩展,它就会接着调用IContextMenu的方法让我们添加菜单项,提供状态栏上的提示,并响应执行用户的选择.

添加IContextMenu接口到Shell扩展类似于上面IshellExtInit接口的添加.打开SimpleShlExt.h,添加下列标红的代码:

classATL_NO_VTABLECSimpleShlExt:
publicCComObjectRootEx,
publicCComCoClass,
publicIDispatchImpl,
publicIShellExtInit,
publicIContextMenu
{
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(),它让我们可以修改上下文菜单.其原型为:

HRESULTIContextMenu::QueryContextMenu(HMENUhmenu,UINTuMenuIndex,UINTuidFirstCmd,UINTuidLastCmd,UINTuFlags);


hmenu上下文菜单句柄.
uMenuIndex是我们应该添加菜单项的起始位置.
uidFirstCmd和uidLastCmd是我们可以使用的菜单命令ID值的范围.
uFlags标识了Explorer调用QueryContextMenu()的原因,
这我以后会说到的.

而返回值根据你所查阅的文档的不同而不同.
DinoEsposito的书中说返回值是你所添加的菜单项的个数.
而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()非常简单:

HRESULTCSimpleShlExt::QueryContextMenu(HMENUhmenu,UINTuMenuIndex,
UINTuidFirstCmd,UINTuidLastCmd,UINTuFlags)

{         

     //如果标志包含CMF_DEFAULTONLY我们不作任何事情.

     if(uFlags&CMF_DEFAULTONLY)

      {

         returnMAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_NULL,0);

       }

             

       InsertMenu(hmenu,uMenuIndex,MF_BYPOSITION,uidFirstCmd,_T("SimpleShlExtTestItem"));

       returnMAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_NULL,1);

}


首先我们检查uFlags.
你可以在MSDN中找到所有标志的解释,但对于上下文菜单扩展而言,只有一个值是重要的:CMF_DEFAULTONLY.
该标志告诉Shell命名空间扩展保留默认的菜单项,这时我们的Shell扩展就不应该加入任何定制的菜单项,这也是为什么此时我们要返回0的原因.
如果该标志没有被设置,我们就可以修改菜单了(使用hmenu句柄),并返回1告诉Shell我们添加了一个菜单项.

在状态栏上显示提示帮助

下一个要被调用的IContextMenu方法是GetCommandString().如果用户是在浏览器窗口中右击文本文件,或选中一个文本文件后单击文件菜单时,状态栏会显示提示帮助.
我们的GetCommandString()函数将返回一个帮助字符串供浏览器显示.

GetCommandString()的原型是:

HRESULTIContextMenu::GetCommandString(UINTidCmd,UINTuFlags,UINT*pwReserved,LPSTRpszName,UINTcchMax);

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字符串转换宏而包含的头文件

             

HRESULTCSimpleShlExt::GetCommandString(UINTidCmd,UINTuFlags,
UINT*pwReserved,LPSTRpszName,UINTcchMax)

 {

USES_CONVERSION;

//检查idCmd,它必须是0,因为我们仅有一个添加的菜单项.

if(0!=idCmd)

returnE_INVALIDARG;

//如果Explorer要求帮助字符串,就将它拷贝到提供的缓冲区中.

if(uFlags&GCS_HELPTEXT)

{

LPCTSTRszText=_T("透明图标");

if(uFlags&GCS_UNICODE)

{

//我们需要将pszName转化为一个Unicode字符串,接着使用Unicode字符串拷贝API.

lstrcpynW((LPWSTR)pszName,T2CW(szText),cchMax);

}

else

{

//使用ANSI字符串拷贝API来返回帮助字符串.

lstrcpynA(pszName,T2CA(szText),cchMax);

}

returnS_OK;

}

returnE_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().当用户点击我们添加的菜单项时该方法将被调用.其函数原型是:

HRESULTIContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFOpCmdInfo);

CMINVOKECOMMANDINFO结构带有大量的信息,但我们只关心lpVerb和hwnd这两个成员.
lpVerb参数有两个作用–它或是可被激发的verb(动作)名,或是被点击的菜单项的索引值.
hwnd是用户激活我们的菜单扩展时所在的浏览器窗口的句柄.

因为我们只有一个扩展的菜单项,我们只要检查lpVerb参数,如果其值为0,我们可以认定我们的菜单项被点击了.
我能想到的最简单的代码就是弹出一个信息框,这里的代码也就做了这么多.信息框显示所选的文件的文件名以证实代码正确地工作.

HRESULTCSimpleShlExt::InvokeCommand(LPCMINVOKECOMMANDINFOpCmdInfo)

{

//如果lpVerb实际指向一个字符串,忽略此次调用并退出.

if(0!=HIWORD(pCmdInfo->lpVerb))

{

returnE_INVALIDARG;

}

//点击的命令索引–在这里,唯一合法的索引为0.

switch(LOWORD(pCmdInfo->lpVerb))

{

case0:

{

HWNDWnd;

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);

returnS_OK;

}

break;

default:

returnE_INVALIDARG;

break;

              }

}

注册Shell扩展
现在我们已经实现了所有需要的COM接口.可是我们怎样才能让浏览器使用我们的扩展呢?
ATL自动生成注册COMDLL服务器的代码,但这只是让其它程序可以使用我们的DLL.

最后,在shell版本4.71 中,你可以让上下文菜单在用户右击浏览器窗口(包括桌面)的背景时激发.
要让你的扩展在这种情况下被激发,需要在HKCR\Directory\Background\shellex\ContextMenuHandlers键下进行注册.
使用该方法,你可以添加定制菜单到桌面或任意目录上下文菜单.
这时传送到IShellExtInit::Initialize()的参数有些不同,所以我将在以后的文章中讲述这方面的内容.

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
外壳扩展
(C#)Windows Shell 外壳编程系列7
第十三章 MFC工具条和状态栏
怎样访问Internet Explorer中的WebBrowser
VC|MFC学习笔记六: 几个小知识(I)--全域函数,数据类型,CWnd和HWnd等
手工添加单击文件右键二级菜单项
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服