Сравнивая примеры 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 недоступна в конкретном месте программы (потому что принадлежит другому классу и объявлена в нем защищенной или даже закрытой); тогда приходится обращаться к соответствующей функции Windows 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;
и, соответственно, переменная класса, представляющая дескриптор окна (она здесь имеет имя не HWindow, 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.