使用动态谓词自定义快捷菜单

快捷菜单处理程序也称为上下文菜单处理程序或谓词处理程序。 快捷菜单处理程序是一种文件类型处理程序。

本主题的组织方式如下:

关于静态和动态谓词

强烈推荐使用一种静态谓词方法实现快捷菜单。 建议按照创建快捷菜单处理程序的“使用静态谓词自定义快捷菜单”部分中提供的说明进行操作。 若要在 Windows 7 及更高版本中获取静态谓词的动态行为,请参阅创建快捷菜单处理程序中的“获取静态谓词的动态行为”。 有关静态谓词实现的详细信息以及要避免使用的动态谓词,请参阅为快捷菜单选择静态或动态谓词

如果必须通过注册文件类型的动态谓词来扩展文件类型的快捷菜单,请按照本主题后面提供的说明进行操作。

注意

注册在 32 位应用程序的上下文中工作的处理程序时,对于 64 位 Windows 有一些特殊注意事项:当 Shell 谓词在 32 位应用程序的上下文中调用时,WOW64 子系统会将文件系统访问重定向到某些路径。 如果 .exe 处理程序存储在其中一个路径中,则在此上下文中无法访问它。 因此,解决方法是将 .exe 存储在未重定向的路径中,或存储启动实际版本的 .exe 的存根版本。

 

快捷菜单处理程序如何与动态谓词配合使用

除了 IUnknown 外,快捷菜单处理程序还导出以下附加接口来处理实现所有者绘制的菜单项所需的消息传递

有关所有者绘制的菜单项的详细信息,请参阅使用菜单中的“创建所有者绘制的菜单项”部分

Shell 使用 IShellExtInit 接口初始化处理程序。 当 Shell 调用 IShellExtInit::Initialize 时,它会传入一个具有对象名称的数据对象,以及一个指向包含该文件的文件夹的项标识符列表 (PIDL) 的指针。 hkeyProgID 参数是注册快捷菜单句柄的注册表位置。 IShellExtInit::Initialize 方法必须从数据对象中提取文件名,并存储该名称和指向项标识符列表 (PIDL) 的文件夹的指针供稍后使用。 有关处理程序初始化的详细信息,请参阅实现 IShellExtInit

在快捷菜单中显示谓词时,首先发现它们,然后呈现给用户,最后调用。 以下列表更详细地描述了这三个步骤:

  1. Shell 调用 IContextMenu::QueryContextMenu,它返回一组可以基于项或系统的状态的谓词
  2. 系统传入 HMENU 句柄,该方法可用于将项添加到快捷菜单
  3. 如果用户单击某个处理程序的项,Shell 将调用 IContextMenu::InvokeCommand。 然后,处理程序可以执行相应的命令。

避免由于非限定谓词名称而发生冲突

由于谓词是按类型注册的,因此不同项上的谓词可以使用相同的谓词名称。 这样,应用程序就可以引用独立于项类型的通用谓词。 虽然此功能很有用,但使用非限定名称可能会导致与选择同一谓词名称的多个独立软件供应商 (ISV) 冲突。 若要避免这种情况,请始终在谓词前添加 ISV 名称,如下所示:

ISV_Name.verb

始终使用应用程序特定的 ProgID。 采用将文件扩展名映射到 ISV 提供的 ProgID 的约定可以避免潜在的冲突。 但是,由于某些项类型不使用此映射,因此需要供应商唯一的名称。 将谓词添加到可能已注册该谓词的现有 ProgID 时,必须先删除旧谓词的注册表项,然后再添加自己的谓词。 必须执行此操作,以避免合并两个谓词中的谓词信息。 不执行此操作会导致不可预测的行为。

使用动态谓词注册快捷菜单处理程序

快捷菜单处理程序与文件类型或文件夹相关联。 对于文件类型,处理程序在以下子项下注册。

HKEY_CLASSES_ROOT
   Program ID
      shellex
         ContextMenuHandlers

若要将快捷菜单处理程序与文件类型或文件夹相关联,请先在 ContextMenuHandlers 子项下创建子项。 为处理程序命名子项,并将子项的默认值设置为处理程序的类标识符 (CLSID) GUID 的字符串形式。

然后,若要将快捷菜单处理程序与不同类型的文件夹相关联,请按照与文件类型相同的方式注册处理程序,但要在 FolderType 子项下注册,如以下示例所示

HKEY_CLASSES_ROOT
   FolderType
      shellex
         ContextMenuHandlers

有关可为其注册处理程序的文件夹类型的详细信息,请参阅注册 Shell 扩展处理程序

如果文件类型具有与之关联的快捷菜单,则双击对象通常会启动默认命令,并且不会调用处理程序的 IContextMenu::QueryContextMenu 方法。 若要指定在双击对象时应调用的处理程序的 IContextMenu::QueryContextMenu 方法,请在处理程序的 CLSID 子项下创建一个子项,如下所示

HKEY_CLASSES_ROOT
   CLSID
      {00000000-1111-2222-3333-444444444444}
         shellex
            MayChangeDefaultMenu

双击与处理程序关联的对象时,将调用 IContextMenu::QueryContextMenu,并在 uFlags 参数中设置 CMF_DEFAULTONLY 标志

仅当快捷菜单处理程序可能需要更改快捷菜单的默认谓词时,才应该设置 MayChangeDefaultMenu 子项。 设置此子项会强制系统在双击关联项时加载处理程序的 DLL。 如果处理程序未更改默认谓词,则不应设置此子项,因为这样会导致系统不必要地加载 DLL。

以下示例演示了为 .myp 文件类型启用快捷菜单处理程序的注册表项。 处理程序的 CLSID 子项包括 MayChangeDefaultMenu 子项,以确保当用户双击相关对象时调用处理程序

HKEY_CLASSES_ROOT
   .myp
      (Default) = MyProgram.1
   CLSID
      {00000000-1111-2222-3333-444444444444}
         InProcServer32
            (Default) = C:\MyDir\MyCommand.dll
            ThreadingModel = Apartment
         shellex
            MayChangeDefaultMenu
   MyProgram.1
      (Default) = MyProgram Application
      shellex
         ContextMenuHandler
            MyCommand = {00000000-1111-2222-3333-444444444444}

实现 IContextMenu 接口

IContextMenu 是最有效但也是最复杂的实现方法。 强烈推荐使用一种静态谓词方法实现谓词。 有关详细信息,请参阅为快捷菜单选择静态或动态谓词IContextMenu 有三种方法:GetCommandString、InvokeCommand 和 QueryContextMenu,接下来将详细介绍

IContextMenu::GetCommandString 方法

处理程序的 IContextMenu::GetCommandString 方法用于返回谓词的规范名称。 此方法是可选的。 在 Windows XP 和更低版本的 Windows 中,当 Windows 资源管理器具有状态栏时,此方法用于检索菜单项的“状态”栏中显示的帮助文本。

idCmd 参数包含调用 IContextMenu::QueryContextMenu 时定义的命令的标识符偏移量。 如果请求帮助字符串,uFlags 将设置为 GCS_HELPTEXTW。 将帮助字符串复制到 pszName 缓冲区,将其强制转换为 PWSTR。 通过将 uFlags 设置为 GCS_VERBW 来请求谓词字符串。 与帮助字符串相同,将相应的字符串复制到 pszName。 快捷菜单处理程序不使用 GCS_VALIDATEA 和 GCS_VALIDATEW 标志

以下示例演示了 IContextMenu::GetCommandString 的简单实现,它对应于本主题的 IContextMenu::QueryContextMenu 方法部分给出的 IContextMenu::QueryContextMenu 示例。 由于处理程序只添加一个菜单项,因此只能返回一组字符串。 该方法测试 idCmd 是否有效,如果是,则返回请求的字符串

StringCchCopy 函数用于将请求的字符串复制到 pszName,以确保复制的字符串不超过 cchName 指定的缓冲区的大小。 此示例仅实现对 uFlags 的 Unicode 值的支持,因为只有自 Windows 2000 起在 Windows 资源管理器中使用了这些值

IFACEMETHODIMP CMenuExtension::GetCommandString(UINT idCommand, 
                                                UINT uFlags, 
                                                UINT *pReserved, 
                                                PSTR pszName, 
                                                UINT cchName)
{
    HRESULT hr = E_INVALIDARG;

    if (idCommand == IDM_DISPLAY)
    {
        switch (uFlags)
        {
            case GCS_HELPTEXTW:
                // Only useful for pre-Vista versions of Windows that 
                // have a Status bar.
                hr = StringCchCopyW(reinterpret_cast<PWSTR>(pszName), 
                                    cchName, 
                                    L"Display File Name");
                break; 

            case GCS_VERBW:
                // GCS_VERBW is an optional feature that enables a caller
                // to discover the canonical name for the verb passed in
                // through idCommand.
                hr = StringCchCopyW(reinterpret_cast<PWSTR>(pszName), 
                                    cchName, 
                                    L"DisplayFileName");
                break; 
        }
    }
    return hr;
}

IContextMenu::InvokeCommand 方法

当用户单击菜单项以告知处理程序运行关联的命令时,将调用此方法。 pici 参数指向包含所需信息的结构

尽管 pici 在 Shlobj.h 中声明为 CMINVOKECOMMANDINFO 结构,但实际上它通常指向 CMINVOKECOMMANDINFOEX 结构。 此结构是 CMINVOKECOMMANDINFO 的扩展版本,具有多个其他成员,使其能够传递 Unicode 字符串

检查 pici 的 cbSize 成员以确定传入的是哪个结构。 如果是 CMINVOKECOMMANDINFOEX 结构,并且 fMask 成员具有 CMIC_MASK_UNICODE 标志集,则将 pici 强制转换为 CMINVOKECOMMANDINFOEX。 这使应用程序能够使用该结构的最后五个成员中包含的 Unicode 信息。

结构的 lpVerb 或 lpVerbW 成员用于标识要执行的命令。 可通过以下两种方式之一标识命令:

  • 通过命令的谓词字符串
  • 通过命令的标识符偏移量

若要区分这两种情况,检查 ANSI 场景的 lpVerb 的高序位词,或 Unicode 场景的 lpVerbW。 如果高序位词非零,lpVerb 或 lpVerbW 包含谓词字符串。 如果高序位词为零,则命令偏移量为 lpVerb 的低序位词

以下示例演示了 IContextMenu::InvokeCommand 的简单实现,它对应于本部分前后给出的 IContextMenu::QueryContextMenuIContextMenu::GetCommandString 示例。 该方法首先确定传入的结构。 然后,确定命令是由其偏移量还是其谓词标识的。 如果 lpVerb 或 lpVerbW 包含有效的谓词或偏移量,则该方法将显示一个消息框

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

IContextMenu::QueryContextMenu 方法

Shell 调用 IContextMenu::QueryContextMenu,使快捷菜单处理程序能够将其菜单项添加到菜单。 它在 hmenu 参数中传入 HMENU 句柄。 indexMenu 参数设置为要添加的第一个菜单项所使用的索引

处理程序添加的任何菜单项都必须具有介于 idCmdFirst 和 idCmdLast 参数值之间的标识符。 通常,第一个命令标识符设置为 idCmdFirst,每个附加命令的增量为一 (1)。 这种做法有助于避免超过 idCmdLast,并在 Shell 调用多个处理程序时最大化可用标识符数

项标识符的“命令偏移量”是标识符与 idCmdFirst 中的值之间的差。 存储处理程序添加到快捷菜单的每个项的偏移量,因为 Shell 可能会在随后调用 IContextMenu::GetCommandStringIContextMenu::InvokeCommand 时使用它来标识该项

还应为添加的每个命令分配谓词。 谓词是一个字符串,可用于在调用 IContextMenu::InvokeCommand 时标识命令的偏移量ShellExecuteEx 函数也使用它来执行快捷菜单命令

可以通过 uFlags 参数传入三个标志,这些标志与快捷菜单处理程序相关。 下表对它们进行了说明。

标记 说明
CMF_DEFAULTONLY 用户已选择默认命令,方法通常是双击对象。 IContextMenu::QueryContextMenu 应将控制权返回给 Shell,而无需修改菜单
CMF_NODEFAULT 菜单中的项不应为默认项。 此方法应将其命令添加到菜单中。
CMF_NORMAL 快捷菜单将正常显示。 此方法应将其命令添加到菜单中。

 

使用 InsertMenuInsertMenuItem 将菜单项添加到列表中。 然后返回一个 HRESULT 值,其严重性设置为 SEVERITY_SUCCESS。 将代码值设置为已分配的最大命令标识符的偏移量加一 (1)。 例如,假设 idCmdFirst 设置为 5,将三个项添加到菜单中,命令标识符分别为 5、7 和 8。 返回值应为 MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1)

以下示例显示了插入单个命令的 IContextMenu::QueryContextMenu 的简单实现。 此命令的标识符偏移量是 IDM_DISPLAY,设置为零。 m_pszVerb 和 m_pwszVerb 变量是专用变量,用于采用 ANSI 和 Unicode 格式存储关联的与语言无关的谓词字符串

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

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

有关其他谓词实现任务,请参阅创建快捷菜单处理程序

快捷(上下文)菜单和快捷菜单处理程序

谓词和文件关联

为快捷菜单选择静态或动态谓词

快捷菜单处理程序和多个谓词的最佳做法

创建快捷菜单处理程序

快捷菜单参考