简短回答
尝试使用JAM Software的ShellBrowser Components。他们提供了一个组件,可以让您显示Explorer上下文菜单,并混合自己的命令从TPopupMenu。
长篇回答
获取资源管理器菜单,查询其所有属性并将它们托管在您自己的菜单中是可能的,但您真的应该熟悉阅读/编写低级Win32代码,并具有C语言的工作知识将会有所帮助。您还需要注意一些细节(gotchas)(下面有说明)。我强烈推荐阅读 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
OleCheck(SHGetDesktopFolder(Desktop));
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;
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;
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;
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的生命周期,并且只有在弹出菜单关闭后才能释放它们。