Многие знают, что для перемещения окна на передний план существует функция SetForegroundWindow из Win32 API (в MFC ей соответствует обёртка CWnd::SetForegroundWindow). Она отлично работала под Windows 95 и Window NT. Но потом парни из Майкрософт провозгласили новый принцип: "Никто кроме пользователя не имеет право выдвигать окно на передний план". И хотя их собственные продукты продолжают делать это при необходимости, функция SetForegroundWindow перестала работать, как раньше. Теперь только активный процесс (foreground process) может переместить окно на передний план с использовании этой функции, а окно фонового процесса начнёт "мерцать" на панели задач, чтобы привлечь внимание пользователя.
В общем случае не рекомендуется нарушать правила работы пользовательского интерфейса, предписанные Микрософт. Как правило, окно, выпрыгивающее из ниоткуда, только раздражает пользователя. Тем не менее, в некоторых приложениях бывает необходимо добиться именно такого поведения. Рассмотрим несколько способов достижения требуемого.
ПРИМЕЧАНИЕ Микрософт постоянно занимается "затыканием дыр в своей обороне", и всё больше известных способов отказывает с выходом новых версий Windows. |
Этот способ подразумевает использование недокументированной функции SwitchToThisWindow, описание которой дал Ашот Оганесян (подробности можно найти здесь). Эта функция получает два параметра: первый - дескриптор окна, а второй - TRUE или FALSE, в зависимости от того, нужно ли восстановить минимизированное окно.
SwitchToThisWindow входит в user32.dll, но не включена в библиотеку импорта, поэтому необходимо получить её адрес с помощью GetProcAddress. Предварительно нужно загрузить библиотеку user32.dll или (если вы уверены, что она уже загружена - в большинстве случаев она загружается при запуске приложения) получить её дескриптор с помощью GetModuleHandle. Например:
// hWnd - дескриптор окна. HMODULE hLib = GetModuleHandle("user32.dll"); void (__stdcall *SwitchToThisWindow)(HWND, BOOL); (FARPROC &)SwitchToThisWindow = GetProcAddress(hLib, "SwitchToThisWindow"); SwitchToThisWindow(hWnd, TRUE); |
К сожалению, практика показывает, что последних версиях Windows 98/2000 эта функция ведёт себя аналогично SetForegroundWindow.
Следующие два способа описаны на сайте Боба Мура, одного из Microsoft MVP.
В Windows 98/2000 появился новый системный параметр FOREGROUNDLOCKTIMEOUT. Он задаёт интервал времени, который должен пройти с момента последнего пользовательского ввода, прежде чем приложение сможет выдвинуть своё окно на передний план. По истечении этого интервала SetForegroundWindow работает, как нам требуется. Микрософт имеет в виду, что окно не может "выпрыгнуть", когда пользователь набирает что-то на клавиатуре, но может на законных основаниях появиться, если он просто сидит и наблюдает происходящее на экране.
Параметр FOREGROUNDLOCKTIMEOUT можно изменить программно, для этого предназначена функция SystemParametersInfo. А значит, мы можем установить интервал в ноль, вызвать SetForegroundWindow, а затем восстановить исходное значение интервала. Вот фрагмент кода, который выполняет задуманное.
DWORD dwTimeout; SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, &dwTimeout, 0); SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, 0, 0); // hWnd - дескриптор окна. SetForegroundWindow(hWnd); SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, (LPVOID)dwTimeout, 0); |
ПРИМЕЧАНИЕ Константы SPI_*FOREGROUNDLOCKTIMEOUT описаны в windows.h, но будут доступны только при компиляции под Windows 98/2000. Другими словами, должен быть задан макрос: #define WINVER 0x0500 Если этого делать не хочется (или если вы используете устаревшие версии заголовков), вы можете описать необходимые константы прямо в программе, включив в код следующие макросы: #define SPI_GETFOREGROUNDLOCKTIMEOUT 0x2000 #define SPI_SETFOREGROUNDLOCKTIMEOUT 0x2001 |
Описанный метод успешно работает под Windows 98, но отказывает под Windows 2000.
Как утверждает документация, SetForegroundWindow работает как нам нужно, только если вызывающий её процесс является активным. А активен тот процесс, чей поток обрабатывает пользовательский ввод. Оказывается, наш процесс может "прикинуться" активным, подключив свой поток к обработке пользовательского ввода. Это осуществляется при помощи функции AttachThreadInput. После вызова SetForegroundWindow можно будет отключиться от чужого потока, используя ту же функцию (но передавая в качестве третьего параметра FALSE, а не TRUE).
Реализация этой идеи выглядит так.
HWND hCurrWnd; int iMyTID; int iCurrTID; hCurrWnd = ::GetForegroundWindow(); iMyTID = GetCurrentThreadId(); iCurrTID = GetWindowThreadProcessId(hCurrWnd,0); AttachThreadInput(iMyTID, iCurrTID, TRUE); // hWnd - дескриптор окна. SetForegroundWindow(hWnd); AttachThreadInput(iMyTID, iCurrTID, FALSE); |
Моя практика показывает, что описанный метод успешно работает как под Windows 98/Me, так и под Windows 2000.