如何访问Windowsshell上下文菜单项?

Windows资源pipe理器中,右键点击一个文件,会出现一个上下文菜单,其中包含内置项目,如“发送到…”和/或第三方操作,例如“使用Winzip压缩文件”。 我的问题是:

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

先谢谢你!

[编辑]:虽然其他信息是绝对有用的,delphi解决scheme将非常感激!

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

查看这个很棒的文章Shell context menu support了解更多细节。

UPDATE

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

简短的回答

尝试从JAM软件的ShellBrowser组件 。 他们有一个组件,可以让你显示资源管理器的上下文菜单,并且你可以从TPopupMenu中混入自己的命令。


长答案

获取资源管理器菜单,查询其所有属性并将其托管在自己的菜单中是可能的,但是您真的应该可以自如地阅读/编写低级别的Win32代码,并且对C的使用知识将会有所帮助。 你还需要注意一些陷阱(下面将会介绍)。 我强烈建议阅读Raymond Chen的“ 如何主持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; 

如果你真的想提取菜单项/标题,并将它们添加到你自己的弹出菜单(我们使用工具栏2000,并做到这一点),这里是您将遇到的其他重大问题:

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

下面是一个例子,如何从Delphi应用程序中使用“发送到…邮件收件人”上下文菜单项背后的操作系统逻辑来打开默认的邮件客户端,显示一个新的邮件,附带传递的(选择的)文件:

如何用Delphi模拟“Send To …”?