лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows
.pdf
Модификация характеристик окна |
51 |
|
|
|
|
Таблица 1.13. Часто используемые сообщения |
|
|
|
|
|
Сообщение |
Назначение |
|
|
|
|
WM_CLOSE |
Уведомляет окно о том, что оно должно быть закрыто. Сообщение может |
|
|
быть использовано для вывода запроса пользователю с предложением |
|
|
подтвердить завершение работы, прежде чем будет вызвана функция |
|
|
DestroyWindow. По умолчанию (если обработка WM_CLOSE не предусмо- |
|
|
трена) DefWindowProc вызывает DestroyWindow |
|
WM_COMMAND |
Сообщение посылается, когда пользователь выбирает команду меню или |
|
|
посылает команду из элемента управления |
|
WM_CREATE |
Посылается, когда приложение создает окно вызовом функции CreateWindow |
|
|
или CreateWindowEx. Оконная процедура получает это сообщение, когда |
|
|
окно уже создано, но еще не показано на экране. Поэтому, обрабатывая это |
|
|
сообщение, можно изменить характеристики окна либо выполнить некото- |
|
|
рые инициализирующие действия, например открыть необходимые файлы. |
|
|
После обработки сообщения приложение должно вернуть нулевое значение |
|
|
для продолжения процесса создания окна. Если приложение вернет зна- |
|
|
чение –1, то окно не будет создано, а функция CreateWindow вернет |
|
|
значение NULL |
|
WM_DESTROY |
Посылается оконной процедуре уничтожаемого окна после того, как окно |
|
|
удалено с экрана |
|
WM_INITDIALOG |
Сообщение посылается оконной процедуре диалогового окна1 непосредствен- |
|
|
но перед тем, как оно будет отображено на экране. Обработка этого сообще- |
|
|
ния позволяет произвести инициализацию тех объектов, которые связаны |
|
|
с диалоговым окном |
|
WM_MOVE |
Посылается окну, которое переместилось на экране |
|
WM_PAINT |
Посылается окну, содержимое которого требует перерисовки |
|
WM_SIZE |
Посылается окну, размеры которого изменились |
|
WM_TIMER |
Уведомляет окно о том, что некоторый системный таймер, установленный |
|
|
функцией SetTimer, отсчитал заданный ему интервал |
|
|
|
|
Модификация характеристик окна
Если окно уже создано, например, в виде объекта класса KWnd, то его характерис тики могут быть изменены с помощью одной из двух функций: SetClassLong или
SetWindowLong.
Рассмотрим применение функции SetClassLong, предназначенной для измене ния одного из полей структуры WNDCLASSEX. Напомним, что указатель на эту струк туру с начальными значениями ее полей передавался функции RegisterClassEx для регистрации класса окна.
Функция SetClassLong имеет следующий прототип:
DWORD SetClassLong( |
|
|
HWND hWnd, |
// |
дескриптор окна |
int nIndex, |
// индекс значения, которое нужно изменить |
|
LONG dwNewLong |
// |
новое значение |
); |
|
|
1 Диалоговые окна рассматриваются в главе 7.
52 Глава 1. «Hello, World!», или Первые шаги к пониманию концепции Windows
Здесь nIndex — смещение (с отсчетом от нуля) дополнительных данных1 класса, которые должны быть изменены, или одно из значений, приведенных в табл. 1.14.
Таблица 1.14. Значения параметра nIndex функции SetClassLong
Значение |
Описание |
|
|
GCL_HBRBACKGROUND |
Изменить дескриптор кисти цвета фона |
GCL_HCURSOR |
Изменить дескриптор курсора |
GCL_HICON |
Изменить дескриптор пиктограммы |
GCL_HICONSM |
Изменить дескриптор малой пиктограммы |
GCL_MENUNAME |
Изменить адрес имени ресурса меню |
GCL_STYLE |
Изменить стиль класса окна |
GCL_WNDPROC |
Изменить адрес оконной процедуры, связанной с классом |
|
|
Покажем на примере проекта Hello2, как можно модифицировать характерис тики главного окна приложения.
Добавьте в текст функции WndProc, а именно в блок оператора switch, следую щие строки:
case WM_CREATE:
SetClassLong(hWnd, GCL_HBRBACKGROUND,
(LONG) CreateSolidBrush(RGB(200,160,255)));
break;
Посмотрите, как изменится вид окна приложения после сделанной модифи кации2.
А теперь добавьте после строки
hDC = BeginPaint(hWnd, &ps);
следующую строку:
SetBkMode(hDC, TRANSPARENT);
и посмотрите на полученный результат.
Особенности программирования для Windows
В разделе «Базовые концепции» уже говорилось о том, что в основе взаимодей ствия приложения и операционной системы лежит механизм сообщений. Для каж дого приложения Windows создает очередь сообщений приложения и направляет
вэту очередь все сообщения, адресованные окнам приложения. В то же время
вописании функции UpdateWindow (см. табл. 1.12) отмечается, что в процессе ее выполнения Windows посылает сообщение WM_PAINT непосредственно оконной процедуре, минуя очередь сообщений приложения. Внимательный читатель на верняка заметил это противоречие. На самом деле противоречия нет, а есть два типа сообщений, с которыми работает Windows.
1 Дополнительные данные — это те байты, количество которых задано в поле cbClsExtra.
2Вызов функции CreateSolidBrush (RGB (200, 160, 255)) создает сплошную кисть, цвет которой обра зован тремя цветовыми составляющими заданной интенсивности: красной (200), зеленой (160) и синей (255). В результате должен получиться сиреневый цвет. Более подробно создание кисти с использованием функции CreateSolidBrush рассматривается в главе 2.
Особенности программирования для Windows |
53 |
|
|
Синхронные и асинхронные сообщения
Асинхронными сообщениями называются сообщения, которые Windows помещает в очередь сообщений приложения. Такие сообщения извлекаются и диспетчери зуются в цикле обработки сообщений.
Синхронные сообщения передаются непосредственно окну, когда Windows вы зывает оконную процедуру.
Приведем примеры асинхронных сообщений. Прежде всего, к ним относятся сообщения о событиях пользовательского ввода, таких как нажатие клавиш (WM_KEYDOWN и WM_KEYUP), перемещение мыши (WM_MOUSEMOVE) или щелчок левой кнопкой мыши (WM_LBUTTONDOWN). Кроме этого, асинхронными являют ся сообщения от таймера (WM_TIMER), сообщение о необходимости перерисовки клиентской области (WM_PAINT) и сообщение о выходе из программы (WM_QUIT). Приложение может само направить в очередь асинхронное сообщение, вызвав функцию PostMessage.
Остальные сообщения, как правило, являются синхронными. Во многих слу чаях синхронные сообщения являются результатом обработки асинхронных со общений. Вообще, когда синхронное сообщение обрабатывается функцией DefWindowProc, Windows часто генерирует другие сообщения, направляемые окон ной процедуре. Приложение также может послать синхронное сообщение, выз вав функцию SendMessage.
Таким образом, оконная процедура должна быть повторно входимой (reentrant program). Это означает, что Windows часто вызывает функцию WndProc с новым сообщением, появившимся в результате вызова DefWindowProc из WndProc при об работке предыдущего сообщения. В большинстве случаев повторная входимость оконной процедуры не создает каких то особых проблем, но знать об этом по лезно.
Рассмотрим, например, какие события произойдут после щелчка кнопкой мыши на кнопке закрытия окна приложения Hello1. Все начнется с того, что Windows отправит синхронное сообщение WM_SYSCOMMAND оконной процедуре WndProc. Оконная процедура передаст это сообщение на обработку функции DefWindowProc. Функция DefWindowProc реагирует на него, отправляя сообщение WM_CLOSE оконной процедуре. В рассматриваемом примере предусмотрена об работка этого сообщения — вызывается функция DestroyWindow. Однако если не предусмотреть эту обработку, то функция DefWindowProc сделала бы то же самое, то есть вызвала бы функцию DestroyWindow. Функция DestroyWindow заставляет Windows отправить оконной процедуре сообщение WM_DESTROY. И наконец, WndProc, обрабатывая это сообщение, вызывает функцию PostQuitMessage, кото рая посылает асинхронное сообщение WM_QUIT в очередь сообщений приложе ния. Сообщение WM_QUIT прерывает цикл обработки сообщений в WinMain, и приложение завершает свою работу.
Сообщения не похожи на аппаратные прерывания. Пока оконная процедура обрабатывает одно сообщение, программа не может быть прервана другим сооб щением. Только в том случае, когда функция, выполняемая в теле оконной проце дуры, генерирует новое синхронное сообщение, оно вызывает повторно оконную процедуру, и только после его обработки выполнение прерванной функции про д о л ж а е т с я .
54 Глава 1. «Hello, World!», или Первые шаги к пониманию концепции Windows
Посылка сообщений из приложения
Основным средством программного взаимодействия между разными окнами при ложения и даже между разными приложениями является посылка сообщений. Приведем краткие сведения об основных функциях, используемых для отправки сообщений.
Функция SendMessage
Эта функция посылает синхронное сообщение указанному окну или нескольким окнам. Она имеет следующий прототип:
LRESULT SendMessage(
HWND hWnd, |
// дескриптор окна-получателя |
|||
UINT Msg, |
// код сообщения |
|||
WPARAM |
wParam, |
// |
первый |
параметр сообщения |
LPARAM |
lParam |
// |
второй |
параметр сообщения |
);
Параметры функции те же, что и параметры, передаваемые в оконную процедуру. Когда приложение вызывает SendMessage, Windows, в свою очередь, вызывает оконную процедуру с дескриптором окна hWnd, передавая ей эти четыре параметра. После того как оконная процедура заканчивает обработку сообщения, система Windows передает управление инструкции, следующей за вызовом SendMessage. Оконная процедура, которой отправляется сообщение, может быть той же самой оконной процедурой, другой оконной процедурой той же программы или даже
оконной процедурой другого приложения.
Если первый параметр функции имеет значение HWND_BROADCAST, то сообще ние посылается всем окнам верхнего уровня, существующим в настоящий момент в системе.
Параметры wParam и lParam содержат дополнительную информацию, интерпре тация которой зависит от кода сообщения. Чтобы получить справочную инфор мацию в MSDN об интерпретации этих параметров, применяйте поиск по коду сообщения, то есть по идентификаторам WM_PAINT, WM_TIMER, WM_SETFONT и им по добным.
Функция SendNotifyMessage
Функция SendNotifyMessage имеет те же параметры, что и функция SendMessage. Если окно, которому адресовано сообщение, создано вызывающим потоком,
то поведение функции SendNotifyMessage не отличается от поведения функции SendMessage. Она отправляет сообщение оконной процедуре и не возвращает уп равление, пока оконная процедура не обработает это сообщение.
Если же окно, которому адресовано сообщение, создано другим потоком, то функция SendNotifyMessage передает сообщение указанной оконной процедуре и немедленно возвращает управление, не дожидаясь окончания обработки сообще ния оконной процедурой.
Функция PostMessage
Функция PostMessage посылает асинхронное сообщение указанному окну. Она име ет те же параметры, что и функция SendMessage.
В отличие от SendMessage, функция PostMessage не вызывает оконную проце дуру, а отправляет сообщение в очередь сообщений потока, создавшего окно hWnd.
Особенности программирования для Windows |
55 |
|
|
После этого функция возвращает управление, не дожидаясь, пока поток обрабо тает отправленное сообщение.
Использование глобальных или статических переменных
Иногда бывает нужно, чтобы информация, полученная в процессе обработки ка кого то сообщения, была сохранена и использована позже при обработке другого сообщения.
Рассмотрим следующий пример. В программе Hello1 размеры клиентской об ласти окна были получены при помощи функции GetClientRect. Но существует и другой способ получить эту информацию — обрабатывая сообщение WM_SIZE. Параметр lParam этого сообщения содержит в своем младшем слове значение ши рины, а в старшем слове — значение высоты клиентской области окна. Допустим, что мы определили две локальные переменные в теле функции WndProc:
int width, height;
и добавили обработку сообщения WM_SIZE, обеспечивающую сохранение соответ ствующих значений:
case WM_SIZE:
width = LOWORD(lParam); height = HIWORD(lParam); return 0;
Здесь LOWORD и HIWORD — макросы, определенные в заголовочных файлах Windows; первый из них извлекает младшее слово, а второй — старшее слово из 32 разрядного аргумента.
Закомментируем вызов функции GetClientRect в блоке обработки сообщения WM_PAINT и вместо него вставим следующий код:
rect.left = 0; rect.top = 0; rect.right = width; rect.bottom = height;
После компиляции и запуска программы будет показано пустое окно, не со держащее никакого текста.
Неудача объясняется тем, что при повторном вызове WndProc значения ее ло кальных переменных от предыдущего вызова не сохраняются.
Но эту ошибку легко исправить. Надо лишь объявить переменные width и height либо как статические (static), либо как глобальные.
ВНИМАНИЕ
Если в оконной процедуре присваивание значения некоторой локальной переменной и дальнейшее использование этого значения разделены по разным ветвям оператора switch, то не забудьте объявить эту переменную с классом памяти static! Другое возможное решение — использование глобальной переменной.
Получение дескриптора экземпляра приложения
Для многих функций Win32 API необходимо передавать дескриптор экземпляра приложения в качестве одного из параметров. Напомним, что значение этого
56 Глава 1. «Hello, World!», или Первые шаги к пониманию концепции Windows
дескриптора hInstance функция WinMain получает от операционной системы через свой первый параметр.
Если значение дескриптора hInstance используется в теле функции WinMain, проблем никаких нет, если в теле другой функции — возникает вопрос, как полу чить значение hInstance? Есть три способа решения проблемы.
Первый способ (наихудший, с точки зрения стиля программирования на C++) — объявить глобальную переменную
HINSTANCE hInst;
и в теле функции WinMain запомнить значение дескриптора hInstance. Естественно, значение глобальной переменной будет доступно во всех других функциях.
Во втором и третьем способах переменная hInst объявляется как локальная. Во втором способе ее значение определяют при помощи функции GetClassLong:
hInst = (HINSTANCE)GetClassLong(hWnd, GCL_HMODULE);
В третьем способе для этого используется функция GetModuleHandle:
hInst = GetModuleHandle(NULL);
Предотвращение зависания приложения в случае медленной обработки отдельных событий
Предположим, что в вашей программе обработка какого то события является очень ресурсоемкой, занимая процессор в течение нескольких минут. Несмотря на то что Windows является многозадачной операционной системой, выделяющей при нудительно кванты времени для выполнения других приложений, пользователь будет не в состоянии что нибудь сделать с этой злополучной программой, пока не завершится указанная обработка. Он не сможет даже перетащить окно в дру гое место экрана или закрыть приложение. Внешне это выглядит как зависание приложения. Вряд ли пользователю понравится такое поведение программы.
Рассмотрим эту проблему на конкретном примере. Предположим, в приложе нии имеется обработка некоторой команды меню (например, Play) с помощью сле дующей функции:
void OnPlay() {
while (!fReadData.eof()) { fReadData.read(buf, 512);
DoSomething(); // какая-нибудь обработка
}
}
Здесь fReadData — это объект класса ifstream. В цикле while осуществляются чтение файла fReadData блоками по 512 байт и последующая обработка каждого блока с помощью функции DoSomething. Допустим, что размер файла достигает 5 Мбайт, а время выполнения функции DoSomething составляет 10 мс. Тогда тело цикла будет выполняться 5335040 / 512 = 10420 раз, и возврат из функции OnPlay произойдет примерно через 105 с. В течение этого промежутка времени окно программы будет безвольно висеть на экране.
Для решения проблемы нам нужен способ узнать, имеется ли какое нибудь сообщение в очереди сообщений приложения или же она пуста. Если сообще ние есть, нужно дать возможность Windows обработать его, если нет — можно продолжить выполнение цикла. Win32 API предлагает использовать для этого
Особенности программирования для Windows |
57 |
|
|
функцию PeekMessage. Ее прототип практически идентичен прототипу функции
GetMessage:
BOOL PeekMessage( |
|
LPMSG lpMsg, |
// адрес структуры с сообщением |
HWND hWnd, |
// дескриптор окна |
UINT wMsgFilterMin, |
// первое сообщение |
UINT wMsgFilterMax, |
// последнее сообщение |
UINT wRemoveMsg |
// флаг удаления сообщения |
); |
|
Данная функция возвращает ненулевое значение, если в очереди имеется со общение. Различие прототипов функций заключается в последнем параметре, который определяет, как именно должно быть выбрано сообщение из очереди. Возможными значениями параметра wRemoveMsg являются:
PM_NOREMOVE — сообщение остается в очереди;
PM_REMOVE — сообщение удаляется из очереди.
Покажем, как нужно переделать функцию OnPlay, используя вызов PeekMessage, чтобы приложение не зависало:
void OnPlay(HWND hWnd) { MSG message;
while (!fReadData.eof()) { fReadData.read(buf, 512);
// Предотвращение зависания приложения
if (PeekMessage(&message, hWnd, 0, 0, PM_REMOVE)) { TranslateMessage(&message); DispatchMessage(&message);
} |
|
DoSomething(); |
// какая-нибудь обработка |
}
}
Если функция PeekMessage обнаруживает сообщение в очереди, то оно обраба тывается так же, как и в основном цикле обработки сообщений.
Данный инструментальный прием будет использован в приложении ProgressBar, рассматриваемом в главе 8.
Использование утилиты Spy++
В процессе отладки приложений иногда бывает полезным увидеть, какие сообще ния вырабатывает Windows в ответ на те или иные действия пользователя. В со ставе интегрированной среды Visual Studio 6 есть удобное инструментальное сред ство для решения данной проблемы — утилита Spy++.
Утилиту можно вызвать при помощи команды меню интегрированной среды:
Tools Spy++.
Работа с утилитой Spy++ описана в приложении 3.
58 |
Глава 2. GDI — графический интерфейс устройства |
|
|
GDI — графический
2интерфейс устройства. Рисование линий,
фигур, текста
Графический интерфейс устройства (graphics device interface (GDI)) — это состав ная часть Win32 API, обеспечивающая графический вывод на устройства отобра жения информации, такие как дисплей или принтер. Windows приложение не имеет непосредственного доступа к аппаратным устройствам. Вместо этого оно обращается к функциям GDI, а GDI транслирует эти обращения к программным драйверам физических устройств, обеспечивая аппаратную независимость при ложений (рис. 2.1). Код библиотеки GDI находится в файле gdi32.dll, то есть биб лиотека является динамически загружаемой. Драйверы стандартных устройств поставляются как часть Windows, а драйверы специализированных устройств со здаются производителями оборудованиями.
Рис. 2.1. Соотношения между приложениями, GDI и драйверами устройств
Контекст устройства |
59 |
|
|
|
|
Контекст устройства
Взаимодействие приложения с GDI осуществляется при непременном участии еще одного посредника — так называемого контекста устройства.
Контекст устройства (device context) — это внутренняя структура данных, ко торая определяет набор графических объектов и ассоциированных с ними атри бутов, а также графических режимов, влияющих на вывод.
В следующем списке приведены основные графические объекты:
Перо (pen) для рисования линий.
Кисть (brush) для заполнения фона или заливки фигур.
Растровое изображение (bitmap) для отображения в указанной области окна.
Палитра (palette) для определения набора доступных цветов.
Шрифт (font) для вывода текста.
Регион (region) для отсечения области вывода.
Если необходимо рисовать на устройстве графического вывода (экране дис
плея или принтере), то сначала нужно получить дескриптор контекста устрой ства. Возвращая этот дескриптор после вызова соответствующих функций, Windows тем самым предоставляет разработчику право на использование дан ного устройства. После этого дескриптор контекста устройства передается как параметр в функции GDI, чтобы идентифицировать устройство, на котором долж но выполняться рисование.
Контекст устройства содержит много атрибутов, определяющих поведение функций GDI. Благодаря этому списки параметров функций GDI содержат толь ко самую необходимую информацию, например начальные координаты или раз меры графического объекта. Все остальное система извлекает из контекста уст ройства.
Когда в приложении Hello1 вызывалась функция DrawText, то в ее параметрах нужно было указать только дескриптор контекста устройства, адрес строки с вы водимым текстом, его длину, положение и размеры прямоугольной области для размещения текста, а также способ позиционирования текста внутри этой облас ти. Однако при этом не указывались шрифт, цвет текста, цвет фона, режим сме шивания фона, режим рисования и другие атрибуты, потому что все они являют ся частью контекста устройства и имеют значения по умолчанию. И только если значения по умолчанию не устраивают разработчика, необходимо вызывать фун кции GDI, изменяющие значения соответствующих атрибутов.
Типы контекстов устройства
Win32 API поддерживает следующие типы контекстов устройства:
контекст дисплея;
контекст принтера;
контекст в памяти (совместимый контекст);
метафайловый контекст;
информационный контекст.
60 |
Глава 2. GDI — графический интерфейс устройства |
|
|
Конечно, чаще всего используется контекст дисплея. Первое знакомство с ним состоялось, когда рассматривалась обработка сообщения WM_PAINT в про грамме Hello1 (глава 1). Сообщение WM_PAINT уведомляет программу, что часть или вся клиентская область окна недействительна и ее следует перерисовать. Понятие недействительного, или обновляемого, региона очень важно для взаи модействия Windows и приложения, поэтому, прежде чем продолжить более подробное рассмотрение контекста дисплея, мы остановимся кратко на следую щей теме.
Регионы Windows. Отсечение
Для повышения эффективности работы Windows оперирует с несколькими типа ми регионов. Идея заключается в том, чтобы рисовать именно в той части окна, которая требует обновления, а не перерисовывать все окно. Также регионы позво ляют отсекать вывод той части графической информации, которая не может быть отображена в данный момент. Вообще полное изучение всей иерархии регионов и их взаимодействия является непростой задачей, требующей пространного из ложения1. В то же время приведенное ниже упрощенное описание достаточно для понимания работы большинства функций Win32 GDI.
Обновляемый регион (update region), или, как его тоже иногда называют, недей ствительный регион (invalid region) — это часть окна, которая требует обновления после возникновения тех или иных событий.
Видимый регион (visible region) — та часть окна, которую в данный момент ви дит пользователь. Система изменяет видимый регион окна и в том случае, когда окно изменяет размеры, и в том случае, когда перемещение другого окна либо закрывает часть данного окна, либо открывает закрытую прежде часть.
Регион отсечения (clipping region) ограничивает область, внутри которой сис тема разрешает отображение графической информации. Когда приложение полу чает контекст устройства при помощи функции BeginPaint, система устанавлива ет регион отсечения путем пересечения видимого региона и обновляемого региона. Приложение может ужесточить регион отсечения и ввести дополнительные огра ничения при помощи вызова функции SetWindowRgn, SelectClipPath или SelectClipRgn.
Если при создании окна функцией CreateWindow был использован стиль WS_CLIPCHILDREN или WS_CLIPSIBLINGS, то это вносит дополнительные правила в определение видимого региона, исключая из него любое дочернее или любые «сестринские» окна. Благодаря этому рисование не затрагивает отображаемые об ласти таких окон.
Контекст дисплея
Контекст дисплея создавать не нужно — об этом уже позаботилась операционная система. Нужно лишь получить его дескриптор. Windows предоставляет для это го два метода, применение которых обусловлено (извините за каламбур) программ ным контекстом.
1Интересующихся мы можем отослать к известной книге Фень Юаня «Программирование графики для Windows» [6].
