Как переместить окно на передний план?

Автор: Александр Шаргин

Версия текста: 1.0

Многие знают, что для перемещения окна на передний план существует функция SetForegroundWindow из Win32 API (в MFC ей соответствует обёртка CWnd::SetForegroundWindow). Она отлично работала под Windows 95 и Window NT. Но потом парни из Майкрософт провозгласили новый принцип: "Никто кроме пользователя не имеет право выдвигать окно на передний план". И хотя их собственные продукты продолжают делать это при необходимости, функция SetForegroundWindow перестала работать, как раньше. Теперь только активный процесс (foreground process) может переместить окно на передний план с использовании этой функции, а окно фонового процесса начнёт "мерцать" на панели задач, чтобы привлечь внимание пользователя.

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

ПРИМЕЧАНИЕ
Микрософт постоянно занимается "затыканием дыр в своей обороне", и всё больше известных способов отказывает с выходом новых версий Windows.

Способ 1

Этот способ подразумевает использование недокументированной функции 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.

Способ 2

В 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.

Способ 3

Как утверждает документация, 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.


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