Обработка сообщения WM_PAINT

Сравнивая примеры 25-1 и 26-1, легко заметить, что они различаются всего несколькими строками. К числу несущественных отличий относится изъятие из конструктора класса главного окна MyWindow строки задания цвета фона окна, в результате чего окно приложения будет иметь цвет по умолчанию, т.е. белый. Более принципиальное отличие заключается в том, что в классе MyWindow замещена открытая виртуальная функция класса TWindow Paint(), которая перешла по наследству сначала в производный от TWindow класс TFrameWindow, а оттуда в прикладной класс MyWindow. Эта функция вызывается про­граммами OWL в ответ на приход в окно приложения сообщения WM_PAINT. Исходная функция Paint(), входящая в класс TWindow, является функцией-заглушкой: в ее определении нет не единой строки. За­местив ее в производном от TWindow классе функцией с разумным содержимым, мы получаем возмож­ность обрабатывать в нашей программе сообщения WM_PAINT, поступающие (в данном случае) в глав­ное окно приложения. В примере 26-1 обработка заключается в выводе в окно с помощью функции TextOut() короткой строки текста; в следующих примерах будут продемонстрированы другие средства графического интерфейса.

Функция TextOut() принадлежит классу TDC и вызывается для объекта этого класса dc (откуда взял­ся этот объект, будет объяснено ниже). Эта функция совпадает по наименованию и смыслу с аналогич­ной функцией Windows API, хотя отличается от последней набором аргументов (см. для сравнения при­мер 8.1 из гл. 8). В таком случае говорят, что функция OWL инкапсулирует аналогичную функцию API. Практически все основные функции API Windows инкапсулированы в библиотеке OWL, хотя способ их вызова и состав аргументов, естественно, различаются. Иногда оказывается, что требуемая функция OWL недоступна в конкретном месте программы (потому что принадлежит другому классу и объявлена в нем защищенной или даже закрытой); тогда приходится обращаться к соответствующей функции Win­dows API. Примеры такого рода будут приведены в дальнейшем.

"Отлавливание" требуемого сообщения в прикладной программе просто путем вызова функции с тем же именем характерна только для сообщения WM_PAINT. Остальные сообщения следует обслуживать по другим, более сложным правилам, создавая в программе таблицу откликов на сообщения и связывая эту таблицу с набором прикладных функций обработки сообщений; эти процедуры будут подробно рас­смотрены в последующих главах. Сообщение же WM_PAINT, учитывая его важность, частично обслу­живается внутри класса TWindow, где имеется своя таблица откликов; программисту предоставляется функция-заглушка, и на его долю остается только написать свою функцию с именем Paint().

Рассмотрим немного подробнее процедуру обслуживания сообщений WM_PAINT классом TWindow. Это поможет нам разобраться в смысле аргументов функции Paint() и правилах составления текста заме­щающей функции.

Как было показано в гл. 8, важнейшим понятием GDI является контекст устройства, содержимого которого в каждый момент определяет характеристики всех доступных инструментов рисования. В дей­ствительности в Windows используется не один, а целый ряд контекстов устройств, предоставляющих возможность рисовать в рабочей области окна, во всем окне приложения, на рабочем столе, на всем эк­ране и т.д. Наиболее общие свойства контекстов устройств описаны в классе TDC, являющемся базовым для подклассов, определяющих свойства упомянутых выше контекстов. Из этих производных подклас­сов нас пока будет интересовать только класс TPaintDC, с экземпляром которого dc мы сталкиваемся в нашей программе. С другой стороны, сам класс TDC является производным от базового для всей графи­ческой системы (а не только для контекстов устройств) класса TGdiBase. Эти три класса образуют струк­туру, изображенную на рис. 26.2.

Рассмотрим некоторые из членов приве­денной иерархии классов.

В базовый класс TGdiBase входит деск­риптор контекста устройства Handle. Этот дескриптор имеет обобщенный тип HANDLE, который затем преобразуется в более привычный нам тип контекста устрой­ства НDС.

Класс TDC, являющийся базовым для целого ряда классов, описывающих различ­ные контексты устройств (TPaintDC, TWin-dowDC, TClientDC и др.), включает дескрип­торы исходных графических объектов (ин-

струментов рисования) OrgBrush, OrgPen, OrgFont и OrgPalette, а также большое количество (около 200) графических функций, обеспечивающих вывод на экран текстов, фигур и других изображений, создание, выбор и настройку инструментов рисования,- получение и изменение режимов работы графической сис­темы и т.д. Большинство этих функций инкапсулируют соответствующие функции API Windows.

Очень небольшой по объему класс TPaintDC содержит в качестве данных-членов дескриптор окна типа HWND (данное с именем Wnd) и хорошо известную нам структуру PAINTSTRUCT (данное с име-


230____________________________________________________________ ____ Глава 26

нем Ps), используемую системой Windows при перерисовке изображения (см. гл. 8). Функций-членов в классе TPaintDC всего две: конструктор TPaintDC() и деструктор ~TPaintDC(). Как показано на рис. 26.2, в конструкторе вызывается функция API Windows BeginPaint(), которая заполняет структуру Ps и воз­вращает контекст устройства; в деструкторе вызывается функция API Windows EndPaint(), освобождаю­щая контекст устройства. Таким образом, при создании объекта класса TPaintDC автоматически выпол­няются необходимые инициализирующие действия, а при его уничтожении - необходимые завершаю­щие действия. Стоит еще отметить, что в деструктор ~TPaintDC() входит также вызов функции Restore-Objects(), выполняющей выбор в контекст устройства исходных инструментов. Таким образом, в OWL-программе после окончания работы с созданными и выбранными в контекст устройств инструментами, нет необходимости выполнять относительно громоздкую процедуру выбора назад в контекст всех ис­ходных дескрипторов, так это действие автоматически выполняется в деструкторе класса.

Поступление в окно приложения сообщения WM_PAINT приводит к вызову защищенной функции класса TWindow EvPaint(). Поскольку сообщение WM_PAINT приходит в наше окно, с которым ассо­циируется объект MyWin (являющийся потомком класса TWindow и наследующий его открытые и за­щищенные члены), то функция EvPaint() вызывается именно для этого объекта. Определение функции EvPaint() (которое можно найти в файле SOURCE\OWL\WINDOW.CPP), за вычетом некоторых несуще­ственных пока деталей, выглядит следующим образом:

void TWindow::EvPaint(){ TPaintDC dc(*this);

TRect& rect=*(TRect*)&dc.Ps.rcPaint; Paint(dc,dc.Ps.fErase,rect);

}

В первом предложении вызывается конструктор класса TPaintDC, который создает объект этого класса с именем dc. Имя этого объекта используется в дальнейших предложениях приведенного фраг­мента, а также и в тексте нашей программы. Прототип конструктора класса TPaintDC имеет следующий вид:

TPaintDC (HWND);

В качестве аргумента конструктора выступает дескриптор окна типа HWND. Однако в функции EvPaint() в качестве фактического аргумента конструктора использовано обозначение *this, что означает указатель на текущий объект со снятой ссылкой, т.е. сам текущий объект (этот объект в нашей програм­ме не имеет имени, так как создан не по имени, а с помощью указателя MyWin). Каким образом целый объект *MyWin преобразуется в конкретное данное типа HWND? Это делается с помощью оператора преобразования типа, включенного в класс TWindow, который в OWL 2.5 (пакет Borland C++ 4.5) вы­глядит следующим образом:

TWindow::operator HWND() const {return HWindow;}

Как было показано в гл. 22, операторы такого рода позволяют преобразовывать пользовательские классы в скалярные данные базовых типов; в данном случае задаются правила преобразования класса TWindow в скалярную переменную HWindow типа HWND. Конструктор TPaintDC требует в качестве ар­гумента переменную типа HWND, однако в функции EvPaint() он вызывается с указанием параметра ти­па "объект класса TWindow". Следовательно, компилятор должен преобразовать объект TWindow в пе­ременную типа HWND. Правила такого преобразования, задаваемые оператором operator, требуют под­становки вместо объекта класса TWindow данного-члена того же класса HWindow. Таким образом, кон­структор TPaintDC получает в качестве параметра данное HWindow, представляющее собой дескриптор окна, которое он затем использует при вызове функции BeginPaint().

В OWL 5.0 (пакет Borland C++ 5.0) описанная процедура выглядит сложнее, хотя ее смысл остается в точности тем же. Отличие заключается в том, что в классе TWindow оператором typedef вводится новый тип данных THandle, в точности эквивалентный HWND: typedef HWND THandle;

и, соответственно, переменная класса, представляющая дескриптор окна (она здесь имеет имя не HWin­dow, a Handle), объявлена типа THandle:

THandle Handle;

Оператор преобразования типа также написан для типа THandle: TWindow::operator THandle() const {return GetHandle();}

Функция GetHandle() того же класса возвращает дескриптор окна Handle:
TWindow::THandle TWindow::GetHandle() const {return Handle;}

В итоге конструктор TPaintDC получает в качестве параметра дескриптор окна Handle, который за­тем передается в функцию BeginPaint().

Вернемся, однако, к обсуждению функции EvPaint(). Во втором предложении этой функции TRect& rect=*(TRect*)&dc.Ps.rcPaint;


Обработка сообщения WM_PAINT и интерфейс GDI 231

создается ссылочная переменная rect класса TRect. Эта переменная описывает прямоугольную область (в данном случае - область вырезки изображения) по ее четырем координатам. Как известно, область вы­резки передается Windows в элемент rcPaint структуры PAINTSTRUCT, однако она имеет там тип RECT. Поскольку переменная rcPaint является элементом структурного объекта Ps типа PAINTSTRUCT, a Ps является данным-членом объекта dc (созданного конструктором), полное имя области вырезки будет dc.Ps.rcPaint. В приведенном выше предложении образуется адрес этой переменной (&dc.Ps.rcPaint), с помощью префикса преобразования типа (TRect*) преобразуется в указатель на класс TRect, затем с него снимается ссылка (знак *) и полученное значение присваивается ссылочной переменной rect. В итоге в rect поступает область вырезки.

В последнем предложении функции EvPaint()

Paint(dc,dc.Ps.fErase,rect);

вызывается функция TWmdow::Paint() с передачей ей трех параметров: образованного ранее объекта dc класса TPaintDC, члена fErase структуры Ps и области вырезки rect. Как уже отмечалось выше, функция Paint() является заглушкой, которую необходимо переопределять в производных классах, при этом за­мещающая ее функция производного класса получает все три описанные выше параметра и может с ни­ми работать.

Заметим, что в прототипе функции Paint

virtual void Paint(TDC& dc, bool erase, TRect& rect);

первый аргумент указан типа TDC, а мы присваиваем ему значение типа TPaintDC. Поскольку, однако, класс TPaintDC является производным от TDC, такое преобразование указателей (и ссылочных перемен­ных) является допустимым (см. гл. 24). Правда, при этом преобразовании объект dc усекается до содер­жимого базового класса TDC и из него исключаются члены, добавленные классом TPaintDC, конкретно, структура Ps и дескриптор окна Wnd, которые мы уже не можем использовать в функции Paint(). Види­мо, разработчики полагали, что эти данные не понадобятся прикладной программе. Область же вырезки, которая может программе потребоваться, передается в функцию Paint() через параметр rect. Передается также и флаг стирания фона окна, который обычно имеет нулевое значение, задающее автоматическое перерисовывание фона окна программами Windows.

Итак, при поступлении в наше окно сообщения WM_PAINT, автоматически создается объект класса TPaintDC с конкретным именем dc, через который мы можем получить доступ ко всему набору графиче­ских функций класса TDC, и выполняется вызов функции API Windows BeginPaint(), которая заполняет структуру Ps. Наша задача теперь сводится к вызову любых требуемых функций GDI для объекта dc. В примере 26-1 для объекта dc вызывается единственная функция TextOut() (инкапсулированная в OWL) для вывода строки текста:

dc.TextOut(10,10,"Строка текста"); //Вывод строки текста

В дальнейших примерах будут проиллюстрированы приемы работы с другими функциями GDI.

Как уже отмечалось, при выходе из функции нам нет необходимости заботиться об освобождении контекста устройства (функция API Windows EndPaint()), так как эту работу берет на себя класс TPaintDC.



Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow