c++ - 如何处理具有不同文件夹的 IShellFolder 菜单或具有多个文件的 ShellExecuteEx
问题描述
网上有很多关于这个问题的分析器,但我找不到解决方案。
fe 两个文件:'C:\user\doc1.pdf'、'C:\custom\doc.pdf'
对于第一个文件:当请求指向整个路径“C:\user”的相对“IShellfolder _fld1”并使用“_fld1.ParseDisplayName(..., 'doc1.pdf', ...)”获取 PIDLIST 时菜单将显示所有菜单项,就像在资源管理器中一样。fe(安装了 Adobe)我得到“使用 Adobe Acrobat DC 打开”。
但是当使用绝对路径到桌面时,例如“IShellfolder _root”并使用“_root.ParseDisplayName(..., 'C:\user\doc1.pdf', ...)”查询 PIDList 则不会发生错误,一些菜单-显示了项目(fe“副本”),但缺少一些项目(fe“使用 Adobe Acrobat DC 打开”)。
尝试驱动器路径时出现相同的错误行为:“IShellfolder _top”引用“C:\”和“_root.ParseDisplayName(..., 'user\doc1.pdf', ...)”
我是否必须始终采用完整的目录路径来显示所有项目?
========
在所有这些情况下,“复制”之类的标准(!)菜单项都可以工作,因此我可以一次从所有不同的目录(也包括网络驱动器)复制文件,并一步粘贴到资源管理器中的某个位置。伟大的。
========
但具体的菜单:有多个网站都在谈论我自己合并菜单并使用“ShellExecuteEx”。“合并菜单”不是问题,但是如何在多个文件上使用“ShellExecuteEx”?
fe 如果我有 6 个文件并在资源管理器中单击“7-zip -> 存档并发送”,那么 7-zip 只对所有 6 个文件调用一次。探索者如何做到这一点?
如果我像网络中的示例一样调用 6 次“ShellExecuteEx”,它将产生 6 个单独的存档文件,每个存档文件仅包含 1 个文件,并且将发送 6 个存档文件。
请问,如何解决?
示例代码(从https://ghisler.ch/board/viewtopic.php?t=25228更改了一点点):
unit Unit2;
interface
uses
shellapi,
ShlObj,
comobj, activex,
Windows, Messages, SysUtils, Classes;
procedure ContextMenuForFileList(
X, Y : Integer; Handle : HWND;
lst:TStrings
);
implementation
function SHParseDisplayName(
pszName: PWideChar; // PCWSTR pszName,
const pbc: IBindCtx; // IBindCtx *pbc,
var ppidl: PItemIDList; // PIDLIST_ABSOLUTE *ppidl,
sfgaoIn: DWORD; //SFGAOF sfgaoIn,
var psfgaoOut: DWORD //SFGAOF *psfgaoOut
):HResult; stdcall; external 'Shell32.dll';
function SHGetIDListFromPath( v:WideString):PItemIDList; // ShellFolder);
var
sfgaoIn: uint;
sfgaoOut: uint;
begin
sfgaoIn:= 0;
sfgaoOut:= 0;
OleCheck(
SHParseDisplayName(
PWideChar( v),
nil,
Result,
sfgaoIn,
sfgaoOut
)
);
end;
procedure ContextMenuForFileList(
X, Y : Integer; Handle : HWND;
lst:TStrings
);
var
aContextMenu: IContextMenu;
aPrgOut: Pointer;
aPopup: HMENU;
aCmd: Integer;
aCmdInfo: TCMInvokeCommandInfo;
ShellFolder: IShellFolder;
I, Cnt: integer;
PIDL: array of PItemIDList;
begin
Cnt := lst.Count;
SetLength(PIDL, Cnt);
for I := 0 to Cnt - 1 do begin
//////
// PIDL[I] := SHGetIDListFromPath(lst[I], ShellFolder);
PIDL[I] := SHGetIDListFromPath( lst[I]);
if I<Cnt - 1 then
// ShellFolder.Release;
ShellFolder:= nil;
end;
OleCheck( SHGetDesktopFolder( ShellFolder));
for I := 0 to Cnt - 1 do
if not Assigned(PIDL[I]) then Exit; {Here we would need to free data...}
aPrgOut := nil;
OLECheck(ShellFolder.GetUIObjectOf(0, Cnt, PIDL[0],
IID_IContextMenu, aPrgOut, Pointer(aContextMenu)));
aPopup := CreatePopUpMenu;
if aPopup = 0 then Exit;
try
OLECheck(aContextMenu.QueryContextMenu(aPopup, 0, 1, $7FFF, CMF_NORMAL));
aCmd := Integer(TrackPopupMenuEx(aPopup,
TPM_LEFTALIGN or TPM_RETURNCMD
or TPM_RIGHTBUTTON or TPM_HORIZONTAL
or TPM_VERTICAL, X, Y, Handle, nil));
if aCmd <> 0 then begin
FillChar(aCmdInfo, Sizeof(aCmdInfo), 0);
with aCmdInfo do begin
cbSize := SizeOf(TCMInvokeCommandInfo);
lpVerb := MakeIntResourceA(aCmd - 1);
nShow := SW_SHOWNORMAL;
end;
try
aContextMenu.InvokeCommand(aCmdInfo);
except
end;
end;
finally
DestroyMenu(aPopup);
end;
end;
end.
{in the main unit:}
procedure TFormMain.Test;
var
lst: TStringList;
begin
lst:= TStringList.Create;
try
lst.Add( 'C:\temp\test.pdf');
ContextMenuForFileList(
0, //X,
0, //Y : Integer;
Handle,
lst
);
finally
lst.Free;
end;
end;
使用列表中唯一的一个 pdf 文件测试此代码,我得到一个不包含“Open Adobe ...”的菜单,但我得到了为目录(HKEY_CLASSES_ROOT\Folders)定义的仅(!)项目,但是不应出现在文件中。
==========================
更新:
==========================
在插入将“pidl”映射到相对路径的代码之后
_root.ParseDisplayName( ..., <directory of file>, .. _par_PIDL )
_root.BindToObject( _par_PIDL, ... _iDir);
_idir.ParseDisplayName( ... <file_name_without_path> ..., new_pidl)
并用 new_pidl 填充数组,但在 root 上调用“_root.GetUIObjectOf( ...”),我让它工作,菜单显示所有项目。
但是“打开 Adobe ...”失败并显示“找不到文件”。使用“C:\temp\folder1\a.bmp”进行测试并按下“open”,绘图显示“无法打开文件 C:\users\heinz\a.bmp”。
也许原因:
“SHGetDesktopFolder(_Root)”应该返回最上面的“IShellFolder”,可以访问计算机上的所有驱动器和文件,对吧?确实,检查_root的pidl,
var
_fld: IPersistFolder2;
begin
_fld:= _root as IPersistFolder2;
OleCheck( _fld.GetCurFolder( root_pidl));
root_pidl 为空。但测试这个“空ItemIDList”属于哪个路径
SHGetPathFromIDListA( root_pidl, buf)
然后我得到我的本地用户路径:C:\users\heinz 但不是真正的根“”。
==========================
更新 2:
==========================
经过更多调查,我发现了以下链接: https ://www.zabkat.com/blog/08Jul07.htm
它解释了问题并给了我有用的信息。修改了 Delphi 中的代码并进行了尝试。更进一步,但不会更多。听起来很容易只为文件提供一个正确扩展名的数组,对于 PDF 文件,fe "HKCR\.pdf"。还有什么?没用。
还尝试了其他类似“HKCR\ADOBE.Document.0”,但没有得到菜单项“Open Adobe ...”。
==========================
更新3:(扩展至c++,或许高手可以帮忙)
==========================
不知何故,它必须能够做到这一点,因为您可以在资源管理器中选择一些不同的文件,如果您选择顶级目录,请在搜索字段中搜索“*.pdf”并从不同的子目录中选择一些 pdf 文件。然后一切都按预期工作。
在一个程序中,我阅读了 Explorer 在两种情况下都提供的 IDataObject 并发现:
- 如果在资源管理器中仅选择 1 个文件,则方法“GetData()”返回(除了 CF_HDROP,...)还有 CF_SHELLIDLIST 的 cfFormat,并且使用 parentpidl 或 pidl 调用函数“SHGetPathFromIDListA()”给我正确的文件路径。
- 如果在资源管理器搜索视图中选择多个文件,则方法“GetData()”也会返回 CF_SHELLIDLIST 的 cfFormat,但“SHGetPathFromIDListA()”在 parentpidl 或 pidl 上都会崩溃。
但是您可以调用 SHCreateItemFromIDList() 并为两个 pidls 调用 refiid 为“IUnknown”。此外,“IShellItem”的 _shitm=Queryinterface() 为两个 pid 返回 S_OK。
_shitm.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, ..) 为 parentpidl 返回 'Searchresults in "dbg"&"*.pdf",
但在文件的 pidls 上失败。
==========================
更新 4:
==========================
毕竟我最终自己构建了整个菜单。
解决方案
推荐阅读
- ruby-on-rails - Ruby - Airbourne Rspec API 测试
- javascript - npm 错误!找不到模块'./access-error.js'
- android - 使用 lambda forEach Kotlin 遍历列表
- graphics - 碰撞检测的特例
- jupyter-notebook - jupyterhub 上的 Jupyterlab
- filesystems - 硬盘坏了
- java - Spring Boot Security & Vaadin 页面导航&渲染问题
- python-3.x - 如果字符串出现在数据框中,则增加一列
- c# - 创建没有外键的导航属性
- sql - SQL Server 不会将触发器事务回滚到保存点