简体   繁体   English

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

[英]How to access Windows shell context menu items?

In Windows Explorer, you right click on a file, a context menu shows up which contains built-in items like 'Send to...' and/or 3rd party actions such as 'zip file with Winzip'. 在Windows资源管理器中,右键单击一个文件,显示一个上下文菜单,其中包含内置项,如“发送到...”和/或第三方操作,如“带Winzip的zip文件”。 My question are: 我的问题是:

  • How to obtain the full list of available menu items for a specific file? 如何获取特定文件的可用菜单项的完整列表?
  • For each menu item, how to get the caption? 对于每个菜单项,如何获取标题?
  • How to invoke a specific menu item action for a specific disk file? 如何为特定磁盘文件调用特定菜单项操作?

Thank you in advance! 先感谢您!

[EDIT]: While other info is absolutely useful, Delphi solution will be much appreciated! [编辑]:虽然其他信息绝对有用,但Delphi解决方案将非常受欢迎!

The key to obtain the Shell Context menu is use the IContextMenu interface. 获取Shell上下文菜单的关键是使用IContextMenu接口。

check this great article Shell context menu support for more details. 查看这篇很棒的文章Shell context menu support更多细节。

UPDATE UPDATE

for delphi examples you can see the JclShell unit from the JEDI JCL (check the DisplayContextMenu function) and the ShellCtrls unit included in the samples folder of Delphi. 为Delphi例子可以看到JclShell从JEDI JCL单元(检查DisplayContextMenu功能)和包含在Delphi的样品夹在ShellCtrls单元。

Short answer 简短的回答

Try the ShellBrowser Components from JAM Software. 试试JAM Software的ShellBrowser组件 They have a component that will let you show Explorer's context menu with your own commands mixed in from a TPopupMenu. 它们有一个组件,可以让您显示资源管理器的上下文菜单,其中您自己的命令来自TPopupMenu。


Long answer 答案很长

Getting the Explorer menu, querying all of its properties, and hosting them in your own menu is possible, but you really should be comfortable reading/writing low-level Win32 code and a working knowledge of C will help. 获取资源管理器菜单,查询其所有属性,并在您自己的菜单中托管它们是可能的,但您应该很乐意阅读/编写低级Win32代码,并且C的工作知识将有所帮助。 You'll also need to watch out for some gotchas (covered below). 你还需要注意一些陷阱(如下所述)。 I strongly recommend reading Raymond Chen's How to host an IContextMenu series for a lot of the technical details. 我强烈建议阅读Raymond Chen的“ 如何主持IContextMenu系列”以获取大量技术细节。

The easier approach is to query for the IContextMenu interface, then the HMENU, then use TrackPopupMenu to let have Windows show the menu, then call InvokeCommand at the end. 更简单的方法是查询IContextMenu接口,然后查询HMENU,然后使用TrackPopupMenu让Windows显示菜单,最后调用InvokeCommand。

Some of the code below is untested or has been modified from what we're using, so proceed at your own risk. 以下某些代码未经测试或已根据我们使用的内容进行了修改,因此请自行承担风险。

Here's how you get the IContextMenu , for a group of files within a base folder: 以下是为基本文件夹中的一组文件获取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;

To get the actual menu items you need to call IContextMenu.QueryContextMenu . 要获取实际的菜单项,您需要调用IContextMenu.QueryContextMenu You can destroy the returned HMENU using DestroyMenu . 您可以使用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;

Here's how you actually call the command that the user has selected from the menu: 以下是您实际调用用户从菜单中选择的命令的方法:

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;

Now you can use the GetMenuItemInfo function to get the caption, bitmap, etc, but a much easier approach is to call TrackPopupMenu and let Windows show the popup menu. 现在您可以使用GetMenuItemInfo函数来获取标题,位图等,但更简单的方法是调用TrackPopupMenu并让Windows显示弹出菜单。 That would look something like this: 这看起来像这样:

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;

If you actually want to extract the menu items/captions and add them to your own popup menu (we use Toolbar 2000 and do exactly that), here are the other big issues you'll run into: 如果你真的想要提取菜单项/标题并将它们添加到你自己的弹出菜单中(我们使用Toolbar 2000并完全这样做),这里是你将遇到的其他重大问题:

  • The "Send To" menu, and any others that are built on-demand won't work unless you handle messages and pass them to the IContextMenu2/IContextMenu3 interfaces. 除非您处理消息并将消息传递给IContextMenu2 / IContextMenu3接口,否则“发送到”菜单以及按需构建的任何其他菜单都将无法工作。
  • Menu bitmaps are in a couple of different formats. 菜单位图有几种不同的格式。 Delphi doesn't handle Vista high-color ones without coaxing, and the older ones are blended onto the background color using an XOR. Delphi不会在没有哄骗的情况下处理Vista高色彩的产品,并且使用XOR将较旧的产品混合到背景颜色上。
  • Some menu items are owner-drawn, so you have to capture paint messages and have them paint to your own canvas. 有些菜单项是所有者绘制的,因此您必须捕获绘制消息并将它们绘制到您自己的画布上。
  • Hint strings won't work unless you manually query for them. 除非您手动查询它们,否则提示字符串将不起作用。
  • You'll need to manage the lifetime of the IContextMenu and HMENU and only release them once the popup menu has been closed. 您需要管理IContextMenu和HMENU的生命周期,并且只有在关闭弹出菜单后才会释放它们。

Here is an exmple how the operating system logic behind the "Send To ... | Mail recipient" context menu item can be used from a Delphi application to open the default mail client, displaying a new mail with the passed (selected) files attached: 下面是一个例子,如何从Delphi应用程序中使用“发送到... |邮件收件人”上下文菜单项后面的操作系统逻辑来打开默认邮件客户端,显示附带传递(选定)文件的新邮件:

How can I simulate 'Send To…' with Delphi? 如何使用Delphi模拟“发送到...”?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM