Creating Context Menu Handlers Creating Context Menu Handlers When a user right-clicks a Shell object, the Shell displays its context menu. For file system objects there are a number of standard items, such as Cut and Copy, that are on the menu by default. If the object is a file that is a member of a class, additional items can be specified in the registry. Finally, the Shell checks the registry to see if the file class is associated with any context menu handlers. If it is, the Shell queries the handler for additional context menu items. A context menu handler is a shell extension handler that adds commands to an existing context menu. Context menu handlers are associated with a particular file class and are called any time a context menu is displayed for a member of the class. While you can add items to a file class context menu with the registry, the items will be the same for all members of the class. By implementing and registering such a handler, you can dynamically add items to an object's context menu, customized for the particular object. A closely related Shell extension handler is the drag-and-drop handler. It is a context menu handler that adds items to the context menu that is displayed when a user drags and drops a file with the right mouse button. The procedures for implementing and registering a Shell extension handler are discussed in Creating Shell Extension Handlers. This document focuses on those aspects of implementation that are specific to context menu handlers. The following topics are discussed. · How Context Menu Handlers Work · Registering a Context Menu Handler · Creating Drag-and-Drop Handlers Context menu handlers are a type of Shell extension handler. Like all such handlers, they are in-process Component Object Model (COM) objects implemented as DLLs. Context menu handlers must export two interfaces in addition to IUnknown: IShellExtInit and IContextMenu. Optionally, a context menu handler can also export IContextMenu2 and IContextMenu3. These interfaces handle the messaging needed to implement owner-drawn menu items. For more information on owner-drawn menu items, see the Creating Owner-Drawn Menu Items section under Using Menus. The IShellExtInit interface is used by the Shell to initialize the handler. When the Shell calls IShellExtInit::Initialize, it passes in a data object with the object's name and the pointer to an item identifier list (PIDL) of the folder that contains the file. The hKeyProgID parameter is not used with context menu handlers. The IShellExtInit::Initialize method must extract the file name from the data object and store the name and the folder's PIDL for later use. For further details, see Implementing IShellExtInit. The remainder of the operation takes place through the handler's IContextMenu interface. The Shell first calls IContextMenu::QueryContextMenu. It passes in an HMENU handle that the method can use to add items to the context menu. If the user selects one of the commands, IContextMenu::GetCommandString is called to retrieve the Help string that will be displayed on the Microsoft Windows Explorer status bar. If the user clicks one of the handler's items, the Shell calls IContextMenu::InvokeCommand. The handler can then execute the appropriate command. Registering a Context Menu Handler Context menu handlers are associated with either a file class or a folder. For file classes, the handler is registered under the following subkey. · HKEY_CLASSES_ROOT o Program ID § shellex § ContextMenuHandlers Create a subkey under ContextMenuHandlers named for the handler, and set the subkey's default value to the string form of the handler's class identifier (CLSID) GUID. You can also associate a context menu handler with different kinds of folders. Register the handler the same way you would for a file class, but under the following subkey, where FolderType is the name of the type of folder. · HKEY_CLASSES_ROOT o FolderType § shellex § ContextMenuHandlers For a discussion of how to register Shell extension handlers and more information about which folder types you can register handlers for, see Registering Shell Extension Handlers. If a file class has a context menu associated with it, double-clicking an object normally launches the default command. The handler's IContextMenu::QueryContextMenu method is not called. To specify that the handler's IContextMenu::QueryContextMenu method should be called when an object is double-clicked, create a shellex\MayChangeDefaultMenu subkey under the handler's CLSID key. When an object associated with the handler is double-clicked, IContextMenu::QueryContextMenu will be called with the CMF_DEFAULTONLY flag set in the uFlags parameter. Note Setting the MayChangeDefaultMenu key forces the system to load the handler's DLL when an associated item is double-clicked. If your handler does not change the default verb, you should not set MayChangeDefaultMenu . Doing so causes the system to load your DLL unnecessarily. Context menu handlers should set this key only if they might need to change the context menu's default verb. The following example illustrates registry entries that enable a context menu handler for an example .myp file class. The handler's CLSID key includes a MayChangeDefaultMenu subkey to guarantee that the handler is called when the user double-clicks a related object. · HKEY_CLASSES_ROOT o o .myp o CLSID § {00000000-1111-2222-3333-444444444444} § InProcServer32 § shellex § MayChangeDefaultMenu o MyProgram.1 § shellex § ContextMenuHandler Most of the operation described in How Context Menu Handlers Work is handled by the IContextMenu interface. This section discusses how to implement its three methods. The Shell calls IContextMenu::QueryContextMenu to allow the context menu handler to add its menu items to the menu. It passes in the HMENU handle in the hmenu parameter. The indexMenu parameter is set to the index to be used for the first menu item that is to be added. Any menu items that are added by the handler must have identifiers that fall between the idCmdFirst and idCmdLast parameters. Typically, the first command identifier is set to idCmdFirst, which is incremented by one (1) for each additional command. This practice ensures that you don't exceed idCmdLast and maximizes the number of available identifiers in case the Shell calls more than one handler. An item identifier's command offset is the difference between the identifier and idCmdFirst. Store the offset of each item that your handler adds to the context menu because the Shell might use it to identify the item if it subsequently calls IContextMenu::GetCommandString or IContextMenu::InvokeCommand. You should also assign a verb to each command you add. A verb is a language-independent string that can be used instead of the offset to identify the command when IContextMenu::InvokeCommand is called. It is also used by functions such as ShellExecuteEx to execute context menu commands. Only three of the flags that can be passed in through the uFlags parameter are relevant to context menu handlers.
Use either InsertMenu or InsertMenuItem to add menu items to the list. Then return an HRESULT value with the severity set to SEVERITY_SUCCESS. Set the code value to the offset of the largest command identifier that was assigned, plus one (1). For example, assume that idCmdFirst is set to 5 and you add three items to the menu with command identifiers of 5, 7, and 8. The return value should be MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1). The following example shows a simple implementation of IContextMenu::QueryContextMenu that inserts a single command. The identifier offset for the command is IDM_DISPLAY, which is set to zero. The m_pszVerb and m_pwszVerb variables are private variables used to store the associated language-independent verb string in both Unicode and ANSI formats. #define IDM_DISPLAY 0
STDMETHODIMP CMenuExtension::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { HRESULT hr;
if(!(CMF_DEFAULTONLY & uFlags)) { InsertMenu(hMenu, indexMenu, MF_STRING | MF_BYPOSITION, idCmdFirst + IDM_DISPLAY, "&Display File Name");
// TODO: Add error handling to verify HRESULT return values.
hr = StringCbCopyA(m_pszVerb, sizeof(m_pszVerb), "display"); hr = StringCbCopyW(m_pwszVerb, sizeof(m_pwszVerb), L"display");
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(IDM_DISPLAY + 1)); }
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0)); } If a user highlights one of the items added by a context menu handler, the handler's IContextMenu::GetCommandString method is called to request a Help text string that will be displayed on the Windows Explorer status bar. This method can also be called to request the verb string that is assigned to a command. Either ANSI or Unicode verb strings can be requested. The idCmd parameter holds the identifier offset of the command that was defined when IContextMenu::QueryContextMenu was called. If a Help string is requested, uFlags will be set to either GCS_HELPTEXTA or GCS_HELPTEXTW, depending on whether an ANSI or Unicode string is desired. Copy the Help string to the pszName buffer, casting it to an LPWSTR for the Unicode case. The verb string is requested by setting uFlags to either GCS_VERBA or GCS_VERBW. Copy the appropriate string to pszName, just as with the Help string. The GCS_VALIDATEA and GCS_VALIDATEW flags are not used by context menu handlers. The following example shows a simple implementation of IContextMenu::GetCommandString that corresponds to the IContextMenu::QueryContextMenu example given in the QueryContextMenu Method section. Since the handler adds only one menu item, there is only one set of strings that can be returned. The method simply tests whether idCmd is valid and, if it is, returns the requested ANSI or Unicode string. The StringCchCopyN function is used to copy the requested string to pszName to ensure that the copied string doesn't exceed the size of the buffer given by uMaxNameLen. The ANSI and Unicode versions of the function are used explicitly, because the format defined when the handler is compiled might not match the requested format. STDMETHODIMP CMenuExtension::GetCommandString(UINT idCommand, UINT uFlags, LPUINT lpReserved, LPSTR pszName, UINT uMaxNameLen) { HRESULT hr = E_INVALIDARG;
if(idCommand != IDM_DISPLAY) { return hr; }
switch(uFlags) { case GCS_HELPTEXTA: hr = StringCchCopyNA(pszName, lstrlen(pszStr)/sizeof(pszStr(0)), "Display File Name", uMaxNameLen); break;
case GCS_HELPTEXTW: hr = StringCchCopyNW((LPWSTR)pszName, lstrlen(pszStr)/sizeof(pszStr(0)), L"Display File Name", uMaxNameLen); break;
case GCS_VERBA: hr = StringCchbCopyNA(pszName, lstrlen(pszStr)/sizeof(pszStr(0)), m_pszVerb, uMaxNameLen); break;
case GCS_VERBW: hr = StringCchCopyNW((LPWSTR)pszName, lstrlen(pszStr)/sizeof(pszStr(0)), m_pwszVerb, uMaxNameLen); break;
default: hr = S_OK; break; } return hr; } This method is called when a user clicks a menu item to tell the handler to run the associated command. The pici parameter points to a structure that contains the needed information. Although pici is declared in Shlobj.h as a CMINVOKECOMMANDINFO structure, in practice it often points to a CMINVOKECOMMANDINFOEX structure. This structure is an extended version of CMINVOKECOMMANDINFO and has several additional members that allow Unicode strings to be passed. Check the cbSize member of pici to determine which structure was passed in. If it is a CMINVOKECOMMANDINFOEX structure and the fMask member has the CMIC_MASK_UNICODE flag set, cast pici to CMINVOKECOMMANDINFOEX. This allows your application to use the Unicode information contained in the last five members of the structure. The structure's lpVerb or lpVerbW member is used to identify the command to be executed. There are two ways to identify commands. · The command's verb string · The command's identifier offset To distinguish between these two cases, check the high-order word of lpVerb for the ANSI case or lpVerbW for the Unicode case. If the high-order word is nonzero, lpVerb or lpVerbW holds a verb string. If the high-order word is zero, the command offset is in the low-order word of lpVerb. The following example shows a simple implementation of IContextMenu::InvokeCommand that corresponds to the IContextMenu::QueryContextMenu and IContextMenu::GetCommandString samples given in the previous sections. The method first determines which structure is being passed in. It then determines whether the command is identified by its offset or verb. If lpVerb or lpVerbW holds a valid verb or offset, the method displays a message box. STDMETHODIMP CShellExtension::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) { BOOL fEx = FALSE; BOOL fUnicode = FALSE;
if(lpcmi->cbSize == sizeof(CMINVOKECOMMANDINFOEX)) { fEx = TRUE; if((lpcmi->fMask & CMIC_MASK_UNICODE)) { fUnicode = TRUE; } }
if( !fUnicode && HIWORD(lpcmi->lpVerb)) { if(StrCmpIA(lpcmi->lpVerb, m_pszVerb)) { return E_FAIL; } }
else if( fUnicode && HIWORD(((CMINVOKECOMMANDINFOEX *) lpcmi)->lpVerbW)) { if(StrCmpIW(((CMINVOKECOMMANDINFOEX *)lpcmi)->lpVerbW, m_pwszVerb)) { return E_FAIL; } }
else if(LOWORD(lpcmi->lpVerb) != IDM_DISPLAY) { return E_FAIL; }
else { MessageBox(lpcmi->hwnd, "The File Name", "File Name", MB_OK|MB_ICONINFORMATION); }
return S_OK; } Creating Drag-and-Drop Handlers When a user right-clicks a Shell object to drag an object, a context menu is displayed when the user attempts to drop the object. The following illustration shows a typical drag-and-drop context menu. A drag-and-drop handler is a context menu handler that can add items to this context menu. Drag-and-drop handlers are typically registered under the following subkey. · HKEY_CLASSES_ROOT o Directory § shellex § DragDropHandlers Add a subkey under the DragDropHandlers subkey named for the drag-and-drop handler, and set the subkey's default value to the string form of the handler's CLSID GUID. The following example enables the MyDD drag-and-drop handler. · HKEY_CLASSES_ROOT o Directory § shellex § DragDropHandlers § MyDD The basic procedure for implementing a drag-and-drop handler is the same as for conventional context menu handlers. However, context menu handlers normally use only the IDataObject pointer passed to the handler's IShellExtInit::Initialize method to extract the object's name. A drag-and-drop handler could implement a more sophisticated data handler to modify the behavior of the dragged object. Windows外壳扩展编程 www. 下面是与外壳扩展相关的三个重要术语: Windows支持七种类型的外壳扩展(称为Handler),它们相应的作用简述如下: Windows的所有外壳扩展都是基于COM(Component Object Model) 组件模型的,外壳是通过接口(Interface)来访问对象的。外壳扩展被设计成32位的进程中服务器程序,并且都是以动态链接库的形式为操作系统提供服务的。因此,如果要对Windows的用户界面进行扩充的话,则具备写COM对象的一些知识是十分必要的。 写好外壳扩展程序后,必须将它们注册才能生效。所有的外壳扩展都必须在Windows注册表的HKEY_CLASSES_ROOT\CLSID键之下进行注册。在该键下面可以找到许多名字像{ 注册表HKEY_CLASSES_ROOT主键下有几个特殊的子键,如*、Folder、Drive以及Printer。如果把外壳扩展注册在*子键下,那么这个外壳扩展将对Windows中所有类型的文件有效;如果把外壳扩展注册在Folder子键下,则对所有目录有效。 上面提到的在Windows
Explore中在鼠标右键菜单中添加菜单项(我们成为上下文相关菜单)的操作属于外壳扩展的第一类,即Context
menu handlers向特定类型的文件对象增添上下文相关菜单。要动态地在上下文相关菜单中增添菜单项,可以通过写Context Menu Handler来实现。 (1)QueryContextMenu函数:每当系统要显示一个文件对象的上下文相关菜单时,它首先要调用该函数。为了在上下文相关菜单中添加菜单 (2)InvokeCommand函数:当用户选定了某个Context
Menu Handler登记过的菜单项后,该函数将会被调用,系统将会传给该函数一个指向 (3)GetCommandString函数:当鼠标指针移到一个上下文相关菜单项上时,在当前窗口的状态条上将会出现与该菜单项相关的帮助信息,此 下面我通过具体的例程来说明编写一个比较完整的上下文菜单程序,这个程序是一个文件操作程序,当安装并注册了外壳扩展的服务器动态连接库之后,当选择一个或者多个文件并单击鼠标右键后,在右键菜单中就会多出一个“执行文件操作”的上下文菜单,点击菜单就会弹出相应的程序执行文件操作。 library contextmenu; exports {$R *.TLB} {$R *.RES} begin end. 将工程文件保存为contextmenu.dpr。 unit ContextMenuHandler; interface type const Class_ContextMenu: TGUID = '{19741013-C829-11D1-8233-0020AF3E {全局唯一标识符(GUID)是一个16字节(128为)的值,它唯一地标识一个接口(interface)} implementation uses ComServ, SysUtils, ShellApi, Registry,UnitForm; function TContextMenu.SEIInitialize(pidlFolder:
PItemIDList; lpdobj: IDataObject; file://首先初始化并清空FileList以添加文件 file://首先查询用户选中的文件的个数 ReleaseStgMedium(StgMedium); function TContextMenu.QueryContextMenu(Menu: HMENU;
indexMenu, idCmdFirst, function TContextMenu.InvokeCommand(var lpici:
TCMInvokeCommandInfo): HResult; file://建立一个临时文件保存用户选中的文件名 AssignFile(F,sSaveFile); { next
file in Files property } Result := NOERROR; function TContextMenu.GetCommandString(idCmd, uType: UINT;
pwReserved: PUINT; type procedure TContextMenuFactory.UpdateRegistry(Register:
Boolean); file://如果操作系统为Windows
NT的话 initialization end. 将该单位文件保存为unit2.pas,文件同contextmenu.dpr位于同一个目录下。 下面来建立文件操作程序。打开VB,建立一个新的工程文件,在Form1中加入一个ListBox控件和三个CommandButton控件,将ListBox的MultiSelect属性设置为2。然后在Form1的代码窗口中加入以下代码: Private Type BrowseInfo Const FO_COPY = &H2 Private Declare Function ShellAbout Lib
"shell32.dll" Alias _ Dim DirString As String Sub UpdateList() Function BrowseForFolder(hwndOwner As Long, sPrompt As
String) As String '初试化udtBI结构 BrowseForFolder = sPath Private Sub Command1_Click() '执行文件拷贝操作 Private Sub Command2_Click() '执行文件移动操作 Private Sub Command3_Click() '执行文件删除操作 Private Sub Form_Load() Private Sub Form_Unload(Cancel As Integer) Private Sub List1_Click()
由 ATL 想起的外壳扩展编程(一)
参考文献
Windows外壳扩展
也许有人会问我实现它们困难吗?答案是:比较简单。实现它是不是必须得去看那些枯燥乏味的ATL模板类,或者生硬死板的 MFC 宏定义呢?答案是否定的。也许以上的问题阻碍了大多数COM初学者的学习欲望,其实我刚接触ATL时多的是迷惘,常常抱怨 ATL 的知识太深奥,MFC的构架太生硬,一般我是不太喜欢用#define来定义程序的全部(请参阅
effective C++)。言归正传,我们再回到今天的话题上来,那么为实现 图1.1 所示功能可以通过哪些途径呢?答案有二,第一:注册表编程。第二:Shell Extension COM编程。通过注册表方式实现其实十分简单,请参阅
COM 组件注册表实现,在这里本文不做重复介绍,再者也不是本文的主题所在。在以下的内容中我会以第一类
Shell 扩展编程---" Context Menu 处理器" 为例来讲解 Handler 的实现过程。
3. HRESULT Initialize( 4. LPCITEMIDLIST pidlFolder, 5. LPDATAOBJECT lpdobj, 6. HKEY hkeyProgID ); 在 Initialize 函数中,我们要做的事情就是获取用户鼠标右键点击的文件名称,但是有可能用户选择了多个文件,这里为了简单起见我们仅获取文件列表中的第一个文件。在这里我们得补充一点内容:当用户在一个拥有 WS_EX_ACCEPTFILES 风格的窗体中Drag/Drop 文件时这些文件名会以同一种格式存储,而且文件完整路径的获取也都以DragQueryFile API函数来实现。但是 DragQueryFile 需要传入一个 HDROP 句柄,该句柄即为 Drag/Drop 文件名称列表数据句柄(开始存放数据的内存区域首指针)。而 HDROP 句柄的可以通过接口 " DATAOBJECT lpdobj" 的成员函数" GetData" 来获取。以下为获取第一个 Drag/Drop 文件的完整文件路径的具体代码: //数据存储格式 FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; //数据存储内存句柄(常用于IDataObject和IAdviseSink接口的数据传输操作) STGMEDIUM stg = { TYMED_HGLOBAL }; if(FAILED(pDataObj->GetData(&fmt, &stg))) { //如果获取数据内存句柄失败则返回E_INVALIDARG, //返回E_INVALIDARG则Explorer不会再调用我们的Shell扩展接口 return E_INVALIDARG; } //获取实际数据内存句柄 HDROP hDrop = (HDROP)GlobalLock(stg.hGlobal); if(NULL==hDrop) { //在COM程序中养成良好的检错习惯是很重要的!!! return E_INVALIDARG; } //获取用户Drag/Drop的文件数目 int nDropCount = ::DragQueryFile((HDROP)stg.hGlobal, 0xFFFFFFFF, NULL, 0); //本示例程序仅获取第一个Drag/Drop文件完整路径 //以下注释代码为获取所有文件完整路径的实现代码: //for(int i = 0; i < nDropCount; ++i){ //循环获取每个Drag/Drop文件的完整文件名 // ::DragQueryFile((HDROP)stg.hGlobal, i, m_pzDropFile, MAX_PATH); //} //如果用户Drag/Drop的文件数目不为一个则不予处理 if(1==nDropCount) { //pzDropFile为组件类内部的private变量 //它用来保存用户Drag/Drop的文件完整文件名 memset(m_pzDropFile, 0x0, MAX_PATH*sizeof(TCHAR)); ::DragQueryFile((HDROP)stg.hGlobal, 0, m_pzDropFile, MAX_PATH); } //释放内存句柄 ::ReleaseStgMedium(&mdmSTG); 至此
IShellExtInit 接口已经完全实现,从此我们也可以看出进程内组件编程的一些特点,大体总结如下:"新建自己的接口,然后继承某些接口,最后一一实现这些接口的所有虚成员函数或
加入自己的成员函数,最后就是组件的注册"。 IContextMenu 接口 :该接口和 "Context Menu 处理器" 一一对应,说到此我们也顺便说一下 Shell 扩展接口编程中和(表一)中所列处理器各自对应的COM接口:
其中 "Drag and drop 处理器" 的除了 COM 接口 IContextMenu 实现外还得需要注册表的特殊注册才可以实现。其中 IContextMenu 接口有三个虚成员函数需要我们的组件来实现,其函数原型分别如下: HRESULT QueryContextMenu( HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags ); 注:在QueryContextMenu 成员函数中我们可以加入自己的菜单项,插入菜单项其实很简单,我们可以通过 InsertMenu API 函数来实现,如下代码所示: ::InsertMenu(hmenu, indexMenu, MF_STRING | MF_BYPOSITION, idCmdFirst, IDM_REG_MNU_TXT); QueryContextMenu 的处理过程十分简单,在这里无须多说。 HRESULT GetCommandString( UINT idCmd, UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax ); 注:GetCommandString 成员函数为 Explorer 提供了在状态栏显示菜单命令提示信息的方法。在这个方法中 "LPSTR pszName" 是我们要关注的参数,我们只要根据 "UINT uFlags" 参数来填充 "LPSTR pszName" 参数即可。在这里可能会涉及到 ANSI 和 UNICODE 之间相互转换的知识,不过在这里我要提醒大家的是:在 COM 编程中尽可能使用兼容的 TCHAR 类型,同时对字符操作也尽量不要使用 C 类的 <string.h> 和<stdio.h> 等等函数库,因为这样会使您无法通过 "Win32 Release Mindependency " 或其他 UINCode/Release 版本的编译过程。 HRESULT InvokeCommand( LPCMINVOKECOMMANDINFO pici ); InvokeCommand 函数实现最终菜单项命令的执行。在 "LPCMINVOKECOMMANDINFO pici" 参数中包含了当前用户执行的菜单项ID和其他一些标志信息,如下代码可获取菜单项的ID: //如果 nFlag 不为0则说明 pici->lpVerb 指向一个以''\0''结尾的字符串 int nFlag = HIWORD(pici->lpVerb); //用户当前点击的菜单项ID int nMnuId = LOWORD(lpici->lpVerb); 一旦获取了菜单项ID那么我们就可以根据不同的菜单项来执行相应的动作,如图1.2 所示的 "Register Component" 和 "UnRegister Component" 菜单项所对应的 "注册/反注册进程内组件" 动作。
8. //声明组件注册所用的注册表REG资源 9. //其中IDR_SIMPLESHLEXT为注册表资源ID 10.DECLARE_REGISTRY_RESOURCEID(IDR_SIMPLESHLEXT) 11.//AddRef和Release成员函数的实现 12.DECLARE_PROTECT_FINAL_CONSTRUCT() 13.//组件接口映射部分,该部分映射主要是告诉QueryInterface能返回哪些接口给外部 14.BEGIN_COM_MAP(CSimpleShlExt) 15. COM_INTERFACE_ENTRY(ISimpleShlExt) 16. COM_INTERFACE_ENTRY(IDispatch) 17. COM_INTERFACE_ENTRY(IShellExtInit) //IShellExtInit接口 18. COM_INTERFACE_ENTRY(IContextMenu) //IContextMenu接口 END_COM_MAP()
o HKCR o { o NoRemove dllfile o { o NoRemove ShellEx o { o NoRemove ContextMenuHandlers o { o //类的GUID字符串 o ForceRemove SimpleShlExt = s''{ |
|