1

In a Delphi 10.4.2 win-32 VCL Application in Windows 10, I show the content of a directory in Windows File Explorer using this code and passing a path e.g. C:\MyDirectory\:

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

This works. But how can I force Explorer to show the files in this directory using THUMBNAILS? Are there any parameters for this that I could use in this procedure?

I have searched a lot for this but did not find anything.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
user1580348
  • 5,721
  • 4
  • 43
  • 105
  • You can open the window, find it programmatically (maybe using [this](https://devblogs.microsoft.com/oldnewthing/?p=38393) approach) and then obtain a `IFolderView` interface and use its [`IFolderView::SetCurrentViewMode`](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifolderview-setcurrentviewmode) method. – Andreas Rejbrand May 15 '21 at 17:43
  • By the way, this is a very non-trivial question related to the Windows Shell, so you might want to remove the Delphi references. The solution will be the same in any native language (C, C++, Rust, Go, Pascal, Delphi, ...). – Andreas Rejbrand May 15 '21 at 18:15
  • I added a few tags so your question are more likely to get some attention from all Win32 experts. – Andreas Rejbrand May 15 '21 at 18:47
  • Go is not a native programming language. It's lacking in the deterministic destruction department. Quite important with COM, where resource management needs to follow a strict protocol. – IInspectable May 16 '21 at 10:18
  • Or rather, Go is not systems programming language. Its reliance on Green Threading makes it a poor fit for a system based on native threads, like COM. So, yes, any of the aforementioned languages will do just fine, except for Go. – IInspectable May 16 '21 at 10:26
  • @IInspectable: As you can see, I am not a Go expert! Thanks for the correction. – Andreas Rejbrand May 16 '21 at 10:45

3 Answers3

3

You want to use the IFolderView::SetCurrentViewMode method.

Here is a C++ (using Visual Studio's ATL) example:

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;
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Isn't there a risk of an infinite loop here if `disp` is never set to a non-NULL value? Still, this seems like a very straight-forward approach -- I'll try it when I get back home tonight. – Andreas Rejbrand May 16 '21 at 10:55
  • @AndreasRejbrand - In theory it could, yes, this is sample code, you could add a cumulated max timeout. – Simon Mourier May 16 '21 at 11:38
  • Using the technique from [Do you know when your destructors run? Part 1](https://devblogs.microsoft.com/oldnewthing/20040520-00/?p=39243) allows you to drop a level of nesting. @and You're going to have to blame Alan Turing for proving that a general solution to the [halting problem](https://en.wikipedia.org/wiki/Halting_problem) cannot exist. – IInspectable May 16 '21 at 16:13
  • @IInspectable - yes, thanks, I know all of Raymond's work, but I'm always lazy to copy back this CCoInitialize code, and I find nesting/scoping quite practical for lots of other reasons, including avoiding dependencies when writing sample code – Simon Mourier May 16 '21 at 16:22
  • Fair enough, particularly with this RAII helper, as I've never found a way to silence the compiler's *"unused variable"* warning. Maybe I just never picked the right spot to drop the [`[[maybe_unused]]`](https://en.cppreference.com/w/cpp/language/attributes/maybe_unused) attribute. – IInspectable May 16 '21 at 16:34
  • I just implemented this in Delphi and can confirm that it works well. (+1) – Andreas Rejbrand May 16 '21 at 21:47
  • @AndreasRejbrand - you should add your code as an answer too – Simon Mourier May 16 '21 at 22:13
1

Here's a Delphi version of the approach given by 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;

Instead of sleep-polling indefinitely for the window (and potentially killing the application!), I give up after 3 seconds.

Example usage:

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

The view modes,

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

are mapped directly to Windows' FOLDERVIEWMODEs. Please note that your version of Windows might not support all of them.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
0

No, EXPLORER.EXE has no parameter for this - it neither had one in Windows 7, nor does it have one in Windows 10. There are surprisingly few parameters available anyway.

Your best bet is starting the Explorer thru CreateProcessW() to then obtain the handle of its main thread and finally find the new window. Then you can manipulate individual controls, such as the file list. See this answer, based on AutoIt: Automating Windows Explorer - it basically uses IShellBrowser and (beyond Windows XP) IFolderView2.SetViewModeAndIconSize() to then apply FVM_THUMBNAIL.

AmigoJack
  • 5,234
  • 1
  • 15
  • 31
  • That's not how you monitor creation of an Explorer window. See [A big little program: Monitoring Internet Explorer and Explorer windows, part 1: Enumeration](https://devblogs.microsoft.com/oldnewthing/20130610-00/?p=4133), [A big little program: Monitoring Internet Explorer and Explorer windows, part 2: Tracking navigations](https://devblogs.microsoft.com/oldnewthing/20130613-00/?p=4093), and [A big little program: Monitoring Internet Explorer and Explorer windows, part 3: Tracking creation and destruction](https://devblogs.microsoft.com/oldnewthing/20130614-00/?p=4083). – IInspectable May 16 '21 at 08:33