如何编程实现扩展右键菜单之一:增加自己的菜单项

      我们在使用各种软件时,常常可以发现,许多软件在资源管理器的右键菜单中增加了自己的功能菜单,比如WinRAR、WinZIP、7Z等压缩软件等。本文将介绍如何编程实现增加自己的右键菜单。

      阅读本文要求读者熟悉VC,了解COM,同时由于示例代码是用ATL编写,因此读者需要对ATL有一定了解。本文示例是以VC6为开发环境,但所有代码在VC6、VC2005、VC2008下均可编译通过。

一. 开始

      本文将一步一步的介绍如何编写一个简单的右键菜单示例,完成此示例后,右键点击文本文件,将弹出我们的右键菜单,选择此菜单将弹出对话框显示所选择的文件名称。

      右键菜单扩展一般可实现为一个COM组件,Windows将载入这些COM组件并调用一些特定的接口。因此,我们需要创建一个COM组件。

      首先,使用VC的AppWizard新建一个ATL COM项目,项目名称为“SimpleShlExt”,项目设置就使用默认设置。

      完成后,向此项目加入一个新的ATL对象,对象类型为“Simple Object”

      输入对象名称为“ShlExtObj”

      Shell扩展对象不需要实现双接口,不需要实现聚集,因此可按下图设置:

      确定后,类向导生成一个名称为“CShlExtObj”的类,其中包含一些用于实现COM组件的基本代码。

二. 实现IShellExtInit接口

      当Windows加载我们的Shell扩展时,Explorer.exe进程将首先通过QueryInterface()方法获取指向IShellExtInit接口的指针。IShellExtInit接口只有一个方法,名称为Initialize(),其原型为:

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

      在explorer.exe进程调用此方法时,pidlFolder是PIDL指针(PIDL是一个用于唯一标示Shell中的对象的数据结构),pDataObj是指向IDataObject接口的指针,通过此指针,我们可以获取右键点击时所选中的文件及目录的名称,hProgID是HKEY句柄,通过它我们可以访问保护我们的扩展DLL的注册表信息。

      在本例中,我们只需要使用pDataObj即可。

      现在,我们需要让我们的CShlExtObj对象实现IShellExtInit接口。首先,打开ShlExtObj.h文件,键入标为粗体的代码以实现IShellExtInit接口,因为此COM组件不需要实现自己的接口,因此可以删除标注有删除线的代码

 
  
class ATL_NO_VTABLE CShlExtObj : 
	public CComObjectRootEx,
	public CComCoClass,
	public IShlExtObj
	public IShellExtInit
{
public:
	CShlExtObj()
	{
	}
DECLARE_REGISTRY_RESOURCEID(IDR_SHLEXTOBJ)
DECLARE_NOT_AGGREGATABLE(CShlExtObj)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CShlExtObj)
	COM_INTERFACE_ENTRY(IShlExtObj)
	COM_INTERFACE_ENTRY(IShellExtInit)
END_COM_MAP()
 
  
// IShlExtObj
public:
};

 

      现在,需要在ShlExtObj.h中增加对IShellExtInit::Initialize()方法的声明,此外还需要声明一个变量以保存文件名

 
  
private:
	TCHAR m_szFilename[MAX_PATH];

public:
	STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);

    在ShlExtObj.cpp中实现IShellExtInit::Initialize()方法如下:

 
  
HRESULT CShlExtObj::Initialize(LPCITEMIDLIST pidlFolder, 
                               LPDATAOBJECT pDataObj, 
                               HKEY hProgID)
{
	FORMATETC	fmt={CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
	STGMEDIUM	stg={TYMED_HGLOBAL};
	HDROP		hDrop=NULL;
	// 从IDataObject接口中获取CF_HDROP数据
	if (FAILED(pDataObj->GetData(&fmt, &stg)))
		return E_INVALIDARG;
	// 获取指向实际数据的指针
	hDrop=(HDROP)GlobalLock(stg.hGlobal);
	if (hDrop==NULL)
	{
		ReleaseStgMedium(&stg);
		return E_INVALIDARG;
	}
	// 查询选择的文件数量
	UINT uFilesCount=DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
	HRESULT hr=S_OK;
	if (0==uFilesCount)
	{
		GlobalUnlock(stg.hGlobal);
		ReleaseStgMedium(&stg);
		return E_INVALIDARG;
	}
	// 将文件名称保存在变量中
	if (0==DragQueryFile(hDrop, 0, m_szFilename, MAX_PATH))
		hr=E_INVALIDARG;
 
  
	GlobalUnlock(stg.hGlobal);
	ReleaseStgMedium(&stg);
	return S_OK;
}

      如果IShellExtInit::Initialize()方法返回E_INVALIDARG,则Windows将不再为此次右键单击继续调用此扩展DLL,如果返回S_OK,则Windows将调用QueryInterface(),获取指向IContextMenu接口的指针。

      为了使用IShellExtInit接口,我们还需要打开stdafx.h文件,添加如下一行代码

 
  
#include 
#include 

三. 实现IContextMenu接口

      在Windows调用IShellExtInit::Initialize()后,就会调用IContextMenu接口上的方法,通过这些方法,我们可以在右键菜单中添加自己的菜单项,并相应菜单点击命令。

      首先在ShlExtObj.h中添加实现IContextMenu接口的代码,如下粗体所示:

 
  
class ATL_NO_VTABLE CShlExtObj : 
	public CComObjectRootEx,
	public CComCoClass,
	public IShellExtInit,
	public IContextMenu
{
// 省略了原有的代码......
BEGIN_COM_MAP(CShlExtObj)
	COM_INTERFACE_ENTRY(IShellExtInit)
	COM_INTERFACE_ENTRY(IContextMenu)
END_COM_MAP()
public:
	// IContextMenu
	STDMETHODIMP GetCommandString(UINT, UINT, UINT*, LPSTR, UINT);
	STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO);
	STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT);
};

 

      IContextMenu接口有三个方法,QueryContextMenu()让我们可以向右键菜单中添加菜单项,GetCommandString()方法用于返回菜单项的提示信息,InvokeCommand()方法在用户选择了我们添加的菜单项时被调用。

      首先看一下实现QueryContextMenu()方法的代码:

 
  
HRESULT CShlExtObj::QueryContextMenu (HMENU hmenu, 
                                      UINT uMenuIndex, 
                                      UINT uidFirstCmd,
                                      UINT uidLastCmd, 
                                      UINT uFlags )
{
	if (uFlags & CMF_DEFAULTONLY)
		return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
 
  
	InsertMenu (hmenu, 
                uMenuIndex,
                MF_BYPOSITION,
                uidFirstCmd, 
                _T("显示文件名称"));
	return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 1 );
}

      在这段代码中,首先我们需要检查uFlags标志是否包含CMF_DEFAULTONLY,如包含则直接返回成功。随后我们添加了自己的菜单项,然后返回成功。注意此方法的返回值使用了MAKE_HRESULT宏,该宏的第一个参数表示成功或失败,如失败,则第二个参数表示错误类别,如成功,则第三个参数为我们所添加的菜单项的ID减去uidFirstCmd加1,简单的说就是我们占据的菜单项的个数。比如,uidFirstCmd为5,我们插入的菜单项的ID为5,6,8,则MAKE_HRESULT宏的第三个参数应为8-5+1=4,因为我们的菜单项实际占据的ID个数为4个——包括5,6,7,8——其中7虽然没有使用,但由于我们使用了8,则7也无法被其他扩展菜单项使用了。

      GetCommandString()方法用于返回菜单项的提示信息,在此例中笔者直接返回E_NOTIMPL,表示没有提示信息。

 
  
STDMETHODIMP CShlExtObj::GetCommandString (UINT idCmd, 
                                           UINT uFlags,
                                           UINT* pwReserved,
                                           LPSTR pszName, 
                                           UINT cchMax )
{
	return E_NOTIMPL;
}

      InvokeCommand()方法表示用户选择了我们添加的菜单项。在此例中,我们将弹出一个对话框显示所选择的文件名称。

 
  
HRESULT CShlExtObj::InvokeCommand (LPCMINVOKECOMMANDINFO pCmdInfo )
{
	if (0!=HIWORD(pCmdInfo->lpVerb))
		return E_INVALIDARG;
	switch (LOWORD(pCmdInfo->lpVerb))
	{
	case 0:
		MessageBox (pCmdInfo->hwnd, m_szFilename, _T("文件名"), MB_ICONINFORMATION );
		break;
	default:
		return E_INVALIDARG;
	}
	return S_OK;
}

 

四. 注册信息

      由于Shell的扩展DLL COM组件不需要完整的COM注册信息,我们需要打开ShlExtObj.rgs,按如下修改

 
  
HKCR
{
	SimpleShlExt.ShlExtObj.1 = s 'ShlExtObj Class'	
	{
		CLSID = s '{BEB94AA2-06D6-4674-A5F9-635A202947BD}'
	}
	SimpleShlExt.ShlExtObj = s 'ShlExtObj Class'
	{
		CLSID = s '{BEB94AA2-06D6-4674-A5F9-635A202947BD}'
		CurVer = s 'SimpleShlExt.ShlExtObj.1'
	}
	NoRemove CLSID
	{
		ForceRemove {BEB94AA2-06D6-4674-A5F9-635A202947BD} = s 'ShlExtObj Class'
		{
			ProgID = s 'SimpleShlExt.ShlExtObj.1'
			VersionIndependentProgID = s 'SimpleShlExt.ShlExtObj'
			InprocServer32 = s '%MODULE%'
			{
				val ThreadingModel = s 'Apartment'
			}
			'TypeLib' = s '{6BEB8C88-9F1E-4F7C-ACF3-84962AFD5DF4}'
		}
	}
}

      然后增加Shell扩展注册信息如下粗体所示:

 
  
HKCR
{
	NoRemove txtfile
	{
		NoRemove shellex
		{
			NoRemove ContextMenuHandlers
			{
				ForceRemove SimpleShlExt = s '{BEB94AA2-06D6-4674-A5F9-635A202947BD}'
			}
		}
	}
}
HKLM
{
	NoRemove Software
	{
		NoRemove Microsoft
		{
			NoRemove Windows
			{
				NoRemove CurrentVersion
				{
					NoRemove 'Shell Extensions'
					{
						NoRemove Approved
						{
							val {BEB94AA2-06D6-4674-A5F9-635A202947BD} = s 'ShlExtObj'
						}
					}
				}
			}
		}
	}
}

      由于此COM组件不需要在脚本或VB中调用,因此我们可以将类型库删除。点击【View】下的【Resource Includes...】菜单,在对话框中删除“1 TYPELIB "SimpleShlExt.tlb"”,如图

      如VC显示警告信息,点击OK即可。

      删除类型库后,还需修改DllRegisterServer()和DllUnregisterServer,如下:

 
  
STDAPI DllRegisterServer(void)
{
    return _Module.RegisterServer(TRUE FALSE);
}
STDAPI DllUnregisterServer(void)
{
    return _Module.UnregisterServer(TRUE FALSE);
}

 

五. 编译、测试

      现在,我们可以编译此程序,编译成功后VC将自动注册此COM组件。如一切都没有问题,此时打开资源管理器,在任意后缀名为txt的文本文件上点击右键,就可以看到我们的菜单项“显示文件名称”

      点击此菜单,会弹出对话框显示文件名称

    试试看,你成功了吗?

你可能感兴趣的:(如何编程实现扩展右键菜单之一:增加自己的菜单项)