在Windows资源管理器中,您右键单击一个文件,显示一个上下文菜单,其中包含内置项,如“发送到...”和/或第三方操作,如“带Winzip的zip文件”。我的问题是:
- 如何获取特定文件的可用菜单项的完整列表?
- 对于每个菜单项,如何获取标题?
- 如何为特定磁盘文件调用特定的菜单项操作?
先谢谢你!
[编辑]:虽然其他信息绝对有用,但Delphi解决方案将非常受欢迎!
在Windows资源管理器中,您右键单击一个文件,显示一个上下文菜单,其中包含内置项,如“发送到...”和/或第三方操作,如“带Winzip的zip文件”。我的问题是:
先谢谢你!
[编辑]:虽然其他信息绝对有用,但Delphi解决方案将非常受欢迎!
获取Shell上下文菜单的关键是使用 IContextMenu
接口。
看看这篇好文章 Shell context menu support
更多细节。
UPDATE
对于delphi的例子你可以看到 JclShell 来自JEDI JCL的单位(查看 DisplayContextMenu
函数)和ShellCtrls单元包含在Delphi的samples文件夹中。
简短的回答
试试吧 ShellBrowser组件 来自JAM Software。它们有一个组件,可以让您显示资源管理器的上下文菜单,其中您自己的命令来自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。你可以使用破坏返回的HMENU 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;
如果你真的想要提取菜单项/标题并将它们添加到你自己的弹出菜单中(我们使用Toolbar 2000并完全这样做),这里是你将遇到的其他重大问题:
下面是一个例子,如何从Delphi应用程序中使用“发送到... |邮件收件人”上下文菜单项后面的操作系统逻辑来打开默认邮件客户端,显示附带传递(选定)文件的新邮件: