В примере 29-1 рассматривается организация порожденных (дочерних и всплывающих) окон, а также целый ряд частных, но важных вопросов организации и взаимодействия прикладных классов. Идея программы заключается в том, что на экран выводятся строки текста, в которых отдельные фрагменты выделены зеленым цветом. При прохождении курсора мыши по такому фрагменту курсор приобретает форму ладони , а щелчок левой клавишей мыши приводит к выводу на экран небольшого окна с дополнительной информацией по данному вопросу. Как видно из рис. 29-1, в рассматриваемом примере в главное окно выводится часть содержания настоящей книги, причем зеленым цветом в нем выделены названия глав. Дополнительная информация по каждой главе представляет собой перечень разделов конкретной главы.
//Приложение 29-1. Дочерние и всплывающие окна
//Файл 29-1.rc
Hand CURSOR "hand.cur"
//Файл 29-1.cpp
#include <owl\framewin.h>
/*Глобальные переменные*/
TWindow* plain[5]; //Указатели на базовый класс TWindow, которые будут
TWindow* quest[5];//использоваться, как указатели на классы Plain и Quest
const long winStyle=WS_VISIBLE|WS_CHILD; //Стиль всех окон для строк
const char* plainStrings[]={"Глава 1","Глава 2","Глава 3","Глава 4","Глава 5"};
const char* questStrings[]={"Простейшее приложение DOS",
"Объявление и использование данных","Функции","Основные конструкции языка",
"Интегрированные среды разработки Borland C++"}; const TPoint plainPos[]={TPoint(10,10),TPoint(10,30),TPoint(10,50),// Координаты
TPoint(10,70),TPoint(10,90)};/ /простых строк const TPoint questPos[] = {TPoint(70,10),TPoint(70,30).TPoint(70, 50), //Координаты
TPoint(70,70),TPoint(70,90)};/ /вопросных строк
char* texts[]={"Подготовка выполнимой программы. Оператор #include и включаемые "\ "файлы. Главная функция main. Ввод с клавиатуры и вывод на экран.", "Скалярные данные. Массивы. Структуры. Константы. Перечислимые типы данных.", "Прототипы функций. Передача в функцию параметров. Возврат из функции "\ "результата ее работы. Функции и структуры. Глобальные и Локальные переменные.", "Управление ходом программы. Операторы цикла. Еще об операциях C++. "\ Оператор typedef и создание новых типов данных. Основы аппарата макросов.", "Настройка операционной среды. Подготовка выполнимой программы. Файл проекта."};
270 Глава 29
/*Класс приложения, производный от T application*/
class MyApp:public TApplication{ public:
void InitMainWindowO; //Замещаем функцию InitMainffindow
};
/*Класс Plain дочерних окон для простых строк текста, производный от Twindow*/ class Plain:public TWindow{
int plainIndex;//Номер объекта-окна для строки текста public:
Plain(TWindow* parent,int ind):TWindow(parent),plainIndex(ind){ Attr.Style=winStyle; //Назначаем стиль дочерним окнам }
void Paint(TDC&dc,bool,TRect&); //Замещаем функцию для класса Plain
};
/*Функции-члены класса Plain*/ void Plain::Paint (TDC&dc,bool,TRect&) {
TRect rect=GetClientRect(); //Получаем рабочую область
dc.DrawText //Выводим соответствующую строку текста (номер главы)
(plainStrings[plainIndex],strlen(plainStrings[plainIndex]),rect,DT_LEFT); } /*Класс Quest дочерних окон для вопросных строк текста, производный от Twindow*/ class Quest:public TWindow{
int questIndex; //Номер объекта-окна для строки текста public:
Quest(TWindow* parent,int ind):TWindow(parent),questIndex(ind){ Attr.Style=winStyle; //Назначаем стиль дочерним окнам }
char far* GetClassName(); //Замещаем функцию для класса Quest
void GetWindowClass(WNDCLASS&); //Замещаем функцию для класса Quest
void Paint(TDC&dc,bool,TRect&) ;//Замещаем функцию для класса Quest
void EvLButtonDown(UINT,TPoint&); //Назначаем функцию отклика для класса Quest
DECLARE_RESPONSE_TABLE(Quest) ;//Объявляем таблицу откликов };
/*Функции-члены класса Quest*/ DEFINE_RESPONSE_TABLE1(Quest,TWindow) //Описываем таблицу откликов
EV_WM_LBUTTONDOWN, //Только сообщения от левой клавиши мыши END_RESPONSE_TABLE; char far* Quest::GetClassName(){
return "Quest"; //Назначаем имя этому классу
} void Quest::GetWindowClass(WNDCLASS& wc){
TWindow::GetWindowClass(wc); //Вызываем замещенную функцию
wc.hCursor=GetApplication()->TModule::LoadCursor ("Hand"); //Назначаем курсор
} void Quest::Paint(TDC&dc,bool,TRect&){
dc.SetTextColor(TColor(0,128,0)); //Назначаем вопросным строкам зеленый цвет
TRect rect=GetClientRect() ;//Получаем рабочую область
dc.DrawText //Выводим соответствующую строку текста (название главы)
(questStrings[questIndex],strlen(questStrings[questIndex]),rect,DT_LEFT);
}
/*Класс Contents всплывающих окон с содержанием глав, производный от Twindow*/ class Contents:public TWindow
{int сontIndex; //Номер объекта-окна для текста public:
Contents(TWindow* parent,const char far* title,int ind): TWindow(parent,title),contIndex(ind){
Attr.Style=WS_VISIBLE|WS_THICKFRAME|WS_SYSMENU|WS_POPUP|WS_CAPTION;}// Конструктор
void GetWindowClass(WNDCLASS&); //Замещаем функцию для класса Contents
void Paint(TDC&dc,bool,TRect&); //Замещаем функцию для класса Contents
};
/*Функции-члены класса Contents*/ char far* Contents::GetClassName(){
return "Contents"; //Назначаем имя этому классу
} void Contents::GetWindowClass(WNDCLASS& wc){
TWindow::GetWindowClass(wc); //Вызываем замещенную функцию
wc.style=CS_VREDRAW|CS_HREDRAW;// Для перерисовки при изменении размеров
void Contents::Paint(TDC&dc,bool,TRect&) {
TRect rect=GetClientRect() ;//Получаем рабочую область
rect.Inflate(-2,0); //Сокращаем ее на два пиксела слева и справа
dc.DrawText(texts[contIndex],strlen(texts[contIndex]), //Выводим
Окна и их оформление 271
rect,DT_WORDBREAK);//соответствующий текст (содержание главы) } /*Функция отклика класса Quest, использующая описание класса Contents*/
void Quest::EvLButtonDown(UINT,TPoint& point){//В ответ на щелчок левой клавиши Contents* contents=new Contents(GetWindowPtr(GetParent()),
plainStrings[questIndex],questIndex); //Создаем всплывающее окно с текстом contents->Create(); //Создаем и отображаем окно на экране TRect rect;// Вспомогательная переменная
GetWindowRect(rect);//Получаем координаты окна-вопросной строки point+=rect.TopLeft(); //Корректируем переменную point
contents->MoveWindow(point.x,point.y,300,80,true) ;//Перемещаем окно с текстом }
/*Класс главного окна, производный от TframeWindow*/ class MyWindow:public TFrameWindow{ public:
MyWindow(TWindow*parent,char far*title):TFrameWindow(parent,title){} //Конструктор int EvCreate(CREATESTRUCT far&);// Функция отклика на создание окна DECLARE_RESPONSE_TABLE(MyWindow);/ /Объявляем таблицу откликов
};
/* Функции-члены класса MyWindow */ DEFINE_RESPONSE_TABLE1(MyWindow,TFrameWindow)
EV_WM_CREATE, END_RESPONSE_TABLE; int MyWindow::EvCreate(CREATESTRUCT far&){
for(int i=0;i<5;i++){//В цикле по пяти парам строк
plain[i]=new Plain(this,i); //Создаем объект-окно для номера главы quest[i]=new Quest(this,i); //Создаем объект-окно для названия главы plain[i]->Create(); //Создание и показ quest[i]->Create();// окон
plain[i]->MoveWindow(plainPos[i].x,plainPos[i].y,59,19) ;// Позиционирование окна TClientDC tdc(*quest[i]); //Преобразование объекта TWindow в дескриптор окна TSize strSize=tdc.GetTextExtent(questStrings[i],strlen(questStrings[i])); quest[i]->MoveWindow(questPos[i].x,questPos[i].y,strSize.cx+2,19); }
return 0; } void MyApp::InitMainWindow(void){
SetMainWindow(new MyWindow(0,"Программа 29-1")); }
/*Глатная функция приложения OwlMain*/ int OwlMain(int,char*[]){ return MyApp().Run(); }
Как известно, форма курсора и вид реакции на сообщения Windows являются атрибутами класса окна Windows. Для того, чтобы отдельным фрагментам строк текста придать специфический курсор и чувствительность к щелчкам мышью, их следует оформить в виде дочерних окон - представителей некоторого оконного класса, для которого заданы функция отклика на сообщения мыши и дескриптор соответствующего курсора. Фрагменты же строк, нечувствительных к щелчкам мыши, должны быть оформлены, как окна другого класса, для которого не задана реакция на щелчки мышью и которому, к тому же, может быть назначен стандартный (или любой другой) курсор. В рассматриваемом примере от класса OWL TWindow образованы три производных класса - класс Plain для "простых" строк с номерами глав, класс Quest для "вопросных" строк с названиями глав и класс Contents всплывающих окон с перечнем разделов каждой главы. От каждого класса образовано по 5 объектов соответственно запланированному объему выводимой на экран информации. Форма и расположение на экране дочерних окон классов Plain и Quest показано на рис. 29.2. Приведенное на рисунке изображение легко получить, дополнив стиль окон этих классов (константа winStyle, описанная в списке глобальных переменных) стилем WS_BORDER.
Оконные классы Windows идентифицируются их именами. Однако при образовании классов, производных от TWindow, все они получают имена OwlWin-dow и, соответственно, один и тот же набор характеристик. Для того, чтобы классам, образованным в программе, можно было назначать различающиеся характеристики, им следует дать уникальные имена.
272 Глава 29
Назначение классу окна имени выполняется путем замещения в производном от TWindow (или другого оконного класса OWL) функции GetClassName(). Эта функция в своем исходном варианте содержит строку
return "OwlWindow";
что и приводит к назначению всем классам одного и того же имени (здесь имеются в виду классы, производные от TWindow; функции GetClassName() других классов возвращают другие имена). Замещающая функция должна вернуть произвольное имя, назначенное программистом. В нашем примере функция GetClassName() замещается в классах Quest и Contents, так как только для этих классов предусмотрено изменение их характеристик: для класса Quest - формы курсора, а для класса Contents - стиля класса. То, что классы Windows для OWL-классов Plain и MyWindow будут при этом иметь одинаковые Windows-имена, не помешает нам предусмотреть для них различные таблицы отклика. Явное назначение классу имени требуется лишь в тех случаях, когда мы хотим изменить такие характеристики окна, как курсор, пиктограмму или цвет фона (впрочем, цвет фона можно задать не только для класса окон, но и для каждого конкретного окна данного класса, для чего предусмотрена функция SetBkgndColor()).
Для изменения характеристик классов Quest и Contents в них описываются замещающие функции GetWindowClass(). В классе Quest назначается нестандартный курсор; в классе Contents в стиле окна устанавливаются биты CS_VREDRAW и CS_HREDRAW, чтобы при изменении пользователем размеров или конфигурации всплывающих окон их содержимое, позиционируемое функцией DrawText() относительно границ окна, каждый раз перерисовывалось заново.
В конструкторах классов Plain и Quest для всех окон этих классов устанавливается стиль WS_VISIBLE | WS_CHILD. Окна с текстом, разумеется, не должны иметь ни рамки, ни заголовка. Для всплывающих же окон класса Contents предусмотрена толстая рамка, позволяющая изменять их размеры, а также строка заголовка с системным меню и кнопкой закрытия окна.
Существенным элементом всех трех классов порожденных окон являются (закрытые) члены-данные plainIndex, questIndex и contIndex, которые служат для идентификации конкретных окон каждого класса. Инициализация этих переменных включена в заголовки конструкторов и, следовательно, при образовании соответствующих объектов необходимо указывать конкретное значение этого параметра. Статические объекты классов Plain и Quest создаются в цикле (в функции MyWindow::EvCreate()), и при вызове их конструкторов в качестве второго параметра просто указывается переменная цикла i. Сложнее дело обстоит с объектами класса Contents, которые создаются в произвольном порядке динамически в функции отклика на щелчок мыши; может показаться, что требуемое значение переменной contIndex в этом случае неизвестно. Однако при щелчке по той или иной "вопросной" строке вызывается функция EvLButtonDown() именно для того объекта-строки, по которому щелкнули, и, таким образом, значение данного-члена questIndex, которое, естественно, известно в функции отклика, соответствует индексу этого объекта. В то же время, щелкнув по некоторой строке, мы хотим образовать объект класса Contents именно для этой строки. Поэтому инициализирующим значением для переменной contIndex может служить текущее значение переменной questIndex, что и отражено в фактических аргументах вызова конструктора Contents.
Рассматривая конструкторы классов Plain и Quest, можно заметить, что их форма несколько отличается от той, что использовалась в предыдущих примерах. Конструктору базового класса TWindow передается лишь один параметр, а не два, как это было раньше. Однако вторым параметром конструктора TWindow служит заголовок создаваемого окна, а для строк текста этот заголовок не нужен. Опустить этот параметр можно потому, что в прототипе конструктора класса TWindow
TWindow(TWindow* parent, const char far* title = 0, TModule* module = 0);
для этого параметра имеется значение по умолчанию. Кстати, для первого параметра этого конструктора умолчания нет, и его указывать необходимо.
С конструктором класса Contents ситуация несколько иная. Объекты этого класса представляют собой обычные окна с системным меню и заголовком. Конечно, строку заголовка можно оставить пустой, однако значительно разумнее формировать и ее динамически, используя в качестве второго параметра конструктора Contents строку из массива plainStrings с соответствующим индексом (см. рис. 29.1).
Обсудим теперь вопрос о таблицах и функциях откликов. В классе Plain таблицы откликов нет, однако имеется замещающая функция Paint(), в которой в окно класса Plain выводится соответствующая номеру конкретного объекта строка текста из массива plainStrings. Поскольку функция Paint() вызывается (системой Windows, когда возникает необходимость перерисовывать главное окно) для конкретных дочерних окон, то функция Paint() может использовать для определения выводимой строки индекс plainIndex, характеризующий номер перерисовываемого в настоящий момент окна.
В классе Quest таблица откликов имеется, и в нее включен единственный макрос для сообщения WM_LBUTTONDOWN. В соответствующей функции отклика создается и отображается на экране объект - всплывающее окно класса Contents. Для большего благообразия окно выводится в ту точку, в которой находился курсор мыши в момент щелчка по "вопросной" строке.
Для класса MyWindow тоже предусмотрена таблица откликов ради обработки единственного сообщения WM_CREATE. В функции отклика создаются и выводятся на экран объекты - строки текста. По-
Окна и их оформление 273
скольку в соответствующих структурных переменных Attr для них не было задано ни положения, ни размеров, все эти окна необходимо позиционировать, для чего используется функция MoveWindow(). Окна класса Plain позиционируются в соответствии с заданными в глобальной переменной plainPos координатами и указанными в функции MoveWindow() размерами; со строками класса Quest дело обстоит сложнее, так как они имеют переменную длину. Для определения фактической длины строки (в числе пикселов, а не символов) служит функция GetTextExtent(), однако она учитывает характеристики шрифта, хранящегося в настоящий момент в контексте устройства, принадлежит по этой причине классу TDC и может вызываться только для объекта этого класса. Для создания объекта контекста устройства для нашего окна можно воспользоваться конструктором класса TClientDC (или TWindowDC), однако он требует в качестве параметра дескриптор окна (типа HWND), а у нас имеется только указатель quest на объект окна. Однако в классе TWindow, от которого образован наш класс Quest, описан оператор преобразования типа (см. гл. 22, пример 22-5). Если в какой-либо операции, требующей переменной типа HWND, указан объект класса TWindow, то, в соответствии с описанием оператора преобразования типа, вместо имени объекта подставляется конкретное значение одного из данных-членов класса TWindow, конкретно, дескриптора окна для этого объекта. В нашем случае в предложении
TClientDC tdc(*quest[i]); //Преобразование объекта TWindow в дескриптор окна
где в скобках должна стоять переменная типа HWND, указано обозначение указателя на объект со снятой ссылкой (знак звездочки), т.е. обозначение самого объекта. В результате выполняется преобразование типа и создается объект tdc - контекст нашего окна. Далее для него вызывается функция GetTextEx-tent() и полученное значение длины строки (несколько увеличенное, поскольку функция GetTextExtent() определяет длину строки неточно) используется при вызове функции перемещения окна.