代码之家  ›  专栏  ›  技术社区  ›  Edwin Yip

如何访问Windows Shell上下文菜单项?

  •  11
  • Edwin Yip  · 技术社区  · 14 年前

    在Windows资源管理器中,右键单击一个文件,将显示一个上下文菜单,其中包含内置项,如“发送到…”和/或第三方操作,如“带WinZip的Zip文件”。我的问题是:

    • 如何获取特定文件可用菜单项的完整列表?
    • 对于每个菜单项,如何获取标题?
    • 如何调用特定磁盘文件的特定菜单项操作?

    提前谢谢!

    [编辑]:虽然其他信息非常有用,但Delphi的解决方案将非常感谢!

    3 回复  |  直到 13 年前
        1
  •  9
  •   RRUZ    14 年前

    获取shell上下文菜单的关键是使用 IContextMenu 接口。

    看看这篇好文章 Shell context menu support 了解更多详细信息。

    更新

    对于Delphi示例,您可以看到 JclShell 绝地JCL的部队(检查 DisplayContextMenu 函数)和包含在delphi示例文件夹中的shellctrls单元。

        2
  •  7
  •   Zoë Peterson RRUZ    14 年前

    简短的回答

    尝试 ShellBrowser Components 来自JAM软件。它们有一个组件,可以让您在tpopupmenu中混合使用自己的命令显示资源管理器的上下文菜单。


    冗长的回答

    获取资源管理器菜单、查询其所有属性,并将它们托管在您自己的菜单中是可能的,但是您确实应该能够轻松地阅读/编写低级的win32代码,并且C的工作知识将有所帮助。您还需要注意一些问题(见下文)。我强烈推荐阅读陈瑞蒙的 How to host an IContextMenu 一系列的技术细节。

    这个 更容易的 方法是先查询icontextmenu接口,然后查询hmenu,然后使用trackpopupmenu让Windows显示菜单,最后调用invokecommand。

    下面的一些代码没有经过测试,或者已经根据我们使用的代码进行了修改,因此请自行承担风险。

    以下是你如何获得 IContextMenu ,对于基本文件夹中的一组文件:

    function GetExplorerMenu(AHandle: HWND; const APath: string;
      AFilenames: TStrings): IContextMenu;
    var
      Desktop, Parent: IShellFolder;
      FolderPidl: PItemIDList;
      FilePidls: array of PItemIDList;
      PathW: WideString;
      i: Integer;
    begin
      // Retrieve the Desktop's IShellFolder interface
      OleCheck(SHGetDesktopFolder(Desktop));
      // Retrieve the parent folder's PItemIDList and then it's IShellFolder interface
      PathW := WideString(IncludeTrailingPathDelimiter(APath));
      OleCheck(Desktop.ParseDisplayName(AHandle, nil, PWideChar(PathW),
        Cardinal(nil^), FolderPidl, Cardinal(nil^)));
      try
        OleCheck(Desktop.BindToObject(FolderPidl, nil, IID_IShellFolder, Parent));
      finally
        SHFree(FolderPidl);
      end;
      // Retrieve PIDLs for each file, relative the the parent folder
      SetLength(FilePidls, AFilenames.Count);
      try
        FillChar(FilePidls[0], SizeOf(PItemIDList) * AFilenames.Count, 0);
        for i := 0 to AFilenames.Count-1 do begin
          PathW := WideString(AFilenames[i]);
          OleCheck(Parent.ParseDisplayName(AHandle, nil, PWideChar(PathW),
            Cardinal(nil^), FilePidls[i], Cardinal(nil^)));
        end;
        // Get the context menu for the files from the parent's IShellFolder
        OleCheck(Parent.GetUIObjectOf(AHandle, AFilenames.Count, FilePidls[0],
          IID_IContextMenu, nil, Result));
      finally
        for i := 0 to Length(FilePidls) - 1 do
          SHFree(FilePidls[i]);
      end;
    end;
    

    要获得需要调用的实际菜单项 IContextMenu.QueryContextMenu . 您可以使用 DestroyMenu .

    function GetExplorerHMenu(const AContextMenu: IContextMenu): HMENU;
    const
      MENUID_FIRST = 1;
      MENUID_LAST = $7FFF;
    var
      OldMode: UINT;
    begin
      OldMode := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
      try
        Result := CreatePopupMenu;
        AContextMenu.QueryContextMenu(Result, 0, MENUID_FIRST, MENUID_LAST, CMF_NORMAL);
      finally
        SetErrorMode(OldMode);
      end;
    end;
    

    以下是您实际调用用户从菜单中选择的命令的方式:

    procedure InvokeCommand(const AContextMenu: IContextMenu; AVerb: PChar);
    const
      CMIC_MASK_SHIFT_DOWN   = $10000000;
      CMIC_MASK_CONTROL_DOWN = $20000000;
    var
      CI: TCMInvokeCommandInfoEx;
    begin
      FillChar(CI, SizeOf(TCMInvokeCommandInfoEx), 0);
      CI.cbSize := SizeOf(TCMInvokeCommandInfo);
      CI.hwnd := GetOwnerHandle(Owner);
      CI.lpVerb := AVerb;
      CI.nShow := SW_SHOWNORMAL;
      // Ignore return value for InvokeCommand.  Some shell extensions return errors
      // from it even if the command worked.
      try
        AContextMenu.InvokeCommand(PCMInvokeCommandInfo(@CI)^)
      except on E: Exception do
        MessageDlg(Owner, E.Message, mtError, [mbOk], 0);
      end;
    end;
    
    procedure InvokeCommand(const AContextMenu: IContextMenu; ACommandID: UINT);
    begin
      InvokeCommand(AContextMenu, MakeIntResource(Word(ACommandID)));
    end;
    

    现在您可以使用 GetMenuItemInfo 函数获取标题、位图等,但更简单的方法是调用 TrackPopupMenu 让Windows显示弹出菜单。看起来像这样:

    procedure ShowExplorerMenu(AForm: TForm; AMousePos: TPoint; 
      const APath: string; AFilenames: TStrings; );
    var
      ShellMenu: IContextMenu;
      Menu: HMENU;
      MenuID: LongInt;
    begin
      ShellMenu := GetExplorerMenu(AForm.Handle, APath, AFilenames);
      Menu := GetExplorerHMenu(ShellMenu);
      try
        MenuID := TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_TOPALIGN or TPM_RETURNCMD, 
          AMousePos.X, AMousePos.Y, 0, AForm.Handle, nil);
        InvokeCommand(ShellMenu, MenuID - MENUID_FIRST);
      finally
        DestroyMenu(Menu);
      end;
    end;
    

    如果您真的想提取菜单项/标题并将它们添加到您自己的弹出菜单中(我们使用工具栏2000并执行此操作),那么您将遇到的其他大问题如下:

    • 除非您处理消息并将其传递到IContextmenu2/IContextmenu3接口,否则“发送到”菜单和任何其他按需构建的菜单将无法工作。
    • 菜单位图有两种不同的格式。Delphi在处理Vista高颜色的时候不需要使用coaxing,而旧的则使用xor混合到背景色中。
    • 有些菜单项是由所有者绘制的,因此您必须捕获绘制消息,并让它们绘制到自己的画布上。
    • 除非手动查询提示字符串,否则它们将无法工作。
    • 您需要管理icontextmenu和hmenu的生命周期,并且只有在关闭弹出菜单后才释放它们。
        3
  •  0
  •   mjn anonym    13 年前

    以下是“发送到…”后面的操作系统逻辑的示例。|“邮件收件人”上下文菜单项可用于从Delphi应用程序打开默认邮件客户端,显示附加了已传递(选定)文件的新邮件:

    How can I simulate ‘Send To…’ with Delphi?