副标题#e#
当用户右击一个shell工具时,shell会显示它的上下文菜单。文件系统工具有大量的尺度菜单项,如"剪切"和"拷贝",这些都是缺省的菜单项。假如工具是一个文件,是文件类的成员,就可以或许在注册内外指定附加的菜单项。Shell查抄注册表,看看文件范例是否与一些上下文菜单handler相关联,假如是,shell会咨询这些handler是否添加特另外菜单项。
上下文菜单handler是一种shell扩展handler,它添加呼吁到已有的上下文菜单中。上下文菜单handler都与特定的文件类相关联,而且在显示这类文件的成员的上下文菜单时挪用。通过实现和注册这样一个handler,可以或许动态地添加菜单项到工具的上下文菜单上,从而为非凡的工具定制菜单。
上下文菜单Handler的事情道理
作为一种shell扩展handler,上下文菜单handler同所有其它handler一样, 是历程内COM 工具,即工具作为动态毗连库 (DLL)实现。除了IUnknown接口外,上下文菜单还必需导出IShellExtInit和IContextMenu接口,作为选择,上下文菜单也能导出IContextMenu2和IContextMenu3,这些接口可以实现自画菜单项。
IShellExtInit接口仅仅被shell用来初始化handler,主要的操纵通过handler的IContextMenu接口举办。Shell首先挪用IContextMenu::QueryContextMenu,传送一个HMENU句柄,这个要领用它来增加上下文菜单。假如用户亮选了这些新添加的某个呼吁项, IContextMenu::GetCommandString将被挪用,以取得这条菜单的辅佐信息,把它显示在资源打点器的状态条上。假如用户单击了handler的条目,shell挪用IContextMenu::InvokeCommand,从而handler可以或许执行符合的操纵。
实现IContextMenu接口
1、实现QueryContextMenu要领
#p#副标题#e#
Shell通过挪用IContextMenu::QueryContextMenu,答允handler把它的菜单项添加到菜单中。QueryContextMenu共有5个参数,各参数浸染如下:
1) Hmenu:HMENU范例,暗示上下文菜单的句柄。
2) IndexMenu:第一个被添加的菜单索引。
3) IdCmdFirst:添加的菜单ID初值。
4) idCmdLast:添加的菜单ID最大值。
5) uFlags:与上下文菜单相关的状态符号,共有3种,如下:
CMF_DEFAULTONLY 用户选择了缺省的呼吁,凡是是通过双击工具发生。QueryContextMenu 在把节制返回给shell前不该该修改菜单。
CMF_NODEFAULT 菜单没有缺省的条目,这个要领应该把它的呼吁加到菜单中。
CMF_NORMAL 上下文菜单将被正常显示,这个要领应该把它的呼吁加到菜单中。
必需留意的是,任何添加的菜单项的ID必需落在idCmdFirst和idCmdLast两个参数中间,凡是,添加的第一个菜单项ID设为idCmdFirst,今后每添加一个菜单项,就把ID加1,这样,纵然shell挪用了不止一个handler,也可以确保菜单项的ID不高出idCmdLast和大概的ID最大值。
在ID和idCmdFirst之间,菜单项ID的command offset(呼吁偏移)是差异的,应该生存handler添加到上下文菜单中的每个菜单项的offset,因为假如shell按顺序挪用GetCommandString可能InvokeCommand,可以利用它来辨别菜单项的ID。
还应该为每一个添加的呼吁赋予一个verb。Verb是语言独立的字符串,当挪用InvokeCommand时,经常用verb来取代偏移以辨别呼吁。
QueryContextMenu 要领利用InsertMenu或InsertMenuItem 添加新的菜单项,然后返回一个严格配置为SEVERITY_SUCCESS的HRESULT值,把它的值配置为被分派的最大的呼吁ID。譬喻,如果idCmdFirst是5,添加了3个菜单项,ID别离是5,7,8,则返回值应该是MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 – 5 + 1)。
以下是一个QueryContextMenu实例:
HRESULT __stdcall TAddContextMenuImpl::QueryContextMenu(HMENU hmenu,
UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
if(!(CMF_DEFAULTONLY & uFlags))
{
InsertMenu(hmenu, indexMenu, MF_STRING | MF_BYPOSITION,idCmdFirst,
_T("选择打开方法..."));
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
}
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
}
2、实现GetCommandString 要领
假如用户高亮了一个handler添加的菜单项,shell将挪用handler的GetCommandString要领。这个要领需要通报菜单项的偏移值(ID)、指定信息范例的符号、一个预留的参数、一个字符串缓冲区以及缓冲区的巨细。
一般,这个要领可以不消处理惩罚,以下示例措施直接返回S_OK。
HRESULT __stdcall TAddContextMenuImpl::GetCommandString(UINT idCmd, UINT uFlags,
UINT *pwReserved, LPSTR pszName, UINT cchMax)
{
return S_OK;
}
3、实现InvokeCommand要领
#p#分页标题#e#
当在上下文菜单中选择一个菜单项时,shell就会挪用InvokeCommand,汇报handler运行相关联的呼吁。在Shlobj.h中,参数pici被声明为CMINVOKECOMMANDINFO布局,但实际上,它常常指向CMINVOKECOMMANDINFOEX布局,这个布局是CMINVOKECOMMANDINFO的扩展版本,有几个成员答允通报Unicode字符串。
CMINVOKECOMMANDINFO的成员简介如下:
1) cbSize :布局的巨细。
2) fMask :为0,或下列符号的组合。
CMIC_MASK_ASYNCOK 在返回之前期待DDE会话竣事
CMIC_MASK_FLAG_NO_UI 当执行呼吁时,系统防备显示用户接口元素(如错误信息)
CMIC_MASK_HOTKEY dwHotKey 成员有效
CMIC_MASK_ICON hIcon成员有效
CMIC_MASK_NO_CONSOLE 假如上下文菜单handler必需建设新历程,正常环境下将建设一个节制台,配置CMIC_MASK_NO_CONSOLE符号可以克制建设新的节制台
3) hwnd :拥有上下文菜单窗口的句柄,handler可以利用这个句柄显示本身的信息提示框和对话框。
4) lpVerb :32位值,高位字包括0,低位字是呼吁的菜单ID偏移。当用户选择一个菜单呼吁时,Shell用MAKEINTRESOURCE宏发生这个值,假如高位字不是0,那么这个成员指向一个以NULL末了的字符串,指出呼吁的语言无关的名称,即上文的verb。典范环境下,当呼吁被一个应用措施激活时,这个成员是一个字符串。系统提供了下面几个预界说的常数值:
值:CMDSTR_NEWFOLDER 字符串:"NewFolder"
值:CMDSTR_VIEWDETAILS 字符串:"ViewDetails"
值:CMDSTR_VIEWLIST 字符串:"ViewList"
5) lpParameters :呼吁传送的参数字符串,对付shell扩展插入的菜单项,这个成员老是NULL。
6) lpDirectory :目次名称,对付shell扩展插入的菜单项,这个成员老是NULL。
7) nShow :显示窗口或启动应用措施时,通报给ShowWindow函数的参数。
8) dwHotKey :分派给被呼吁激活的应用措施的热键。假如fMask 不是CMIC_MASK_HOTKEY,这个成员被忽略。
9) hIcon :被呼吁激活的应用措施利用的图标。假如fMask 不是CMIC_MASK_ICON,这个成员被忽略。
以下示例先打开一个"选择文件"的对话框,然后用所选择的措施打开在资源打点器中被选择的文件。为了简化,假定在资源打点器只选择了一个文件。
HRESULT __stdcall TAddContextMenuImpl::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
if(HIWORD(pici->lpVerb)==0)
{
if(LOWORD(pici->lpVerb)==0) // 添加的第一个菜单项
{
TOpenDialog *Dlg=new TOpenDialog(NULL);
Dlg->Title="打开\"";
Dlg->Title=Dlg->Title+g_szFilePath+"\"";
Dlg->Options.Clear();
Dlg->Options << ofFileMustExist << ofPathMustExist << ofNoChangeDir;
if(Dlg->Execute())
{
ShellExecute(pici->hwnd,"open",Dlg->FileName.c_str(),g_szFilePath,NULL,SW_SHOW); }
return S_OK;
}
}
return S_FALSE;
}
注册上下文菜单Handler
上下文菜单与文件类可能文件夹相关联。对付文件类,handler注册在文件类的HKEY_CLASSES_ROOT\ProgID\Shellex\ContextMenuHandlers子键下。在ContextMenuHandlers下建设一个以handler子键,把子键的缺省值配置为handler的CLSID的字符串值,就可以完成注册。
也可以或许把handler关联到文件夹,注册的要领与上面雷同,不外是在HKEY_CLASSES_ROOT\FolderType\Shellex\ContextMenuHandlers增加子键, 个中的FolderType 是文件夹范例的名称。
假如一个文件类有上下文菜单与它关联,那么双击一个工具将自动启动缺省的呼吁,而不会挪用handler的QueryContextMenu要领。当工具被双击时,为了指定挪用handler的QueryContextMenu要领,必需在handler的CLSID键下建设一个ShellEx\MayChangeDefaultMenu的子键。这样,当与handler关联的工具被双击时,QueryContextMenu 被挪用,并且uFlags参数会包括CMF_DEFAULTONLY 符号。
留意,假如配置了MayChangeDefaultMenu键,当一个关联的项目被双击时,会强制系统载入handler的DLL。假如handler不改变缺省行动,就不该该配置MayChangeDefaultMenu,不然会引起系统不须腹地载入这个DLL。仅仅当在大概改变上下文菜单的缺省行动时,才应该在配置上下文菜单handler的这个值。
建设工程
作为Borland的产物,用C++ Builder建设shell扩展的进程与Delphi有雷同之处,但它究竟是C++语言,所以也有与VC雷同之的处所。
#p#分页标题#e#
1. 选择File菜单的New菜单项,翻到New Items对话框的ActiveX页,双击ActiveX Library项,建设一个新的COM工程,把工程定名为MyContextMenu。从New Items 对话框的ActiveX页选择COM Object项,将打开COM Server领导。把"COClass"改为AddContextMenu,选择Apartment线程模式。其它不要改写。C++ Builder自动发生一个接口和一个类。默认的类名是TAddContextMenuImpl,回收自动生成的IAddContextMenu接口。我们必需本身添加新的接口IShellExtInit和IContextMenu,如下所示,粗体是添加的内容:
#include <shlobj.h> // 声明IShellExtInit和IContextMenu的头文件
class ATL_NO_VTABLE TAddContextMenuImpl :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<TAddContextMenuImpl, &CLSID_AddContextMenu>,
public IShellExtInit,
public IContextMenu,
public IAddContextMenu
{
private:
char g_szFilePath[MAX_PATH];
public:
… …
BEGIN_COM_MAP(TAddContextMenuImpl)
COM_INTERFACE_ENTRY(IAddContextMenu)
COM_INTERFACE_ENTRY(IContextMenu) // 导出IContextMenu接口
COM_INTERFACE_ENTRY(IShellExtInit) // 导出IShellExtInit接口
END_COM_MAP()
… …
};
2. 实现IShellExtInit接口的Initialize要领,在类界说中增加如下内容:
STDMETHOD (Initialize)(LPCITEMIDLIST pidlFolder,LPDATAOBJECT lpdobj,HKEY hkeyProgID);
Initialize要领的代码如下,从lpdobj工具中取出资源打点器中选择的文件名,措施假定只选择了一个文件。
HRESULT __stdcall TAddContextMenuImpl::
Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT lpdobj, HKEY hkeyProgID)
{
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stg = { TYMED_HGLOBAL };
HDROP hDrop;
if (FAILED(lpdobj->GetData(&fmt, &stg))) return E_FAIL;
hDrop = (HDROP)GlobalLock(stg.hGlobal);
if ( hDrop == NULL)
{
ReleaseStgMedium(&stg);
return E_OUTOFMEMORY;
}
DragQueryFile(hDrop, 0, g_szFilePath, MAX_PATH);
GlobalUnlock(stg.hGlobal);
ReleaseStgMedium(&stg);
return S_OK;
}
3. 实现IContextMenu接口的各个要领,内容如上文所示,声明如下:
public:
STDMETHOD (QueryContextMenu)(HMENU hmenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags);
STDMETHOD (InvokeCommand)(LPCMINVOKECOMMANDINFO pici);
STDMETHOD (GetCommandString)(UINT idCmd,UINT uFlags,UINT *pwReserved,LPSTR pszName,UINT cchMax);
最后,把工程编译为DLL文件,运行菜单[Run->Register ActiveX Server],把DLL注册。与Delphi和VC对比,C++ Builder好像有些缺陷。首先,它实现时过分巨大,生成的文件一大堆。最贫苦的是,它无法实现自动注册为shell扩展,它没有VC的rgs文件,像Delphi那样改写UpdateRegistry函数,怎么也不可,仿佛这个函数没有挪用一样。无奈,只好本身动手向注册表添加必需的项目(如图)。可是,C++ Builder给出了3个CLSID,很疑惑人,正确的CLSID应该是类AddContextMenu的,C++ Builder给它定名为CLSID_AddContextMenu。
注册后,在资源打点器右击任何文件,如readme.txt,都将打开一个选择文件的对话框,然后shell用选择的文件打开readme.txt。