如何访问 Windows shell 上下文菜单项?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3777121/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to access Windows shell context menu items?
提问by Edwin Yip
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'. My question are:
在 Windows 资源管理器中,您右键单击一个文件,会出现一个上下文菜单,其中包含诸如“发送到...”之类的内置项目和/或诸如“使用 Winzip 压缩文件”之类的第 3 方操作。我的问题是:
- 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 解决方案将不胜感激!
采纳答案by RRUZ
The key to obtain the Shell Context menu is use the IContextMenu
interface.
获取Shell Context 菜单的关键是使用IContextMenu
界面。
check this great article Shell context menu support
for more details.
查看这篇很棒的文章Shell context menu support
了解更多详情。
UPDATE
更新
for delphi examples you can see the JclShellunit from the JEDI JCL (check the DisplayContextMenu
function) and the ShellCtrls unit included in the samples folder of Delphi.
对于delphi 示例,您可以看到JEDI JCL 中的JclShell单元(检查DisplayContextMenu
函数)和Delphi 示例文件夹中包含的ShellCtrls 单元。
回答by Zo? Peterson
Short answer
简答
Try the ShellBrowser Componentsfrom JAM Software. They have a component that will let you show Explorer's context menu with your own commands mixed in from a TPopupMenu.
试试JAM Software的ShellBrowser 组件。他们有一个组件,可以让您显示资源管理器的上下文菜单,其中包含从 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. You'll also need to watch out for some gotchas (covered below). I strongly recommend reading Raymond Chen's How to host an IContextMenuseries for a lot of the technical details.
获取资源管理器菜单、查询其所有属性并将它们托管在您自己的菜单中是可能的,但您确实应该能够轻松阅读/编写低级 Win32 代码,并且 C 的工作知识会有所帮助。您还需要注意一些问题(见下文)。我强烈建议阅读 Raymond Chen 的How to host an IContextMenu系列以了解许多技术细节。
The easierapproach 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. You can destroy the returned HMENU using DestroyMenu.
要获得实际的菜单项,您需要调用IContextMenu.QueryContextMenu。您可以使用DestroyMenu销毁返回的 HMENU 。
function GetExplorerHMenu(const AContextMenu: IContextMenu): HMENU;
const
MENUID_FIRST = 1;
MENUID_LAST = FFF;
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 = 000000;
CMIC_MASK_CONTROL_DOWN = 000000;
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 GetMenuItemInfofunction to get the caption, bitmap, etc, but a much easier approach is to call TrackPopupMenuand let Windows show the popup menu. That would look something like this:
现在您可以使用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;
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.
- 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.
- 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.
- 除非您处理消息并将它们传递给 IContextMenu2/IContextMenu3 接口,否则“发送到”菜单和任何其他按需构建的菜单都将不起作用。
- 菜单位图有几种不同的格式。Delphi 不会在没有哄骗的情况下处理 Vista 高颜色的,旧的使用 XOR 混合到背景颜色上。
- 一些菜单项是所有者绘制的,因此您必须捕获绘制消息并将它们绘制到您自己的画布上。
- 除非您手动查询,否则提示字符串将不起作用。
- 您需要管理 IContextMenu 和 HMENU 的生命周期,并且只有在弹出菜单关闭后才释放它们。
回答by mjn
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 应用程序使用“发送到 ... | 邮件收件人”上下文菜单项背后的操作系统逻辑来打开默认邮件客户端,显示附加了已传递(选定)文件的新邮件: