Как отобразить содержимое каталога в проводнике в виде миниатюр?

В приложении Delphi 10.4.2 win-32 VCL в Windows 10 я показываю содержимое каталога в проводнике 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;

Это работает. Но как я могу заставить Explorer показывать файлы в этом каталоге с помощью THUMBNAILS? Есть ли какие-либо параметры для этого, которые я мог бы использовать в этой процедуре?

Я много искал для этого, но ничего не нашел.


person user1580348    schedule 15.05.2021    source источник
comment
Вы можете открыть окно, найти его программно (возможно, используя этот подход), а затем получить интерфейс IFolderView и использовать его IFolderView::SetCurrentViewMode метод.   -  person Andreas Rejbrand    schedule 15.05.2021
comment
Кстати, это очень нетривиальный вопрос, связанный с оболочкой Windows, поэтому вы можете удалить ссылки на Delphi. Решение будет одинаковым для любого родного языка (C, C++, Rust, Go, Pascal, Delphi, ...).   -  person Andreas Rejbrand    schedule 15.05.2021
comment
Я добавил несколько тегов, чтобы ваш вопрос с большей вероятностью привлек внимание всех экспертов Win32.   -  person Andreas Rejbrand    schedule 15.05.2021
comment
@AndreasRejbrand Спасибо!   -  person user1580348    schedule 15.05.2021
comment
Go не является родным языком программирования. Его не хватает в отделе детерминированного разрушения. Это очень важно для COM, где управление ресурсами должно следовать строгому протоколу.   -  person IInspectable    schedule 16.05.2021
comment
Вернее, Go не является языком системного программирования. Его зависимость от Green Threading делает его плохо подходящим для системы, основанной на собственных потоках, таких как COM. Так что да, подойдет любой из вышеупомянутых языков, кроме Go.   -  person IInspectable    schedule 16.05.2021
comment
@IInspectable: Как видите, я не эксперт по Go! Спасибо за исправление.   -  person Andreas Rejbrand    schedule 16.05.2021


Ответы (3)


Вы хотите использовать IFolderView:: SetCurrentViewMode.

Вот пример C++ (с использованием ATL Visual Studio):

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;
}
person Simon Mourier    schedule 16.05.2021
comment
Нет ли здесь риска бесконечного цикла, если disp никогда не будет установлено значение, отличное от NULL? Тем не менее, это кажется очень простым подходом — я попробую его, когда вернусь домой сегодня вечером. - person Andreas Rejbrand; 16.05.2021
comment
@AndreasRejbrand - Теоретически это возможно, да, это пример кода, вы можете добавить накопленный максимальный тайм-аут. - person Simon Mourier; 16.05.2021
comment
Используя метод из Знаете ли вы, когда запускаются ваши деструкторы? Часть 1 позволяет отказаться от уровня вложенности. @и вам придется обвинить Алана Тьюринга в том, что он доказал, что общее решение проблемы остановки не может существовать. - person IInspectable; 16.05.2021
comment
@IInspectable - да, спасибо, я знаю всю работу Рэймонда, но мне всегда лень копировать этот код CCoInitialize, и я нахожу вложение/область видимости весьма практичным по многим другим причинам, включая избежание зависимостей при написании примера кода - person Simon Mourier; 16.05.2021
comment
Я только что реализовал это в Delphi и могу подтвердить, что это работает хорошо. (+1) - person Andreas Rejbrand; 17.05.2021
comment
@AndreasRejbrand - вы также должны добавить свой код в качестве ответа - person Simon Mourier; 17.05.2021

Нет, EXPLORER.EXE не имеет для этого параметра - он не имел его ни в Windows 7, ни один в Windows 10. В любом случае, доступных параметров на удивление мало.

Лучше всего запустить проводник через CreateProcessW()< /a> чтобы затем получить дескриптор своего основного потока и, наконец, найти новое окно. Затем вы можете манипулировать отдельными элементами управления, такими как список файлов. См. этот ответ на основе AutoIt: автоматизация проводника Windows — в основном используется IShellBrowser и (помимо Windows XP) IFolderView2.SetViewModeAndIconSize(), чтобы затем применить FVM_THUMBNAIL.

person AmigoJack    schedule 15.05.2021

Вот версия Delphi подхода, предоставленная Саймон Мурье:

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);

сопоставляются непосредственно с FOLDERVIEWMODEs. Обратите внимание, что ваша версия Windows может не поддерживать их все.

person Andreas Rejbrand    schedule 17.05.2021