Проблема.
Есть старое приложение, написанное на C++ Builder 5 и собранное на Windows XP. Все, что нужно о нем знать – в определенный момент программа показывает модальное окно с помощью функции ShowMessage(). Это простой и часто используемый прием вывода сообщения, например, об ошибке.

При запуске приложения на Windows Vista что-то пошло не так. В тот момент, когда должно было появится сообщение, основное окно стало заблокированным, но модального окна я не увидел. Единственный способ остановить программу – Alt+Ctr+Del.
Щелкаю правой кнопкой по exe-файлу, Свойства, Совместимость. Выбираю “Windows XP” и пробую запустить программу снова. На этот раз я увидел модальное окно, но лишь на полсекунды. Далее все то же самое – основное окно заблокировано, модального не видно.
Итак, проблема в том, что Vista показывает модальное окно некоторых старых программ за основным, таким образом, добраться до него и нажать-таки кнопку OK совершенно невозможно.
А мне действительно нужна была эта программа и сообщения, которые она выдавала.
Имеем перед глазами следующую картинку (окно заблокировано, тыкать в него мышкой бесполезно):

Задача.
Увидеть модальное окно и нажать в нем кнопку OK для продолжения выполнения программы.
Решение.
Может, было решение и проще, но я решил вооружиться WinAPI. Известно, что есть у Windows функция ShowWindow(). Она принимает дескриптор окна и команду. Команды бывают разные, мне же достаточно двух: спрятать окно и показать окно. Однако сначала мне нужно узнать дескриптор окна; но и это не проблема – WinAPI имеет средства для перебора окон.
Я решил написать консольное Win32 приложение, которое умело бы делать три действия:
- Получить дескрипторы окон с известным заголовком
- Спрятать окно с известным дескриптором
- Показать окно с известным дескриптором
Алгоритм при появлении модального окна:
- Узнать дескриптор основного окна приложения
- Спрятать его
- Прочитать сообщение и нажать кнопку OK в модальном окне
- Показать основное окно
- Продолжить работу
Для поиска окна существует функция FindWindow(), которая позволяет найти дескриптор по заголовку. Однако в моем случае с одним и тем же именем было сразу три окна: основное, модальное и то, что в панели задач. Более того – искать окно мне хотелось не по точному совпадению заголовка, а только по началу строки. То есть вместо полного “Ввод данных” я хочу писать, скажем, “Вво”. Функция EnumWindows() вполне удовлетворила мои требования.
Итак, консольное приложение готово (исходники ниже). Запускаю его, ввожу команду EnumWindows и первые символы заголовка:

Вот результат команды:

Беру первое найденное окно и посылаю ему команду “погаснуть”:

Теперь я вижу желанное модальное окно:

Читаю, нажимаю OK. Показываю основное окно:

И продолжаю работу.
Можете скачать консольное приложение и посмотреть исходники:
#include <windows.h>
#include <stdio.h>
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
char str[1024];
char oem[1204];
char* txt;
// Получаем заголовок очередного окна.
GetWindowTextA(hwnd, str, 1024);
// Переводим его в кодировку OEM.
CharToOemA(str, oem);
// Строка поиска.
txt = (char*)lParam;
// Ищем по совпадению начала строки.
if (strlen(str) > strlen(txt))
str[strlen(txt)] = 0;
// Если совпадает - выводим HWND.
if (!strcmp(str, txt))
printf("# hwnd = %d : %s\r\n", hwnd, oem);
// Продолжаем поиск.
return TRUE;
}
void CmdEnumWindows()
{
char title[1024];
// Запрос заголовка окна.
printf("> window title: ");
gets_s(title, 1024);
// Кодируем из OEM.
OemToCharA(title, title);
// Запуск поиска.
if (EnumWindows(EnumWindowsProc, (LPARAM)title))
printf("# OK\r\n");
else
printf("# ERROR\r\n");
}
void CmdShowWindow()
{
char tmp[32];
int hwnd;
int nCmdShow;
// Запрос HWND.
printf("> hwnd: ");
gets_s(tmp, 32);
hwnd = atoi(tmp);
// Запрос nCmdShow.
printf("> nCmdShow (SW_SHOW = 5, SW_HIDE = 0): ");
gets_s(tmp, 32);
nCmdShow = atoi(tmp);
// Вызов WinApi.
if (ShowWindow((HWND)hwnd, nCmdShow))
printf("# OK\r\n");
else
printf("# ERROR\r\n");
}
void main()
{
// Цикл обработки команд.
char cmd[256];
while (strcmp(cmd, "Exit"))
{
printf("commands: EnumWindows, ShowWindow, Exit\r\n");
printf("> ");
gets_s(cmd, 256);
if (!strcmp(cmd, "EnumWindows"))
{
CmdEnumWindows();
}
else if (!strcmp(cmd, "ShowWindow"))
{
CmdShowWindow();
}
}
}
P.S. Пока писал статью, понял, как решить проблему значительно проще. Уменьшить основное окно и сдвинуть к краю экрана… Но я все равно рад возможности вспомнить WinAPI и старый добрый Си
