Обработка сообщения WM_PAINT
Как мы уже говорили, окно должно быть способно перерисовать свою внутреннюю поверхность или любую его часть в любой момент времени при получении сообщения WM_PAINT.
Сообщение WM_PAINT передается функции окна, если стала видна область окна, скрытая раньше другими окнами, если вы изменили размер окна или выполнили операцию свертки (пролистывания) изображения в окне. Приложение может передать функции окна сообщение WM_PAINT явным образом, вызывая функции UpdateWindow, InvalidateRect или InvalidateRgn.
Иногда операционная система Windows может сама восстановить содержимое окна, не посылая сообщение WM_PAINT. Например, при перемещении курсора мыши или пиктограммы свернутого приложения Windows самостоятельно восстанавливает содержимое окна. Если же Windows не может восстановить окно, функция окна получает сообщение WM_PAINT и перерисовывает окно самостоятельно.
Перед тем как записать сообщение WM_PAINT в очередь приложения, Windows посылает функции окна сообщение WM_ERASEBKGND. Если функция окна не обрабатывает сообщение WM_ERASEBKGND, передавая его функции DefWindowProc, последняя в ответ на это сообщение закрашивает внутреннюю область окна с использованием кисти, указанной в классе окна (при регистрации класса окна). Поэтому, если функция окна нарисует что-либо в окне во время обработки других сообщений, отличных от WM_PAINT, после прихода первого же сообщения WM_PAINT нарисованное изображение будет закрашено.
Что делать в том случае, когда по логике работы приложения требуется изменить содержимое окна не во время обработки сообщения WM_PAINT, а в любом другом месте приложения?
В этом случае приложение должно сообщить Windows, что необходимо перерисовать часть окна или все окно. При этом в очередь приложения будет записано сообщение WM_PAINT, обработка которого приведет к нужному вам результату. Можно указать Windows, что был изменен прямоугольный участок окна или область произвольной формы, например эллипс или многоугольник. Для этого предназначены функции InvalidateRect и InvalidateRgn, которые мы рассмотрим позже.
Как функция окна может определить область окна, подлежащую обновлению при обработке сообщения WM_PAINT?
Для ответа на этот вопрос вспомним о втором параметре функции BeginPaint, который является указателем на структуру PAINTSTRUCT:
HDC BeginPaint(HWND hwnd, PAINTSTRUCT FAR * lpps);
Структура PAINTSTRUCT определена в файле windows.h следующим образом:
typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[16]; } PAINTSTRUCT;
Поле hdc после вызова функции BeginPaint будет содержать идентификатор контекста отображения (тот же самый, что и возвращаемый самой функцией BeginPaint).
Начальные координаты и размер области, подлежащей обновлению в процессе обработки сообщения WM_PAINT, передаются через поле rcPaint. Это поле представляет собой структуру типа RECT, описывающую прямоугольную область:
typedef struct tagRECT { int left; int top; int right; int bottom; } RECT;
Поля left, top, right и bottom задают координаты области следующим образом:
Поле | Описание |
left | x-координата верхнего левого угла области |
top | y-координата верхнего левого угла области |
right | x-координата правого нижнего угла области |
bottom | y-координата правого нижнего угла области |
Поле fErase структуры PAINTSTRUCT определяет необходимость стирания фона окна в области, подлежащей обновлению. Если это поле установлено в состояние TRUE, функция BeginPaint посылает функции окна сообщение WM_ERASEBKGND.
Как правило, сообщение WM_ERASEBKGND передается функции DefWindowProc, которая при получении этого сообщения перерисовывает фон соответствующей области окна (используя кисть, определенную при регистрации класса окна). Если поле fErase содержит значение FALSE, фон окна не изменяется.
Остальные поля структуры PAINTSTRUCT используются Windows, приложение не должно изменять их содержимое.
Если окно содержит несколько областей, подлежащих обновлению, приложение получает только одно сообщение WM_PAINT, в котором определена область, охватывающая все указанные выше области.
Рассмотрим некоторые функции, имеющие отношение к сообщению WM_PAINT.
Функция UpdateWindow имеет следующий прототип:
void UpdateWindow(HWND hwnd);
Эта функция посылает сообщение WM_PAINT функции окна, идентификатор которого задан в качестве параметра hwnd. Сообщение посылается в обход очереди сообщений приложения, и только в том случае, если для окна существует непустая область обновления. Если в окне обновлять нечего, сообщение WM_PAINT не посылается.
При помощи функции InvalidateRect вы можете объявить любую область окна как требующую обновления. Прототип функции:
void InvalidateRect(HWND hwnd, LPRECT lprc, BOOL fErase);
Первый параметр (hwnd) функции является идентификатором окна, для которого выполняется операция. Второй параметр (lprc) - дальний указатель на структуру типа RECT, определяющую прямоугольную область, подлежащую обновлению. Третий параметр (fErase) определяет необходимость стирания фона окна. Если этот параметр задан как TRUE, фон окна подлежит стиранию (см. поле fErase структуры PAINTSTRUCT).
Функция ValidateRect удаляет прямоугольную область из списка областей, подлежащих обновлению. Она имеет следующий прототип:
void ValidateRect(HWND hwnd, LPRECT lprc);
Первый параметр определяет идентификатор окна, второй является дальним указателем на структуру типа RECT, определяющую прямоугольную область, которая должна быть исключена из области обновления. Если в результате вызова функции ValidateRect в окне не остается областей, подлежащих обновлению, сообщение WM_PAINT удаляется из очереди сообщений приложения.
Подводя итог, отметим еще раз несколько моментов, важных для понимания методов, с помощью которых приложение Windows рисует в своих окнах.
Приложение должно выполнять вывод в окно "централизованно" в функции окна при получении сообщения WM_PAINT.
При обработке сообщения WM_PAINT для увеличения скорости работы следует использовать координаты области окна, подлежащей обновлению, хотя можно обновить и все окно.
Используя специальные функции, приложение в любой момент времени может определить любую область окна как подлежащую (или не подлежащую) обновлению и послать самому себе в соответствующую функцию окна сообщение WM_PAINT.