首页 > 解决方案 > 如何处理具有不同文件夹的 IShellFolder 菜单或具有多个文件的 ShellExecuteEx

问题描述

网上有很多关于这个问题的分析器,但我找不到解决方案。

fe 两个文件:'C:\user\doc1.pdf'、'C:\custom\doc.pdf'

对于第一个文件:当请求指向整个路径“C:\user”的相对“IShellfolder _fld1”并使用“_fld1.ParseDisplayName(..., 'doc1.pdf', ...)”获取 PIDLIST 时菜单将显示所有菜单项,就像在资源管理器中一样。fe(安装了 Adob​​e)我得到“使用 Adob​​e Acrobat DC 打开”。

但是当使用绝对路径到桌面时,例如“IShellfolder _root”并使用“_root.ParseDisplayName(..., 'C:\user\doc1.pdf', ...)”查询 PIDList 则不会发生错误,一些菜单-显示了项目(fe“副本”),但缺少一些项目(fe“使用 Adob​​e 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 Adob​​e ...”的菜单,但我得到了为目录(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( ...”),我让它工作,菜单显示所有项目。

但是“打开 Adob​​e ...”失败并显示“找不到文件”。使用“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 Adob​​e ...”。

==========================

更新3:(扩展至c++,或许高手可以帮忙)

==========================

不知何故,它必须能够做到这一点,因为您可以在资源管理器中选择一些不同的文件,如果您选择顶级目录,请在搜索字段中搜索“*.pdf”并从不同的子目录中选择一些 pdf 文件。然后一切都按预期工作。

在一个程序中,我阅读了 Explorer 在两种情况下都提供的 IDataObject 并发现:

  1. 如果在资源管理器中仅选择 1 个文件,则方法“GetData()”返回(除了 CF_HDROP,...)还有 CF_SHELLIDLIST 的 cfFormat,并且使用 parentpidl 或 pidl 调用函数“SHGetPathFromIDListA()”给我正确的文件路径。
  2. 如果在资源管理器搜索视图中选择多个文件,则方法“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:

==========================

毕竟我最终自己构建了整个菜单。

标签: c++shelldelphi

解决方案


推荐阅读