【问题标题】:How to show the content of a directory in File Explorer as thumbnails?如何在文件资源管理器中将目录的内容显示为缩略图?
【发布时间】:2021-08-05 12:11:41
【问题描述】:

在 Windows 10 的 Delphi 10.4.2 win-32 VCL 应用程序中,我使用此代码在 Windows 文件资源管理器中显示目录的内容并传递路径,例如C:\MyDirectory\:

procedure ShellOpen(const Url: string; const Params: string = '');
begin
  Winapi.ShellAPI.ShellExecute(0, 'Open', PChar(Url), PChar(Params), nil, SW_SHOWNORMAL);
end;

这行得通。但是如何强制资源管理器使用 THUMBNAILS 显示此目录中的文件?是否有任何参数可供我在此过程中使用?

我为此搜索了很多,但没有找到任何东西。

【问题讨论】:

  • 你可以打开窗口,以编程方式找到它(可能使用this方法),然后获取一个IFolderView接口并使用它的IFolderView::SetCurrentViewMode方法。
  • 顺便说一句,这是一个与 Windows Shell 相关的非常重要的问题,因此您可能希望删除 Delphi 引用。任何本地语言(C、C++、Rust、Go、Pascal、Delphi 等)的解决方案都是相同的。
  • 我添加了一些标签,因此您的问题更有可能得到所有 Win32 专家的关注。
  • @AndreasRejbrand 谢谢!
  • Go 不是原生编程语言。确定性破坏部门缺乏它。对于 COM 来说非常重要,其中资源管理需要遵循严格的协议。

标签: windows winapi directory windows-shell windows-explorer


【解决方案1】:

您想使用IFolderView::SetCurrentViewMode 方法。

这是一个 C++(使用 Visual Studio 的 ATL)示例:

int main()
{
    CoInitialize(NULL);
    {
        // get a shell item
        CComPtr<IShellItem> folder;
        ATLASSERT(SUCCEEDED(SHCreateItemFromParsingName(L"c:\\myPath1\myPath2", nullptr, IID_PPV_ARGS(&folder))));

        // get its PIDL
        CComHeapPtr<ITEMIDLIST> pidl;
        ATLASSERT(SUCCEEDED(CComQIPtr<IPersistIDList>(folder)->GetIDList(&pidl)));

        // open the item
        SHELLEXECUTEINFO info = { };
        info.cbSize = sizeof(info);
        info.fMask = SEE_MASK_IDLIST;
        info.nShow = SW_SHOW;
        info.lpIDList = pidl;
        ATLASSERT(ShellExecuteEx(&info));

        // build a variant from the PIDL
        UINT size = ILGetSize(pidl);
        SAFEARRAY* psa = SafeArrayCreateVector(VT_UI1, 0, size);
        CopyMemory(psa->pvData, pidl, size);
        CComVariant v;
        v.parray = psa;
        v.vt = VT_ARRAY | VT_UI1;

        // find the opened window
        CComPtr<IShellWindows> windows;
        ATLASSERT(SUCCEEDED(windows.CoCreateInstance(CLSID_ShellWindows)));

        CComVariant empty;
        long hwnd;
        CComPtr<IDispatch> disp;
        do
        {
            windows->FindWindowSW(&v, &empty, SWC_BROWSER, &hwnd, SWFO_NEEDDISPATCH, &disp);
            if (disp)
                break;

            // we sleep for a while but using events would be better
            // see https://stackoverflow.com/a/59974072/403671
            Sleep(500);
        } while (true);

        // get IFolderView
        CComPtr<IFolderView> view;
        ATLASSERT(SUCCEEDED(IUnknown_QueryService(disp, IID_IFolderView, IID_PPV_ARGS(&view))));

        // change view mode
        ATLASSERT(SUCCEEDED(view->SetCurrentViewMode(FOLDERVIEWMODE::FVM_THUMBNAIL)));
    }

    CoUninitialize();
    return 0;
}

【讨论】:

  • 如果disp 从未设置为非NULL 值,这里是否存在无限循环的风险?不过,这似乎是一种非常直接的方法——我今晚回家后会试试看。
  • @AndreasRejbrand - 理论上可以,是的,这是示例代码,您可以添加累积的最大超时时间。
  • 使用Do you know when your destructors run? Part 1 中的技术可以降低嵌套级别。 @and 你将不得不责怪艾伦图灵证明halting problem 的通用解决方案不存在。
  • @IInspectable - 是的,谢谢,我知道 Raymond 的所有工作,但我总是懒得复制这个 CCoInitialize 代码,而且我发现嵌套/作用域非常实用,还有很多其他原因,包括编写示例代码时避免依赖
  • 很公平,尤其是使用这个 RAII 助手,因为我从来没有找到一种方法来消除编译器的 "unused variable" 警告。也许我只是没有选择正确的位置来删除[[maybe_unused]] 属性。
【解决方案2】:

不,EXPLORER.EXE 没有这个参数 - 它是neither had one in Windows 7, nor does it have one in Windows 10。无论如何,可用的参数非常少。

最好的办法是通过 CreateProcessW() 启动 Explorer 到 obtain the handle of its main thread and finally find the new window。然后您可以操作单个控件,例如文件列表。参见this answer,基于AutoIt: Automating Windows Explorer - 它基本上使用IShellBrowser 和(除了Windows XPIFolderView2.SetViewModeAndIconSize() 然后应用FVM_THUMBNAIL

【解决方案3】:

这是 approach 的 Delphi 版本,由 Simon Mourier 提供:

uses
  ComObj, ShellAPI, ShlObj, ActiveX, SHDocVw, ShLwApi;

function IUnknown_QueryService(punk: IUnknown; const guidService: TGUID;
  const IID: TGUID; out Obj): HRESULT; stdcall; external 'ShLwApi'
  name 'IUnknown_QueryService';

type
  TFolderViewMode = (fvmAuto, fvmIcon, fvmSmallIcon, fvmList, fvmDetails,
    fvmThumbnail, fvmTile, fvmThumbstrip, fvmContent);

procedure OpenFolder(AHandle: HWND; const AFolder: string; AViewMode: TFolderViewMode);
const
  FolderViewModes: array[TFolderViewMode] of Cardinal =
    (Cardinal(FVM_AUTO), FVM_ICON, FVM_SMALLICON, FVM_LIST, FVM_DETAILS,
     FVM_THUMBNAIL, FVM_TILE, FVM_THUMBSTRIP, FVM_CONTENT);
var
  ShellItem: IShellItem;
  PIDL: PItemIDList;
  SEInfo: TShellExecuteInfo;
  ILSize: Cardinal;
  SafeArray: PSafeArray;
  v: OleVariant;
  ShellWindows: IShellWindows;
  ExplorerHWND: Integer;
  disp: IDispatch;
  view: IFolderView;
  dummy: OleVariant;
begin

  OleCheck(CoInitialize(nil));
  try

    OleCheck(SHCreateItemFromParsingName(PChar(AFolder), nil, IShellItem, ShellItem));
    try

      OleCheck((ShellItem as IPersistIDList).GetIDList(PIDL));
      try

        ZeroMemory(@SEInfo, SizeOf(SEInfo));
        SEInfo.cbSize := SizeOf(SEInfo);
        SEInfo.Wnd := AHandle;
        SEInfo.fMask := SEE_MASK_IDLIST;
        SEInfo.nShow := SW_SHOW;
        SEInfo.lpIDList := PIDL;
        Win32Check(ShellExecuteEx(@SEInfo));

        ILSize := ILGetSize(PIDL);
        SafeArray := SafeArrayCreateVector(VT_UI1, 0, ILSize);

        CopyMemory(SafeArray.pvData, PIDL, ILSize);
        PVariantArg(@v).vt := VT_ARRAY or VT_UI1;
        PVariantArg(@v).parray := SafeArray;

        OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER,
          IShellWindows, ShellWindows));
        try
          dummy := Unassigned;
          var c: Integer := 0;
          repeat
            if c > 0 then
              Sleep(200);
            disp := ShellWindows.FindWindowSW(v, dummy, SWC_BROWSER, ExplorerHWND,
              SWFO_NEEDDISPATCH);
            Inc(c);
          until Assigned(disp) or (c > 15);
          if disp = nil then
            Exit;
          OleCheck(IUnknown_QueryService(disp, IFolderView, IFolderView, view));
          try
            OleCheck(view.SetCurrentViewMode(FolderViewModes[AViewMode]));
          finally
            view := nil;
          end;
        finally
          ShellWindows := nil;
        end;

      finally
        CoTaskMemFree(PIDL);
      end;

    finally
      ShellItem := nil;
    end;

  finally
    CoUninitialize;
  end;

end;

我没有无限期地对窗口进行睡眠轮询(并可能杀死应用程序!),而是在 3 秒后放弃。

示例用法:

procedure TForm1.Button1Click(Sender: TObject);
begin
  OpenFolder(Handle, 'C:\Users\Andreas Rejbrand\Skrivbord\Test', fvmThumbnail);
end;

视图模式,

type
  TFolderViewMode = (fvmAuto, fvmIcon, fvmSmallIcon, fvmList, fvmDetails,
    fvmThumbnail, fvmTile, fvmThumbstrip, fvmContent);

直接映射到 Windows 的FOLDERVIEWMODEs。请注意,您的 Windows 版本可能不支持所有这些。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-03
    • 2010-10-20
    • 2010-09-25
    • 1970-01-01
    • 2012-11-22
    • 1970-01-01
    相关资源
    最近更新 更多