Как я могу наблюдать за жизненным циклом окна Проводника или даже управлять им?

Клиент обратился с просьбой помочь реализовать наблюдение за жизненным циклом окна Проводника.

Мы хотим запустить экземпляр Проводника, отображающего определенную папку, а затем подождать, пока пользователь закроет ее, прежде чем продолжить выполнение программы. Мы пытались запустить экземпляр Проводника, передавая ему в качестве аргумента командной строки нужную папку, а затем выполнить функцию WaitForSingleObject над полученным дескриптором процесса, но иногда функция завершала свою работу сразу же, без ожидания. Как нам дождаться того момента, когда пользователь закроет окно Проводника?

Это еще один случай решения проблемы наполовину и попадания впросак со второй половиной проблемы.

Причина немедленного возвращения управления функцией WaitForSingleObject заключается в том, что Проводник — это программа, запускающаяся только в одном экземпляре (хорошо, в ограниченном количестве экземпляров). Когда вы открываете окно Проводника, запрос перенаправляется запущенной копии Проводника, и та копия, которую запустили вы, тут же завершает свою работу. Поэтому функция WaitForSingleObject возвращает управление немедленно.

К счастью, клиент решил объяснить также и исходную задачу.

У нас есть мастер, который создает некоторые файлы в директории, основываясь на предоставленной пользователем информации. Мы хотим запустить Проводник, чтобы он отобразил эту директорию и пользователь смог проверить, что все в порядке. Затем пользователь закрывает окно Проводника и мы спрашиваем его, все ли его устраивает, и если нет, мы отменяем все результаты операции и даем пользователю возможность начать все сначала.

Ага, программа использует Проводник в качестве подпрограммы «покажи-ка ненадолго вот эту папку». К сожалению, Проводник так не работает. К примеру, пользователь может воспользоваться адресной строкой и пойти посмотреть какие-нибудь другие папки, никак не связанные с вашей программой, а она будет просто сидеть и ждать, пока пользователь закроет это окно. А пользователь тем временем может и не догадываться, что программа ждет закрытия этого окна.

Вместо этого вы можете разместить элемент управления браузера Windows Explorer прямо внутри страницы вашего мастера и управлять им при помощи интрефейсов, вроде IExplorerBrowser. Вы также можете отключить навигацию внутри этого элемента (таким образом, пользователю будет доступна для просмотра только та папка, которая вам нужна), и пользователь сможет нажать на кнопку «Назад», если его что-то не устроило, или «Вперед», если он счастлив и хочет продолжить работу. В этом варианте есть еще и дополнительное преимущество, заключающееся в том, что все части вашего мастера находятся внутри одного и того же элемента, что позволяет пользователям работать с единой моделью навигации мастера, с которой они уже знакомы.

Пример программы, которая использует элемент управления браузера Windows Explorer, вы сможете найти в наборе средств разработки Platform SDK.

Для самых нетерпеливых вот версия такой программы на основе эталонного примера. Имейте в виду, что это минимальная версия программы. В реальной жизни вы, наверняка, захотели бы установить какие-нибудь дополнительные настройки или еще что-нибудь в этом роде.

#include <shlobj.h><br>IExplorerBrowser *g_peb; void OnSize(HWND hwnd, UINT state, int cx, int cy) {     if (g_peb) {<br> RECT rc = { 0, 0, cx, cy };<br> g_peb->SetRect(NULL, rc);<br> } } BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) {     BOOL fSuccess = FALSE;<br> RECT rc;<br> PIDLIST_ABSOLUTE pidl = NULL;<br> if (SUCCEEDED(CoCreateInstance(CLSID_ExplorerBrowser, NULL,<br> CLSCTX_INPROC, IID_PPV_ARGS(&g_peb))) &&<br> GetClientRect(hwnd, &rc) &&<br> SUCCEEDED(g_peb->Initialize(hwnd, &rc, NULL)) &&<br> SUCCEEDED(SHParseDisplayName(<br> L"C:\\Program Files\\Internet Explorer",<br> NULL, &pidl, 0, NULL)) &&<br> SUCCEEDED(g_peb->SetOptions(EBO_NAVIGATEONCE)) &&<br> SUCCEEDED(g_peb->BrowseToIDList(pidl, SBSP_ABSOLUTE))) {<br> fSuccess = TRUE;<br> }<br> ILFree(pidl);<br> return fSuccess; } void OnDestroy(HWND hwnd) {     if (g_peb) {<br> g_peb->Destroy();<br> g_peb->Release();<br> }     PostQuitMessage(0); }

Аналогичная техника размещения элемента управления браузера Windows Explorer может быть использована и в других сценариях из серии «сделай свое буритто (мексиканская лепешка с мясной начинкой)». К примеру, вы можете разместить этот элемент управления в своем окне и сказать пользователям, чтобы они копировали файлы в это окно. Когда они нажмут кнопку «ОК» или «Далее» или еще что-нибудь в этом роде, вы можете получить список элементов в этой папке и выполнить над ними нужные вам операции.

Теперь, вооруженные этими знаниями, вы способны ответить на следующие вопросы клиента:

Мы заметили, что состояние процесса Explorer.exe переходит в сигнальное состояние до того, как процесс будет завершен. Вот пример программы:

int _tmain(int argc, TCHAR **argv) {   STARTUPINFO si = { sizeof(si) };   PROCESS_INFORMATION pi;   if (CreateProcess(TEXT("C:\\Windows\\Explorer.exe"), TEXT(" /e,C:\\usr"),                    NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {     WaitForSingleObject(pi.hProcess);     CloseHandle(pi.hProcess);     CloseHandle(pi.hThread);   }   return 0; }

Однако если мы изменим «Explorer.exe» на «Notepad.exe», то дескриптор процесса перейдет в сигнальное состояние лишь после закрытия Блокнота, как и ожидалось.

У нас есть 32-битное расширение оболочки системы, для которой отсутствует 64-битная версия. Поскольку наши клиенты пользуются 64-битной версией Windows, 32-битное расширение оболочки недоступно в Проводнике. Как нам получить доступ к его контекстному меню?

У нас есть расширение оболочки, которое не совместимо с системой контроля учетных записей (UAC). Для корректной его работы требуется, чтобы у пользователя были права администратора. Мы бы не хотели бы поголовно отключать UAC только ради одного этого расширения оболочки. Есть ли какой-нибудь способ, позволяющий временно запустить Проводник с повышенными привилегиями?

Дополнительный пример программы: «Поиск с использованием элемента управления браузера Windows Explorer» показывает, как можно фильтровать содержимое элемента.

Дополнительная альтернатива: если вы действительно хотите увидеть окно Windows Explorer вместо размещения его внутри своего окна, вы можете использовать объект ShellWindows. Эту тему я освещал много лет тому назад (после чего была еще и гораздо более короткая скриптовая версия решения задачи).