如何使用Delphi弹出给定文件的Windows上下文菜单?

9
我想编写以下的过程/函数:
procedure ShowSysPopup(aFile: string; x, y: integer);

这将构建并显示(在坐标x和y处)Windows资源管理器中给定文件所见的右键菜单。我对“显示”部分不是很感兴趣,而是更关注如何构建这样的菜单。

3个回答

19

我为您提供了一个快速的解决方案。 将这些单元添加到“Uses”部分:

... ShlObj, ActiveX, ComObj

以下是您的过程,我只是添加了一个新的参数“HND”,以携带TWinControl的句柄,您将使用它来显示上下文菜单。

procedure ShowSysPopup(aFile: string; x, y: integer; HND: HWND);
var
  Root: IShellFolder;
  ShellParentFolder: IShellFolder;
  chEaten,dwAttributes: ULONG;
  FilePIDL,ParentFolderPIDL: PItemIDList;
  CM: IContextMenu;
  Menu: HMenu;
  Command: LongBool;
  ICM2: IContextMenu2;

  ICI: TCMInvokeCommandInfo;
  ICmd: integer;
  P: TPoint;
Begin
  OleCheck(SHGetDesktopFolder(Root));//Get the Desktop IShellFolder interface

  OleCheck(Root.ParseDisplayName(HND, nil,
    PWideChar(WideString(ExtractFilePath(aFile))),
    chEaten, ParentFolderPIDL, dwAttributes)); // Get the PItemIDList of the parent folder

  OleCheck(Root.BindToObject(ParentFolderPIDL, nil, IShellFolder,
  ShellParentFolder)); // Get the IShellFolder Interface  of the Parent Folder

  OleCheck(ShellParentFolder.ParseDisplayName(HND, nil,
    PWideChar(WideString(ExtractFileName(aFile))),
    chEaten, FilePIDL, dwAttributes)); // Get the relative  PItemIDList of the File

  ShellParentFolder.GetUIObjectOf(HND, 1, FilePIDL, IID_IContextMenu, nil, CM); // get the IContextMenu Interace for the file

  if CM = nil then Exit;
  P.X := X;
  P.Y := Y;

  Windows.ClientToScreen(HND, P);

  Menu := CreatePopupMenu;

  try
    CM.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE or CMF_CANRENAME);
    CM.QueryInterface(IID_IContextMenu2, ICM2); //To handle submenus.
    try
      Command := TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_LEFTBUTTON or TPM_RIGHTBUTTON or
        TPM_RETURNCMD, p.X, p.Y, 0, HND, nil);
    finally
      ICM2 := nil;
    end;

    if Command then
    begin
      ICmd := LongInt(Command) - 1;
      FillChar(ICI, SizeOf(ICI), #0);
      with ICI do
      begin
        cbSize := SizeOf(ICI);
        hWND := 0;
        lpVerb := MakeIntResourceA(ICmd);
        nShow := SW_SHOWNORMAL;
      end;
      CM.InvokeCommand(ICI);
    end;
  finally
     DestroyMenu(Menu)
  end;
End;

像这样修改/添加初始化和结束部分

initialization
  OleInitialize(nil);
finalization
  OleUninitialize;

以下是如何使用此过程的方法:

procedure TForm2.Button1Click(Sender: TObject);
begin
  ShowSysPopup(Edit1.Text,Edit1.Left,Edit1.Top, Handle);
end;

我希望这对你有用。

问候,

编辑:如果你想为多个文件显示上下文菜单,请查看我博客中的这篇文章


1
这似乎有点不完整,没有处理IContextMenu2消息,即未响应菜单消息调用HandleMenuMsg。因此,某些子菜单(如“打开方式”)将无法填充。这里是我所说的一个例子:https://dev59.com/NlXTa4cB1Zd3GeqPyyZ_#5287265。 - Sertac Akyuz
此外,如果没有一个类实现IShellCommandVerb接口,你的布尔型参数“Handled”和接口本身都是无用的。正如你在代码中所看到的,如果它支持该接口,你会查询nil,当然你永远不会得到该接口,只需摆脱那些不必要的代码和不必要的类型声明即可。 - Sertac Akyuz
我自己做了这件事。这个语句 if Supports(nil, IShellCommandVerb, SCV) then 真的很突出。请您也在您的博客上更正代码。不过我的第一条评论仍然有效。 - Sertac Akyuz
Edit1.Text := 'C:\' 失败时 - Hanlin
无法弹出“分享”选项 - Hanlin

2
你确定你想要这样做吗?因为如果你这样做,你实际上需要复制Windows Shell中所有代码及其与大量代码的交互行为。
右键菜单基本上是由“shell扩展”构建的。这些是注册在系统中的COM DLL。当调用右键菜单时,shell遵循一组规则,确定它应该在哪里(在注册表中)查找扩展DLL。
我发现这篇文章对这些规则很有用
但是,仅仅找到扩展DLL并不是全部内容。对于每个DLL,shell会实例化由该DLL注册的COM对象,并向那些对象发出调用,DLL会通过配置或调用菜单命令来响应这些调用。
Shell本身不构建菜单,也不能直接从任何地方查询或读取构建菜单所需的信息 - 菜单完全由shell扩展动态构建。
Shell将菜单句柄传递给每个扩展,以及一些信息,告诉扩展应该为添加到该菜单的任何项目使用哪些命令ID。扩展可以向其提供的菜单句柄添加几乎任何内容,包括子菜单等,它可能会根据当前文件选择的属性添加不同的项目,而不仅仅是文件扩展名(例如,Tortoise SVN客户端根据这些文件的当前SVN状态添加不同的菜单项)。
因此,如果您想自己构建这样的菜单,就像我说的那样,您将不得不在自己的代码中复制整个shell扩展框架(或者至少是初始化菜单的那些部分,假设由于某种原因您不想或不需要调用菜单命令本身)。
也许,如果你解释一下为什么想要这样做以及你想实现什么,那么这可能会有所帮助。也许还有更简单的方法。

看起来可能有一种方法可以避免复制所有的 shell 代码。我找到了这个链接,但它是一个 .NET 的例子。http://www.andrewvos.com/?p=420 - Deltics

0

虽然我同意Deltics的观点,这是一项艰巨的工作,但大多数(如果不是全部)项目所需的信息都可以在注册表中免费获得。 Deltics答案中列出的指南看起来很好,可以提供大部分项目。 许多内容可以从注册表中的基本条目中查找,而其他内容则需要调用COM对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接