【问题标题】:How to access Windows shell context menu items?如何访问 Windows shell 上下文菜单项?
【发布时间】:2011-04-16 04:22:05
【问题描述】:

在 Windows 资源管理器中,右键单击文件,会显示一个上下文菜单,其中包含“发送到...”等内置项目和/或“使用 Winzip 压缩文件”等第 3 方操作。我的问题是:

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

提前谢谢你!

[编辑]:虽然其他信息绝对有用,但 Delphi 解决方案将不胜感激!

【问题讨论】:

  • 您想创建自己的上下文菜单项还是操作现有的菜单项?
  • @Liton,不是创建我自己的 shell 上下文菜单项,而是操作现有的菜单内置或第三部分项。

标签: c++ windows delphi winapi shell


【解决方案1】:

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

查看这篇精彩的文章 Shell context menu support 了解更多详情。

更新

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

【讨论】:

  • 谢谢你的链接,我去看看!
  • 嗨 RRUZ,JCL 包中的 DisplayContextMenu 功能正是我想要的!谢谢!
【解决方案2】:

简答

试试 JAM Software 的 ShellBrowser Components。他们有一个组件,可以让您显示 Explorer 的上下文菜单,其中包含您自己的命令从 TPopupMenu 混合。


长答案

获取资源管理器菜单、查询其所有属性并将它们托管在您自己的菜单中是可能的,但您确实应该能够轻松阅读/编写低级 Win32 代码,并且 C 的工作知识会有所帮助。您还需要注意一些陷阱(如下所述)。我强烈推荐阅读 Raymond Chen 的 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 销毁返回的HMENU。

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;

如果您真的想提取菜单项/标题并将它们添加到您自己的弹出菜单中(我们使用 Toolbar 2000 并做到了这一点),您会遇到以下其他大问题:

  • “发送至”菜单以及其他任何按需构建的菜单都将不起作用,除非您处理消息并将它们传递给 IContextMenu2/IContextMenu3 接口。
  • 菜单位图有几种不同的格式。 Delphi 不处理 Vista 高颜色的不经过哄骗,而旧的则使用 XOR 混合到背景颜色上。
  • 某些菜单项是所有者绘制的,因此您必须捕获绘制消息并将它们绘制到您自己的画布上。
  • 除非您手动查询提示字符串,否则它们将不起作用。
  • 您需要管理 IContextMenu 和 HMENU 的生命周期,并且仅在关闭弹出菜单后才释放它们。

【讨论】:

  • 嗨 Craig,感谢您提供详细的技术信息,他们非常有帮助!但我必须接受 RRUZ 的回答,因为他将我指向 JCL 中的 DisplayContextMenu 函数,该函数完全符合我的要求...
【解决方案3】:

这是一个示例,如何从 Delphi 应用程序中使用“发送至 ... | 邮件收件人”上下文菜单项背后的操作系统逻辑来打开默认邮件客户端,显示一封新邮件,其中包含已传递的(选中) 附件:

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

【讨论】:

    猜你喜欢
    • 2010-11-22
    • 2014-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-26
    • 1970-01-01
    • 1970-01-01
    • 2015-01-19
    相关资源
    最近更新 更多