Программирование под Win32

         

схожа по конечному результату


MyApp myApp; //Создаем экземпляр myApp класса MyApp

return myApp.Run( ); //Вызываем функцию-член Run()  класса TApplication }

Структура OWL-программы

Программа 25- 1 схожа по конечному результату с простейшим приложением Windows, рассмотрен­ным в гл. 6 (пример 6-1); в ней создается и выводится на экран главное окно приложения, для которого (в отличие от примера 6-1) задаются три дополнительных характеристики: местоположение окна на экране, его размер и цвет фона окна. Программа имеет типичную для OWL-приложений структуру и состоит из трех отчетливо выделенных частей: описания класса приложения и входящих в него функций, описания класса главного окна приложения с его функциями и, наконец, главной функции OwlMain(). Функции с таким именем передается управление при запуске приложения, и она, таким образом, должна обязатель­но присутствовать в любых OWL-приложениях, заменяя собой функцию WinMain() обычных приложе­ний Windows или функцию main() приложений MS-DOS.

Библиотека OWL, как и любая другая объектно-ориентированная библиотека для разработки прило­жений Windows, содержит описание классов для реализации практически всех основных средств Win­dows (окон, диалогов, органов управления, средств графического интерфейса GDI и т. д.). Составление приложения Windows, грубо говоря, заключается в подборе библиотечных классов, реализующих сред­ства, используемые в конкретном приложении, описании в программе прикладных классов, являющихся производными от библиотечных, и использовании затем наследуемых данных-членов и функций-членов этих классов. При необходимости библиотечные функции-члены модифицируются (путем их замещения прикладными функциями с теми же именами и сигнатурами) с целью придания им требуемых свойств, а данным-членам задаются требуемые значения. Во многих случаях библиотечные функции вполне удов­летворительно решают прикладную задачу и не нуждаются в модификации, и тогда используются не производные от библиотечных, а сами библиотечные классы: создаются их экземпляры (объекты) и вы­зываются необходимые функции-члены этих классов с передачей им конкретных параметров.



В рассматриваемом OWL- приложении используются только два библиотечных класса: класс прило­жения TApplication и класс главного окна TFrameWindow, который является производным от класса TWindow.

Класс TApplication, от которого мы создаем производный класс МуАрр (имя этого прикладного клас­са, разумеется, может быть любым) сам является производным от класса TModule, и, таким образом, эти три класса образуют иерархическую структуру, показанную на рис. 25.2.





дуля приложения (под модулем приложения понимают ту часть приложения, в которой сосредоточены его коды и ресурсы, в от­личие от экземпляра приложения, содержащего данные и очередь сообщений), в частности, организует загрузку и выгрузку дина­мических библиотек, а также предоставляет ряд информацион­ных функций, относящихся к модулю и приложению в целом. Так, в него входят функции-члены загрузки ресурсов LoadCur-sor(), Loadlcon(), LoadBitmap(), получения информации о свойст­вах модуля и приложения GetModuleFileName(), GetClassInfo(), Getlnstance(), обработки ошибок Error() и ряд других. В примере 25-1 функции базового класса TModule не используются.

Класс TApplication обеспечивает основные свойства приложения Windows. Функции этого класса, в частности, организуют создание главного окна (именно организуют; собственно создание окна возлага­ется на функции другого класса - TWindow), обрабатывают сообщения, поступающие в окно приложе­ния, обеспечивают загрузку динамических библиотек, позволяющих пользоваться органами управления в стиле Borland, и выполняют ряд других важных операций.

Всю работу по инициализации приложения, созданию и выводу на экран главного окна и организа­ции цикла обработки сообщений выполняет открытая функция-член Run(), входящая в класс TApplica­tion. Поэтому & простых случаях главная функция приложения OwlMain() может состоять всего из двух

224                                                                                                                                 Глава 25





Образовав и выведя на экран главное окно приложения, функция Run() вызовом функции Message-LoopO входит в цикл обработки сообщений, в котором приложение и находится до своего завершения.

Для того, чтобы можно было изменять характеристики и свойства главного окна, надо создать собст­венный класс главного окна (в примере 25-1 ему дано произвольное имя MyWindow), производный от библиотечного класса TFrameWindow. Класс TFrameWindow сам является производным от класса TWin­dow, и, таким образом, эти три класса образуют иерархическую структуру, показанную на рис. 25.4.





Класс TWindow является родоначальником ряда классов, описывающих функционирование разного рода окон (диалогов, органов управления и других), которые, естественно, наследуют возможности своего базового класса TWindow. Среди этих про­изводных классов есть и класс окон с рамками TFrameWindow.

В класс TWindow входит большое число (около 300!) функ­ций-членов, обеспечивающих общие черты поведения окон, на­значение многих из которых нам знакомо по части II этой кни­ги: Create(), ShowWindow(), MoveWindow(), GetClientRect(), In-validate(), SendMessage(), SetBkgndColor(), SetWindowWord() и т. д. В примере 25-1 использовалась функция SetBkgndColor(), устанавливающая цвет фона окна.

Помимо функций, в класс TWindow входит ряд данных-членов, из которых нас пока будет интересо­вать только структура типа TWindowAttr, которая представлена в классе TWindow данным-членом Attr. Элементы этой структуры определяют атрибуты окна, устанавливаемые во время его создания: стиль, координаты, размеры и др. В примере 25-1 с помощью элементов этой структуры устанавливаются коор­динаты и размеры окна.

Простейшее OWL-приложение Windows                                                                               225

Создав в программе производный от TApplication класс МуАрр, мы получили возможность переоп­ределить в нем функцию класса TApplication InitMainWindow(). Зачем это нужно? Как видно из рис. 25.3, в этой функции вызовом функции SetMainWindow() создается безымянный экземпляр класса TFrameWindow, к которому и обратиться-то нельзя. Нам нужно заменить его экземпляром производного от него класса My Window, с функциями и данными которого мы сможем работать. В замещенной функ­ции InitMainWindow() выполняется в сущности то же, что было предусмотрено в исходной функции, но в нужном нам варианте:



/* Замещенная функция InitMainWindow()*/

void

МуАрр::InitMainWindow(void){

MyWindow* myWin=new MyWindow(0,"Программа 25-1");//Создаем объект класса MyWindow

SetMainWindow(myWin);//Объявляем новое окно главным

}

Сначала создается экземпляр прикладного класса MyWindow с указателем на него mywin, а затем вызовом функции SetMainWindow() созданное окно объявляется главным. В процессе создания экземп­ляра класса MyWindow (т.е. вызова конструктора этого класса) конструктору передаются требуемые па­раметры, первый из которых (0) говорит о том, что у этого окна нет родителя (поскольку оно является главным), а второй представляет собой заголовок окна. Очевидно, что составить текст замещающей функции InitMainWindow можно, только изучив исходный текст замещаемой, который можно найти в файле \source\owl\applicat.cpp, содержащем исходные тексты всех функций класса TApplication.

Для того, чтобы описанный выше фрагмент работал должным образом, необходимо описать конст­руктор нашего класса MyWindow, предусмотрев в нем, разумеется, вызов конструктора базового класса TFrameWindow ради передачи в него инициализирующих параметров. Этот конструктор описан в файле bc5\include\owl\window.h следующим образом:

TFrameWindow(TWindow* parent,   const char  far  *title=0,TWindow*  clientWnd=0 bool  shrinkToClient=false,TModule* module=0);

Как видно из этого определения, при вызове конструктора в него необходимо передать только пер­вый параметр, для которого не предусмотрено значения по умолчанию, а все остальные можно не пере­давать. Однако второй параметр позволяет задать заголовок окна, поэтому конструктор TFrameWindow часто вызывается с двумя параметрами. В определении прикладного класса MyWindow

class MyWindow:public TFrameWindow{ public:

MyWindow(TWindow*parent,char  far*title):TFrameWindow(parent,title){ SetBkgndColor(COLOR_WINDOWFRAME+1);//Задаем цвет фона  окна   (серый) Attr.X=10;  Attr.Y=10;//Задаем координаты окна Attr.W=200;  Attr.H=60;//Задаем размеры окна } };



указывается, что они является производным от TFrameWindow и описывается единственная функция этого класса, именно, его конструктор. В конструкторе MyWindow предусмотрен вызов конструктора ба­зового класса TFrameWindow с передачей ему двух параметров parent и title. Как мы уже видели, при фактическом вызове этого конструктора ему передаются значения параметров 0 и "Программа 25-1".

Далее в текст конструктора MyWindow включены действия, которые мы хотим выполнить при соз­дании главного окна: вызов открытой функции-члена класса TWindow SetBkgndColor() для задания цвета фона окна, а также изменение действующих по умолчанию значений структуры Attr, определяющих ко­ординаты и размеры окна. В принципе эти действия не обязательно вносить в конструктор; их можно было выполнить и после создания экземпляра класса MyWindow, включив в функцию InitMainWindow следующие строки:

mywin->SetBkgndColor(COLOR_WINDOWFRAME+1); mywin->Attr.Х=20; mywin->Attr.Y=20; mywin->Attr.W=200; mywin->Attr.H=60;

Ясно, однако, что этим инициализирующим действиям самое место в конструкторе класса. Вернемся теперь к классу TApplication. Для того, чтобы переопределить его функцию-член Init-MainWindow(), мы создаем производный класс МуАрр:

/*Кпасс МуАрр приложения,  производной от Tapplication*/

class МуАрр:public TApplication{ public:

virtual void InitMainWindow(void); //Замещаем функцию InitMainWindow

};

В описании класса МуАрр указано, что он является производным от TApplication; конструктор этого класса мы не описываем, так как в классе нет никаких данных, и явный конструктор для него не нужен, а в конструкторе базового класса (описанном в файле bc5\include\owl\applicat.h)

226                                                    Глава 25

TApplication(const char

far*name=0,TModule*&gModule=::Module,TAppDictionary*appDict=0);

все параметры задаются по умолчанию, и его вызывать нет необходимости.

Описав в секции public класса МуАрр прототип виртуальной функции InitMainWindow(), мы замес­тили исходную функцию с тем же именем, описанную в базовом классе TApplication. Как отмечалось в гл. 24, при замещении виртуальной функции описатель virtual можно опустить; часто его оставляют ради наглядности.



Рассмотрим возможный вариант текста главной функции OwlMain(). В примере 25-1 обращение к объекту класса МуАрр происходило по его имени туАрр. Вместо имени объекта можно использовать его адрес; в этом случае текст функции OwlMain несколько изменится:

int OwlMain(int,char*[]){

МуАрр*  myApp=new МуАрр;

return myApp->Run(); }

Здесь, разумеется, myApp имеет другой смысл - это указатель на объект класса МуАрр.

Заголовочные и другие вспомогательные файлы

Как уже отмечалось в гл. 1, исходный текст практически любой программы включает в себя то или иное количество директив #include, с помощью которых препроцессор подключает к исходному тексту программы необходимые для ее успешной компиляции заголовочные файлы. В зависимости от средств, используемых в компилируемой программе, состав заголовочных файлов может сильно различаться. В примерах приложений DOS (часть I этой книги) использовались заголовочные файлы с описанием функ­ций и других средств общего назначения, например, файлы conio.h, iostream.h, stdio.h и др. В приложени­ях Windows, описанным в части II, использовались, наряду с файлами общего назначения, типично "Windows'овские" заголовочные файлы windows.h и windowsx.h.

Библиотека OWL содержит огромное количество заголовочных файлов, в которых описана вся ие­рархия OWL-классов. Для успешной компиляции OWL-программы к ней необходимо подключать, по крайней мере, файлы с описанием классов, используемых в программе. В действительности используе­мые в программе классы могут ссылаться тем или иным образом другие классы, описания которых тоже могут понадобиться компилятору. Поэтому состав заголовочных файлов, которые необходимо включить в программу, оказывается не очень определенным. Задача подбора необходимых заголовочных файлов несколько облегчается тем, что в заголовочные файлы многих классов уже включены директивы #in-elude, подключающие все файлы, необходимые для работы с данным классом.

В примере 25-1 используются библиотечные классы TApplication и TFrameWindow. Описания этих классов содержатся в заголовочных файлах с достаточно наглядными именами applicat.h и framewin.h. Поэтому мы и включили в текст программы директивы



#include <owl\applicat.h> #include <owl\framewin.h>

Реально, однако, в первой директиве нет необходимости. Дело в том, что в заголовочном файле framewin.h содержится директива #include <owl\window.h> с описанием класса TWindow, а в файле win-dow.h имеется целая группа директив #include, среди которых есть и директива #include <owl\applicat.h>. Таким образом, эту директиву в прикладную программу можно не включать.

Почему перед именами заголовочных файлов указывается вышележащий каталог OWL? Дело в том, что программный пакет системы Borland C++ состоит из нескольких тысяч файлов, организованных в виде сложной иерархической структуры каталогов. Все include-файлы помещены в ветвь этой структуры с именем include, однако в этом каталоге, наряду с некоторым количеством заголовочных файлов, име­ются еще и нижележащие каталоги. Заголовочные файлы, описывающие классы OWL, помещены в под­каталог с тем же именем (рис. 25.5).



Простейшее OWL-приложение Windows                                                                       227

Поскольку в настройках Borland C++ в качестве каталога для включаемых файлов обычно указывает­ся каталог \bc5\include, а нужные нам файлы расположены ниже, в подкаталоге OWL, в программе необ­ходимо указывать оставшуюся часть пути к включаемым файлам, начиная от каталога include.

Заголовочные файлы имеют содержательные имена, и не составляет труда найти в них описание лю­бого интересующего нас класса. Например, описания целого ряда классов контекстов устройств (TDC, TPaintDC и др.) содержатся в файле dc.h, описание класса управления сообщениями TEventHandler - в файле eventhan.h, описания многочисленных классов GDI-объектов, т.е. графических инструментов (TBitmap, TBrush, TPen, и т.д.) - в файле gdiobjec.h.

Описания классов, содержащиеся в заголовочных файлах, включают в себя типы данных-членов и прототипы функций-членов. Это чрезвычайно полезная информация, помогающая разобраться в струк­туре используемых в приложении классов и в допустимых способах обращения к их функциям или дан­ным. Однако во многих случаях работа с библиотечным классом, в частности, образование от него про­изводного класса и замещение его функций требует знакомства с исходными текстами конструкторов и других функций-членов библиотечных классов. Исходные тексты классов Borland C++ хранятся в ката­логе source (см. рис. 25.5), причем тексты библиотеки OWL находятся в подкаталоге с тем же именем.



Большую пользу может принести изучение программных примеров, включенных в пакет Borland C++. Как видно из рис. 25.5, здесь можно найти примеры приложений DOS (в каталоге DOS), приложе­ний Windows, написанных в традиционной манере (в каталоге windows), OWL-приложений (в каталоге OWL) и другие.

Глава 26

Обработка сообщения WM_PAINT и интерфейс графических устройств GDI

Исходный текст программы, выводящей в окно строку символов

В предыдущей главе была рассмотрена структура простейшего OWL-приложения с главным окном. Мы даже научились изменять размеры и цвет окна. Однако это окно было пусто - для того, чтобы в него что-то вывести, надо обрабатывать сообщение WM_PAINT и использовать инструменты графического интерфейса GDI. Принципы обработки сообщений Windows в OWL-прштожениях будут описаны в сле­дующей главе; здесь же мы рассмотрим только работу с сообщением WM_PAINT и вывод на экран изо­бражений с помощью функций GDI.

На рис. 26.1. приведен результат работы первого приложения, рассматриваемого в этой главе.



Рис. 26.1. Вывод на экран строки текста с помощью функции GDI.

//Приложение 26-1.   Вывод  текста  в  окно

//Файл 26-1.срр

#include <owl\framewin.h>

/*Класс приложения, производная от Tapplication*/

class MyApp:public TApplication{

public:

virtual void InitMainWindow(void);//Замещаем функцию InitMainWindow };

/*Класс главного окна, производный от TFrameWindow (ради Paint) */ class MyWindow:public TFrameWindow{ public:

MyWindow(TWindow*parent,const char far* title):TFrameWindow(parent,title){ Attr.X=20;Attr.Y=20;//Задаем координаты окна Attr.W=200;Attr.H=60;//Задаем размеры окна }

void Paint(TDC&,bool,TRect&);//Замещаем функцию Paint()класса TWindow };

/*Замещенная функция

InitMainWindow ()*/ void MyApp::InitMainWindow(void){

MyWindow* myWin=new MyWindow(0,"Программа

26-1");

SetMainWindow(myWin);

}

/*Замещенная функция Paint ()*/ void MyWindow::Paint(TDC&dc,bool,TRect&){//Определяем нашу функцию



Paint О

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

} /*Главная функция приложения OwlMain*/

int OwlMain(int,char*[]){

MyApp* myApp=new MyApp;

return myApp->Run(); }

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

Обработка сообщения 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С.



Предпочтительным является использование символических обозначений системных цветов, как это было сделано в примере 25-1, где для фона окна был задан цвет COLOR_WINDOWFRAME+1:

TColor  colorl(COLOR_WINDOWFRAME+1);     //Фактически будет светло-серый цвет

TColor color2(COLOR_WINDOW+1);                                   //Фактически будет  темно-зеленый цвет

Достоинство такого метода заключается в том, что при правильной настройке Windows все цвета бу­дут чистыми, а не составными. Неприятный для глаза составной цвет, т.е. цвет, состоящий фактически из набора точек нескольких разных цветов, может легко получиться при неправильном задании значений цветовых компонент. В частности, если при задании светло-кремового цвета ошибиться хотя бы на 1 в значениях компонент (указав, например, 241 вместо 240 или 254 вместо 255), то поле, закрашенное этим цветом, будет усеяно более темными точками (если только монитор не работает в режиме True Color). С другой стороны, при использовании символических констант не всегда легко определить заранее, какой получится цвет.

Рассмотрим теперь работу с инструментами рисования и контекстом устройства. Процедура созда­ния инструментов рисования (перьев, кистей, шрифтов), отличных от действующих по умолчанию, в принципе не отличается от той, что используется при традиционном программировании для Windows. Новый инструмент необходимо создать и выбрать контекст устройства, после чего все рисование будет осуществляться этим инструментом. При необходимости в контекст можно выбирать последовательно все новые и новые инструменты (например, перья или кисти разных цветов); каждый выбранный инст­румент будет действовать до замены его в контексте устройства следующим. Таким образом, типичная последовательно действий по замене инструмента будет выглядеть таким образом:

TPen myPen   (TColor::LtRed,3);       //Создаем перо красного цвета  толщиной 3 пиксела

dc.SelectObject(myPen);                              //Выбираем его в  текущий контекст устройства



dc.Rectangle(5,5,20,20);                       //Рисуем этим пером квадрат

В программах части II этой книги мы, во-первых, при выборе в контекст нового инструмента сохра­няли в некоторой временной переменной исходный инструмент; во-вторых, перед выходом из функции OnPaint() восстанавливали в контексте этот исходный инструмент; в-третьих, перед завершением про­граммы удаляли все созданные инструменты.

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

В OWL-программах эта процедура несколько упрощается, так как часть работы берут на себя функ­ции классов. При первой смене инструмента в контексте устройства функция SelectObject() сохраняет дескриптор инструмента, находящегося там по умолчанию (черное тонкое перо, белая кисть) в специаль­но отведенных для этого переменных-членах класса TDC OrgPen, OrgBrush, OrgFont и OrgPalette. Эти защищенные члены класса обычно недоступны программисту, однако они используются функциями Re-storePen(), RestoreBrush() и т.д., которые выбирают в контекст устройства сохраненные в перечисленных выше переменных дескрипторы инструментов. Поэтому при выборе в контекст нового инструмента нет необходимости сохранять старый (впрочем, и возможности такой тоже нет, так как используемая для выбора инструмента в контекст устройства функция SelectObject() ничего не возвращает). Далее, восста­навливать исходные инструменты в контексте тоже не обязательно, так как в деструкторе класса TDC вызывается функция RestoreObjects(), которая восстанавливает в контексте исходные значения всех де­скрипторов. Наконец, созданные инструменты можно не удалять, так как деструктор любого класса, вы­зываемый автоматически при выходе из процедуры, в которой был создан объект данного класса, удалит этот объект (между прочим, в составе классов инструментов, например, ТРеn или TBrush, даже нет функций типа DeletePen() или DeleteBrush(), что, впрочем, вполне естественно, так как основным назна­чением деструктора и является как раз удаление объекта).



Функциями восстановления исходного содержимого контекста устройства удобно пользоваться в тех случаях, когда порисовав, например, цветными перьями, мы хотим вернуться к исходному черному перу:

dc.SelectObject(pen1);                //Рисуем далее пером реп1

dc.SelectObject(pen2);                 //Рисуем далее пером реп2

dc.RestorePen();                             //Рисуем далее исходным черным пером

Логические шрифты





Процедуры   создания   и   использования   логических шрифтов в OWL-программах в принципе не отличаются от соответствующих процедур Windows API, описанных в гл. 15 и 16, а практически оказываются заметно проще. На рис. 26.5 приведен вывод программы 26-3, в которой демонстри­руются основные правила работы с логическими шрифтами. //Пример 26-3.   Логические шрифты 11 Файл 26-3.срр #include <owl\framewin.h> /*Константы,   описывающие размера изображения*/

const  dx=22;//Шаг no X

const  X=dx*9;//Ширина  графика  из  10  точек

const Y=120;//Высота  графика /*Надписи на графиках*/

char title1[]="Процесс  1";

char  title2[]="Процесс  2";

char  title3[]="Процесс  3";

char  legend[]="Ход процессов  во времени";

/*Объекты классов положения и размеров*/

TPoint X0Y0(30,10};//Верхний левый угол рамки

TPoint XmYm=X0Y0.OffsetBy(XBorder,YBorder);//Правый нижний угол рамки

TRect border(X0Y0,XmYm);//Прямоугольник рамки

/*Класс приложения,  производный от TApplication   (ради InitMainWindov)*/ class  MyApp:public  TApplication{ public:

void InitMainWindow();//Замещаем функцию InitMainWindow };

/*Класс главного окна,  производный от TFrameWindow   (ради Paint)*/ class MyWindow:public TFrameWindow{ public:

MyWindow(TWindow*parent,char  far*title):TFrameWindow(parent,title){ Attr.X=0;Attr.Y=0; Attr.W=245;Attr.H=200; }

void Paint(TDC&,bool,TRect&);//Переопределяем функцию Paint

};

/*3амещенная функция InitMainWindow()*/ void MyApp::InitMainWindow(void){

SetMainWindow(new MyWindow(0,"Программа 26-3"));



}

/*Замещенная функция Paint() */ void MyWindow::Paint(TDC&dc,bool, TRect&) {

int i; //Переменная циклов

char ticks [10] [2] ; //Массив цифр под осью X

236                                                    Глава 26

TFont font("Times New Roman",14};// Создаем шрифт для цифр под осью X dc.SelectObject(font);//Выбираем в контекст созданный шрифт TPoint р;//Текущая координата для рисования рисок dc.Rectangle(border);//Рисуем рамку for(i=0;i<=9;i++),{

p=border.BottomLeft()+=i*dx;//Текущая координата верхних концов рисок

dc.MoveTo(p);//Перемещаемся по верхним концам рисок

dc.LineTo(p.OffsetBy(0,5));//Рисуем риски вниз до рамки

wsprintf(ticks[i],"%d",i);IIПреобразуем цифры в символы

dc.TextOut(p.OffsetBy(-3,+3),ticks[i]);//Выводим цифры под осью

}

dc.TextOut(border.TopLeft().OffsetBy(-margins-25,-7),"100");//Выводим dc.TextOut(border.BottomLeft().OffsetBy(-margins-10,-7),"0");//масштаб /*Выводим на экран линейные графики трех процессов*/

TColor color1(COLOR_ACTIVECAPTION+1);//Системный цвет заголовка активного окна TColor color2(COLOR_INACTIVECAPTION+1);//Цвет заголовка неактивного окна TColor color3(COLOR_WINDOW+1);//Цвет окна приложения TPen pen1(color1,2);//Создаем первое перо

dc.SelectObject(pen1);//и выбираем его в контекст устройства dc.MoveTo(border.BottomLeft());//Перемещаемся в левый нижний угол рамки dc.LineTo(100,20);//Выводим график 1-го процесса (просто наклонная линия) TPen pen2(color2,2);//Создаем второе перо

dc.SelectObject(pen2);//и выбираем его в контекст устройства dc.MoveTo(border.BottomLeft());//Перемещаемся в левый нижний угол рамки dc.LineTo(220,40); Выводим график 2-го процесса (просто наклонная линия) TPen реnЗ(colors,2);//Создаем третье перо

dc.SelectObject(реnЗ);//и выбираем его в контекст устройства dc.MoveTo(border.BottomLeft());//Перемещаемся в левый нижний угол рамки dc.LineTo(220,100);// Выводим график 3-го процесса (просто наклонная линия) /*Создаем логические шрифты и выводим надписи на графиках*/



TFont font1("Arial",15,0,580);//Создаем шрифт для 1-го процесса dc.SelectObject(font1);//Выбираем его в контекст устройства TColor prevColor=dc.SetTextColor(color1);//Устанавливаем цвет шрифта dc.TextOut(40,80,title1);//Выводим текст у линии графика TFont font2("Arial",15,0,250);//Создаем шрифт для 2-го процесса dc.SelectObject(font2);//Выбираем его в контекст устройства dc.SetTextColor(color2);//Устанавливаем цвет шрифта dc.TextOut(135,60,title2); //Выводим текст у линии графика TFont font3("Arial",15,0,80);//Создаем шрифт для 3-го процесса dc.SelectObject(font3);//Выбираем его в контекст устройства dc.SetTextColor(color3);//Устанавливаем цвет шрифта dc.TextOut(130,95,title3); //Выводим текст у линии графика

TFont font4("Times New Roman",20,0,0,0,FW_BOLD,0,true);//Шрифт для подписи dc.SelectObject(font4);//Выбираем его в контекст устройства dc.SetTextColor(prevColor);//Восстанавливаем исходный цвет шрифта dc.TextOut(border.BottomLeft().OffsetBy(5,15),legend);//Выводим подпись }

/*Главная функция приложения OvlMain*/ int OwlMain(int,char*[]){

return MyApp().Run(); }

Программа 26- 3 использует многие элементы предыдущего примера. Точно так же в главное окно приложения выводится рамка с рисками и цифрами масштаба, но в отличие от предыдущего примера, отображаемые данные занимают все поле рамки border, а поле graph (и константа margins) отсутствуют. Соответственно несколько увеличено расстояние dx между рисками на горизонтальной оси, а также вы­сота рамки Y. Удалены также горизонтальные риски. Отображаемые данные представляют собой три ли­нейных графика, будто бы показывающих ход каких-то процессов во времени. Для большей наглядности графикам приданы разные цвета (для задания которых использованы символические обозначения сис­темных цветов), и над каждым графиком тем же цветом выведена поясняющая надпись. Еще одна общая надпись сделана в нижней части окна.

Новой здесь является только операция создания логических шрифтов, в которой используются объ­екты класса TFont. Конструкторы этого класса принимают полтора десятка параметров, расположенных более или менее по степени их важности. Поскольку все они имеют значения по умолчанию, при вызове конструктора можно указывать только требуемое количество первых параметров, не заботясь об осталь­ных. При этом, разумеется, если нужно задать значение какого-либо параметра, то необходимо указать значения и всех параметров, расположенных перед ним. Для многих параметров значением по умолча­нию является ноль, хотя не во всех случаях это так. Прототип конструктора для класса TFont имеет сле­дующий вид:



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

TFont(const char far*   facename=0,//Имя шрифта

int height=0,                                //Высота

int width=0,                                  //Средняя ширина

int  escapement=0,                    //Угол наклона  в  1/10 градуса

int orientation=0,                       //He используется

int weight=FW_NORMAL,        //Жирность

uint8 pitchAndFamily=DEFAULT_PITCH|FF_DONTCARE,//Шаг и семейство

uint8  italic=false,                    //Курсив

uint8  underline=false,      //Подчеркивание

uint8  strikeout=false,      //Перечеркивание

uint8  charSet=1,                        //Набор символов

uint8  outputPrecision=OUT_DEFAULT_PRECIS, //Требуемая точность  соответствия uint8  clipPrecision=CLIP_DEFAULT_PRECIS,// Способ вырезки части символа uint8  quality=DEFAULT_QUALITY);/1Качество

Подготовка нового шрифта осуществляется точно так же, как и для других инструментов рисования, именно, сначала требуемый шрифт создается вызовом конструктора, а затем его дескриптор выбирается в контекст устройства:

TFont  font("Times New Roman",14);//Создаем шрифт для цифр под осью X dc.SelectObject(font);//Выбираем в контекст созданный шрифт

В приведенном фрагменте создается шрифт с именем Times New Roman размером 14 пунктов, который затем будет использован для вывода цифр масштаба. Остальные параметры принимаются по умолчанию.

Для надписывания графиков удобно использовать наклонные строки. Поэтому при создании трех следующих шрифтов (font1, font2 и font3) указываются уже не два, а четыре параметра, последний из ко­торых определяет наклон строк текста. Наклон экспериментально подобран так, чтобы надписи выводи­лись параллельно соответствующим графикам.

В определение шрифта не входит его цвет, для которого в контексте устройства предусмотрена от­дельная переменная, устанавливаемая функцией TDC::SetTextColor(). В отличие от процедуры загрузки в контекст устройства дескрипторов инструментов, в которой, как отмечалось выше, предусмотрено авто­матическое сохранение исходного дескриптора, для цвета шрифта такая операция не предусмотрена. По­этому если мы хотим в какой-то точке программы вернуться к исходному черному цвету шрифта, надо либо сохранить исходный цвет, либо в нужном месте установить его явным образом. В примере иллюст­рируется методика сохранения исходного цвета, который возвращается функцией SetTextColor(). В конце программы сохраненный дескриптор prevColor (типа TColor) снова загружается в контекст устройства, в результате чего подпись под графиком выводится черного цвета. При вызове конструктора логического шрифта для этой подписи (объект font4) указывается в общей сложности 8 параметров: имя шрифта, раз­мер в пунктах, константа FW_BOLD, образующая полужирное начертание и значение true для последне­го параметра, определяющее курсивное начертание.







Пиктограммы и курсоры

Для демонстрации возможностей включения в прикладную программу собственных пиктограмм (значков) и курсоров ис­пользован видоизмененный соответствующим образом пример 26-1. Приведенный ниже вывод программы (рис. 26-6) отлича­ется от рис. 26.1 только значком в верхнем левом углу окна приложения и формой курсора.

//Приложение 26-4.   Пиктограммы и курсоры

//Файл 26-4.rс

myIcon  ICON  "26-4.ico"//Описание ресурса-значка   (ссылка на  файл)

myCursor CURSOR   "26-4.cur"//Описание ресурса-курсора   (ссылка на  файл)

//Файл

26-1.срр

#include <owl\framewin.h>

/*Глобальные переменные*/

HICON hIcon;//Дескриптор значка

HCURSOR hCursor;//Дескриптор курсора

/*Класс приложения, производный от TApplication (ради InitMainWindow)*/

class MyApp:public TApplication{

public:

virtual void InitMainWindow(void);//Замещаем функцию

InitMainWindow

};

/*Класс главного окна, производный от TFrameWindow (ради Paint и GetWindowClass) */ class MyWindow:public TFrameWindow{ public:

MyWindow(TWindow*parent,const char far*  title):TFrameWindow(parent,title){

238___________________________________________________ Глава 26

Attr.X=20;Attr.Y=20;//Задаем координаты окна Attr.W=200;Attr.H=60;//Задаем размеры окна

}

void Paint(TDC&,bool,TRect&);//Замещаем открытую функцию TWindow::Paint()

void GetWindowClass(WNDCLASS&);//Замещаем функцию

TWindow::GetWindowClass()

}; /*Замещенная функция

InitMainWindow()*/

void MyApp::InitMainWindow(void){

MyWindow* myWin=new MyWindow(0,"Программа

26-4");

SetMainWindow(myWin);

hIcon=LoadIcon("myIcon");//Загружаем значок и получаем его дескриптор

hCursor=LoadCursor("myCursor");//Загружаем курсор и получаем его дескриптор

} /*Замещенная функция Paint()*/

void MyWindow::Paint(TDC&dc,bool,TRect&){

dc.TextOut(10,10,"Строка текста");

}

/*Замещенная функция GetWindowClass()*/ void MyWindow::GetWindowClass(WNDCLASS& wс){



TWindow::GetWindowClass(wс);//Вызываем исходную функцию GetWindowClass()

we.hIcon=hIcon;//Добавляем в wc дескриптор значка

wc.hCursor=hCursor;//Добавляем в wc дескриптор курсора

} /*Главная функция приложения OwlMain*/

int OwlMain(int,char*[]){

MyApp* myApp=new MyApp;

return myApp->Run(); }

Любое уважающее себя приложение обладает собственным значком, по которому файл приложения можно легко отличить от других программ. Многие приложения (в частности, системы программирова­ния Borland C++, текстовый редактор Word и т.д.) содержат в себе не один, а несколько или даже много значков, из которых пользователь может выбрать наиболее привлекательный. Значок приложения ото­бражается в списке файлов при выводе на экран содержимого той или иной папки (каталога); на панели задач в нижней части Рабочего стола Windows; в левом углу заголовка окна, где этот значок выступает в качестве кнопки для вызова системного меню; в ярлыке приложения на рабочем столе или в какой-либо папке, если таковой ярлык был создан пользователем для облегчения нахождения и вызова приложения. Помимо этого, значки иногда выводятся непосредственно в окно приложения в качестве логотипа или заставки.

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





Новые изображения значка и курсора удобнее всего создать с помощью специализированного редактора ресурсов Resource Workshop, входяще­го в состав пакетов Borland C++. Файл с изобра­жением значка должен иметь расширение .ICO, файл с изображением курсора - расширение .CUR. Имена обоих файлов описываются в файле ресурсов (в настоящем примере - в файле 26-4.RC, см. текст программы выше). Произвольные имена, с которых начинаются строки описания ресурсов (у нас это myIcon и myCursor) будут в дальнейшем использоваться в программе в каче­стве идентификаторов ресурсов. В процессе ком­пиляции и сборки программы двоичные представ­ления всех значков и курсоров, описанных в фай­ле ресурсов, включаются в загрузочный модуль приложения; первый из значков используется сис­темой Windows в списках файлов и ярлыках (рис.



26.7), даже если в программе приложения нет никаких обращений к этому ресурсу и он, в сущности, не назначен приложению в качестве его значка.

Для придания окну приложения значка и курсора их следует сначала загрузить в память из загрузоч­ного файла приложения (и получить при этом их дескрипторы), а затем поместить эти дескрипторы в

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

системную структуру типа WNDCLASS, описывающее данное окно. Поскольку мы будем обращаться к дескрипторам ресурсов из разных функций, они описаны в начале программы, как глобальные перемен­ные hIcon и hCursor. OWL-функции загрузки значка LoadIcon() и курсора LoadCursor() включены в со­став класса TModule, поэтому их проще всего вызывать из функций классов, производных от TModule. У нас таким классом является класс приложения МуАрр (см. рис. 25.2), в котором мы переопределили функцию базового класса InitMainWindow(). В текст этой функции можно включить и строки загрузки ресурсов.

Со структурой типа WNDCLASS дело обстоит сложнее, так как во-первых, надо как-то суметь к ней обратиться, и во-вторых, надо сделать это до регистрации класса окна, чтобы сделанные нами исправле­ния зарегистрировались в Windows. Для того, чтобы выполнить требуемую операцию, придется более детально рассмотреть исходные тексты функций OWL, выполняющих создание главного окна приложе­ния (рис. 26.8).



Как уже говорилось, жизнь приложения в значительной степени определяется функцией TApplica-tion::Run(), которая выполняет инициализирующие действия, создает главное окно приложения и органи­зует цикл обработки сообщений. Заместив функцию TApplication::InitMainWindow(), мы получили воз­можность создать объект myWin класса главного окна MyWindow, а также (в настоящем примере) загру­зить необходимые ресурсы - значок и курсор и получить их дескрипторы.

Регистрация класса главного окна выполняется в функции TWindow::Create(), которая вызывает для этого функцию TWindow::Register(). Как видно из рис. 26.8, функция Register() сначала вызывает функ­цию TWindow: :GetWindowClass, назначение которой - заполнить структуру WNDCLASS значениями по умолчанию. Далее вызовом функции API Windows RegisterClass выполнятся регистрация класса окна. Желая изменить настройку структуры WNDCLASS, мы должны заместить функцию класса TWindow GetWindowClass(), подставив на ее место собственную функцию с тем же именем. Это замещение вы­полняется при описании прикладного класса MyWindow.



Очевидно, что в нашей функции GetWindowClass() мы прежде всего должны вызвать исходную функцию с тем же именем класса TWindow, чтобы она заполнила все поля структуры WNDCLASS тре­буемыми по умолчанию значениями. Лишь после этого можно изменить значения отдельных полей нуж­ным нам образом. Следует подчеркнуть, что необходимость вызова исходной, замещенной функции в общем случае совсем не очевидна. Так, например, заместив функцию InitMainWindow(), мы не вызывали исходный вариант, а фактически заново написали ее текст в своей программе с удобными для нас изме­нениями. Надо ли вызывать замещенные функции из замещающих (и, кстати, когда их вызывать - в на­чале замещающей функции или, возможно, в конце), а также какие строки допустимо включать в заме­щающие функции, можно определить, лишь детально рассматривая исходные тексты соответствующих классов OWL.

Наша программа выглядит несколько неуклюже из-за наличия в ней глобальных данных частного характера. Текст программы можно сделать заметно компактнее, если перенести операции загрузки ре­сурсов с получением их дескрипторов в функцию GetWindowClass(). В этом случае отпадет необходи­мость объявлять переменные hIcon и hCursor глобальными: их можно переместить в класс MyWindow. Однако функции LoadIcon() и LoadCursor() принадлежат классу TModule, и просто вызвать их из функ-

240                                                                                                                                  Глава 26

ции класса MyWindow нельзя, необходимо указать объект, для которого они вызываются. В нашей про­грамме указатель на объект производного от TModule класса MyApplication носит название mуАрр, од­нако в функции GetWindowClass() это конкретное имя, разумеется, не может быть известно. Поэтому придется в процессе выполнения функции GetWindowClass() динамически определить указатель на объ­ект приложения. Это можно сделать с помощью функции класса TWindow GetApplication(), которая воз­вращает указатель на объект класса TApplication, с которым связано наше окно. Измененный текст функции GetWindowClass() выглядит следующим образом:



void MyWindow::GetWindowClass(WNDCLASS& wc){

TWindow::GetWindowClass(wc);// Вызываем исходную функцию базового класса wc.hIcon=GetApplication()->TModule::LoadIcon("myIcon"); wc.hCursor=GetApplication()->TModule::LoadCursor("myCursor"); }

Перед функциями LoadIcon() и LoadCursor() пришлось указать их принадлежность к классу TModule, так как мы вызываем их из другого класса. Между прочим, обсуждаемый вариант программы возможен лишь потому, что функции LoadIcon() и LoadCursor() объявлены в классе TModule открытыми. Если бы они были закрытыми или даже защищенными, из другого класса их вызывать было бы нельзя.

Запустив приложение 26-4, можно убедиться в том, что созданный нами значок появляется не только в строке заголовка, но и на панели задач Windows (рис. 26.9).



Рассмотренная процедура придания приложению значка и курсора не является единственной. При­дать приложению значок и курсор можно, например, с помощью функций класса TWindow SetIcon() и SetCursor(). Их можно вызывать в замещающей функции InitMainWindow() после образования объекта класса MyWindow. Функции SetIcon() и SetCursor() являются открытыми, однако, чтобы вызывать их из функции, принадлежащей другому классу, необходимо указать конкретный объект, для которого она вы­зываются, или, как в приведенном ниже фрагменте, указатель на объект:

void MyApp::InitMainWindow(void){

MyWindow* myWin=new MyWindow(0,"Программа  26-4"); SetMainWindow(myWin); myWin->SetIcon(this,"myIcon"); myWin->SetCursor(this,"myCursor"); }

В качестве первого аргумента и той, и другой функции необходимо использовать указатель на объект приложения (производный от класса TModule), для которого осуществляется операция назначения ре­сурсов. У нас этот указатель будет носить имя mуАрр, однако в функции-члене класса использовать имена конкретных объектов, разумеется, нельзя, а можно только вызывать функции Windows для дина­мического, в процессе выполнения программы, получения требуемых имен. Здесь, как и в предыдущем фрагменте, можно воспользоваться функцией GetApplication(), вызывав ее для только что созданного указателя myWin



myWin->SetIcon(myWin->GetApplication(),"myIcon");

однако проще и изящнее использовать указатель this (см. предыдущий фрагмент), который в функции, принадлежащей классу МуАрр, как раз и указывает на текущий объект этого класса.

Вывод растровых изображений

Вывод программы, рассматриваемой в настоящем разделе, приведен на рис. 26.10.

//Приложение  26-5.

11Файл 26-5.rс

myPicture BITMAP "picture.bmp"//Ссылка на файл с растровым изображением

//Файл

26-5.срр

#include <owl\framewin.h>

/*Класс приложения, производный от TApplication (ради InitMainWindow)*/

class MyApp:public TApplication{ public:

virtual void InitMainWindow(void);//Замещаем функцию InitMainWindow };



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



/*Класс главного окна, производный от TFrameWindow (ради Paint) */

class MyWindow:public TFrameWindow{ private:

TBitmap* bitmap;//Создаем указатель на объект - изображение в памяти public:

MyWindow(TWindow*parent,const char far*  title):TFrameWindow(parent,title){ Attr.X=0;Attr.Y=0;//Задаем координаты окна Attr.W=::GetSystemMetrics(SM_CXSCREEN);//Задаем размеры Attr.H=::GetSystemMetrics(SM_CYSCREEN);//окна - на весь экран bitmap=new TBitmap(GetModule()->GetInstance(),"myPicture");//Загружаем ресурс }

~MyWindow(){delete bitmap;}//Деструктор ради удаления bitmap

void Paint(TDC&,bool,TRect&);//Замещаем функцию Paint()

};

/*Замещенная функция InitMainWindow() */ void MyApp::InitMainWindow(void){

MyWindow* myWin=new MyWindow(0,"Программа 26-5");

SetMainWindow(myWin);

}

/*3амещенная функция Paint()*/ void MyWindow::Paint(TDC&dc,bool,TRect&) {

TMemoryDC memdc (dc); //Создаем совместимый контекст

memdc.SelectObject(*bitmap);//Выбор изображения в совместимый контекст

dc.BitBlt(10,80,bitmap->Width(),bitmap->Height(),memdc,0,0,SRCCOPY);//Копирование /*Далее выполняется вывод в окно текстовых строк*/



TFont font1("Times New Roman",22,О,О,0,FW_BOLD);

TFont font2("Times New Roman",22);

TFont font4("Garamond",36,0,0,0,0,0,true);

TFont font5("Academy",40,0,0,0,FW_BOLD);

dc.SelectObject(font1);

TRect rect1(0,10,GetSystemMetrics(SM_CXSCREEN),50) ;

char title1[]="Московский инженерно-физический институт";

dc.DrawText(title1,strlen(title1),rect1,DT_CENTER);

dc.SelectObject(font2);

TRect rect2(0,35,GetSystemMetrics(SM_CXSCREEN),70) ;

char title2[]="(технический университет)";

dc.DrawText(title2,strlen(title2),rect2,DT_CENTER);

TRect rect3(200,150,GetSystemMetrics(SM_CXSCREEN),250);

242                                                    Глава 26

char title3[]="Факультет повышения квалификации\nспециалистов промышленности";

dc.DrawText(title3,strlen(title3),rect3,DT_WORDBREAK|DT_CENTER);

dc.SelectObject(font4);

TRect rect4(0,300,GetSysternMetrics (SM_CXSCREEN),380);

char title4[]="Практический курс";

dc.TextOut(350,320,title4);

dc.SelectObject(font5);

dc.SetTextColor(TColor::Gray);

TRect rect5(0,380,GetSystemMetrics(SM_CXSCREEN),430);

char title5[]="Программирование для Windows";

dc.DrawText(title5,strlen(title5),rect5,DT_CENTER); }

/*Главная функция приложения

OwlMain*/ int OwlMain(int,char*[]){

MyApp* myApp=new MyApp;

return myApp->Run();

}                                                         .

Вспомним основные этапы процедуры вывода на экран растровых изображений (см. гл. 17).

•   Изображение, хранящееся в файле с расширением .BMP, загружается в память (функцией LoadBit-

map()). Система возвращает дескриптор этой области памяти

•   Создается контекст памяти, совместимый с нашим окном (функцией CreateCornpatibleDC())

•   Дескриптор области памяти с изображением выбирается в совместимый контекст (функцией Se-

lectBitmap())

•   Изображение из памяти копируется в окно (функцией BitBlt())

При использовании библиотеки OWL надо сделать, в сущности, то же самое, но с использованием объектов подходящих классов и их функций-членов:



bitmap=new TBitmap(GetModule()->GetInstance(),"myPicture");//Загружаем изображение TMemoryDC memdc(dc);//Создаем совместимый с окном контекст памяти

memdc.SelectObject(*bitmap);// Выбор дескриптора  изображения в  совместимый контекст dc.BitBlt(10,80,bitmap->Width(),bitmap->Height(),memdc,0,0,SRCCOPY);//Копирование

Область памяти с растровым изображением создается конструктором класса TBitmap. Объект этого класса и будет представлять собой требуемую область. Поскольку область памяти с растром можно соз­дать различными способами (копированием, из буфера обмена и др.), в классе TBitmap описано несколь­ко (конкретно 10) конструкторов, отличающихся составом параметров. Для нашей программы мы вы­брали конструктор

TBitmap (HINSTANCE, TResID)

который создает область памяти для приложения с указанным дескриптором из заданного ресурса. В текст конструктора (см. файл bitmap.cpp в каталоге source\owl) входит вызов функции API Windows LoadBitmap(), что избавляет нас от необходимости заботиться о загрузке изображения - оно будет загру­жено автоматически в процессе создания экземпляра класса TBitmap.

Как получить дескриптор приложения, необходимый для вызова конструктора TBitmap? В классах TWindow и TFrameWindow, от которых происходит наш класс MyWindow, нет функций для получения дескриптора экземпляра приложения. Такая (открытая) функция Getlnstance() есть в классе TModule, но чтобы вызвать ее из конструктора класса MyWindow, надо указать объект класса TModule, для которого она вызывается. А вот для получения указателя на этот объект в классе TWindow есть функция GetMod-ule(). Это дает нам возможность получить для конструктора класса MyWindow первый параметр. Вторым параметром служит имя ресурса (у нас - myPicture), как оно записано в файле ресурсов.

Указатель на область памяти, создаваемую с помощью оператора new в конструкторе класса MyWin­dow, должен быть предварительно описан, как данное-член этого класса. Мы присвоили ему атрибут pri­vate, так как обращение к нему будет осуществляться исключительно из функций этого же класса, кон­кретно, из функции Paint().



Дальнейшие операции, использующие контексты устройств, должны выполняться непосредственно в функции Paint(), обрабатывающей сообщение WM_PAINT. Прежде всего с помощью конструктора клас­са TMemoryDC создается совместимый с контекстом устройства dc контекст памяти memdc. Далее функцией TMemoryDC::SelectObject() в него выбирается заданный объект. Из описания функции Selec-tObject()

void SelectObject(const TBitmap&)

видно, что в качестве параметра этой функции выступает объект класса TBitmap, передаваемый по ссыл­ке. У нас в программе объявлено не имя объекта, а указатель на него bitmap, из которого сам объект по­лучается снятием ссылки (символ *).

Наконец, последней операцией является копирование изображения из памяти в окно приложения. Для этого используется функция блочной передачи данных класса TDC BitBlt(), по своему назначению и

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

параметрам совпадающую с одноименной функцией API Windows, которую она инкапсулирует. Исполь­зованные в примере функции Width() и Height() принадлежат классу TBitmap и позволяют получить ши­рину и высоту созданное области памяти.

Контекст области памяти memdc, как это подробно описывалось в гл. 17, можно использовать для вывода в эту область, помимо растровых изображений, также и графических элементов, рисуемых с по­мощью перьев, кистей и шрифтов. Например, предложение

memdc.TextOut(102,4,"0") ;

(выполненное, разумеется, перед копированием изображения в окно) добавит к нашему изображению символ 0 (рис. 26.11).



Последнее замечание касается уничтожения создаваемых объектов. Все объекты, создаваемые в про­грамме в явном виде оператором new, необходимо в каком-то месте программы уничтожить (оператором delete). Проще всего для этого воспользоваться деструктором соответствующего класса. В нашем приме­ре уничтожение выделенной области памяти осуществляется в деструкторе класса MyWindow:

~MyWindow(){delete bitmap;}//Деструктор ради удаления bitmap



Глава 27

Обработка сообщений Windows

Исходный текст программы календаря-часов

На рис. 27.1. приведен результат работы приложения 27-1.



//Приложение 27-1.   Обработка  сообщений Windows

//Файл 27-1.срр

#include <owl\framewin.h>

#include <time.h>

/*Класс приложения, производный от Tapplication*/

class MyApp:public TApplication{

public:

virtual void InitMainWindow();//Замещаем функцию

InitMainWindow

};

// Класс главного окна, производный от TFrameWindow class MyWindow:public TFrameWindow{ private:

char szText[27];//Поле для даты, пробелов слева и справа и завершающего public:

MyWindow(TWindow*,const char far*);//Конструктор класса

MyWindow

void Paint(TDC&,bool,TRect&);//Переопределяем функцию Paint

void EvGetMinMaxInfо(MINMAXINFO far&);//Объявляем функции обработки

void EvTimer(UINT);//сообщений

WM_MINMAXINFO, HM_TIMER

int EvCreate(CREATESTRUCT far&);//и WM_CREATE

void OutTime();//Прикладная функция получения времени

~MyWindow();//Деструктор класса MyWindow

DECLARE_RESPONSE_TABLE(MyWindow);//Объявляем таблицу откликов

}; DEFINE_RESPONSE_TABLE1(MyWindow,TFrameWindow)  //Начинаем таблицу откликов

EV_CREATE                                      //Описываем состав

EV_WM_GETMINMAXINFO,                        //таблицы

EV_WM_TIMER,                               //откликов

END_RESPONSE_TABLE;                          //Завершаем таблицу откликов

/*Конструктор класса MyWindow*/ MyWindow::MyWindow(TWindow*parent,const char far* title):

TframeWindow(parent,title){

Attr.X=0;Attr.Y=0;//Задаем координаты окна

Attr.W=0;Attr.H=0;//Задаем начальные размеры окна

Attr.ExStyle=WS_EX_TOPMOST;//Назначаем главное окно окном переднего плана

} /*Деструктор класса MyWindow*/

MyWindow::~MyWindow(){

Обработка сообщений Windows                                                                                            245

KillTimer(1); } /*Функции откликов на сообщения*/

int MyWindow::EvCreate(CREATESTRUCT far&){



SetTimerd,1000) ;//Устанавливаем таймер на частоту 1с

OutTime() ; //Сразу же выводим в окно текущее время

return 0;

} void MyWindow::EvGetMinMaxInfо(MINMAXINFO far & mmi){

mmi.ptMinTrackSize.x=185; mmi.ptMinTrackSize.y=45; mmi.ptMaxTrackSize.x=185;

mmi.ptMaxTrackSize.y=45;

} void MyWindow::EvTimer(UINT){

OutTime();

}

/*Функция

OutTime() чтения и преобразования текущего времени*/ void MyWindow::OutTime (){

tm* tmTime;// Указатель на структуру для хранения времени

time_t sec=time(NULL);//Получение числа секунд от 01.01.1970

tmTime=localtime(&sec);//Преобразование в формат даты/времени

strcpy(szText," ");//Один пробел в начале для красоты

strcat(szText,asctime(tmTime));//Преобразование в коды ASCII

szText[strlen(szText)-1]='\0';//Замена завершающего CR на нуль

Invalidate();//Инициирование перерисовки рабочей области окна

}

/*3амещающая функция Paint()*/ void MyWindow::Paint(TDC&dc,bool,TRect&){

dc.TextOut(0,0,szText);

}

/*3амещакщая функция

InitMainWindow() */ void MyApp::InitMainWindow(){

MyWindow* myWin=new MyWindow(0,"Текущее время");

SetMainWindow(myWin);

}

/*Главная функция приложения

OwlMain*/ int OwlMain(int,char*[]){

MyApp* myApp=new MyApp;

return myApp->Run();

}

Таблица откликов и функции обработки сообщений

В предыдущей главе уже отмечалось, что для обработки сообщения WM_PAINT, имеющего особую важность, в OWL предусмотрена упрощенная процедура, заключающаяся в предоставлении программи­сту функции-заглушки Paint(), которую можно заместить прикладной функцией с тем же именем. В за­мещающей функции Paint() оказывается доступен контекст устройства dc, что дает возможность исполь­зовать для вывода в окно приложения весь богатый набор функций GDI.

Для обработки других сообщений Windows (от мыши, пунктов меню, таймера и т.д.) в программе не­обходимо предусмотреть следующие элементы:

•   Объявление в классе главного окна таблицы откликов (макрос DECLARE_RESPONSE_TABLE)



•   Саму таблицу откликов (макросы DEFINE_RESPONSE_TABLE1 и END_RESPONSE_TABLE, a

также помещаемые между ними макросы, определяющие конкретные сообщения)

•   Набор функций, обрабатывающих по заданным алгоритмам описанные в таблице откликов сооб­

щения Windows

Таблица откликов объявляется, как элемент класса главного окна с помощью макроса

DECLARE_RESPONSE_TABLE, с именем нашего конкретного класса в качестве параметра. При расши­

рении этого макроса в класс главного окна включается объявление указателя__ entries[] на массив струк­

турных пакетов данных, используемых для вызова конкретных функций отклика, а также прототип слу­

жебной функции Find(), с помощью которой осуществляется нахождение в таблице откликов элемента,

соответствующего пришедшему сообщению. Расширение указанного макроса содержится в заголовоч­

ном файле include\owl\eventhan.h, который наряду со многими другими файлами .h автоматически под­

ключается к компилируемому файлу приложения при выполнении директивы #include framewin.h. По­

скольку в расширении макроса присутствуют "собственные" спецификаторы доступа (private и public),

его можно располагать в любой секции класса, однако после этого макроса необходимо соответствую-

246                                                                                                                                  Глава 27

щим спецификатором восстановить требуемый уровень доступа. Обычно макрос объявления таблицы откликов помещают в конце описания класса, и тогда восстанавливать уровень доступа не надо.

Таблица откликов может располагаться в любом месте программы. Она начинается с макроса DEFINE_RESPONSE_TABLE1 (описанного в том же файле eventhan.h), в качестве параметров которого указываются имена класса главного окна и родительского класса TFrameWindow. При расширении этого макроса в текст программы включается описание объявленной ранее функции Find(), а также строка, на­чинающая описание массива структурных пакетов данных для конкретных сообщений:



MyWindow::_____ entries[   ]={

Перечисляемые после этого через запятые условные имена обрабатываемых в приложении сообще­

ний, вроде EV_WM_TIMER или EV_WM_GETMINMAXINFO (которые, в свою очередь, являются мак­

росами), выступают в качестве элементов массива___ entries[]. Для обозначения конца этого массива ис­

пользуется макрос END_RESPONSE_TABLE, который расширяется в описание "пустого" структурного

пакета данных и завершающей фигурной скобки:

{0,   0,    0,   0}}

Описание любого массива должно, разумеется, завершаться знаком точки с запятой, которого, за­метьте, нет в расширении макроса END_RESPONSE_TABLE. Этот знак включаем в программу мы сами, указывая его после вызова этого макроса (см. текст примера 27.1).

В файле eventhan.h описаны четыре макроса вида DEFINE_RESPONSE_TABLEx, различающиеся по­следним символом-цифрой и предназначенные для использования в классах окон с различным числом родительских (базовых) классов, которые, в свою очередь, содержат внутренние таблицы отклика. Наш класс MyWindow является единственным потомком вертикальной (не разветвленной) структуры классов (см. рис. 25.4), поэтому мы использовали вариант макроса DEFINE_RESPONSE_TABLE 1. Если бы класс MyWindow происходил от двух или трех базовых классов, расположенных непосредственно над ним (и содержащих собственные таблицы отклика на те или иные сообщения), то для образования таблицы от­кликов пришлось бы использовать макросы DEFINE_RESPONSE_TABLE2 или DEFINE_RESPONSE_TABLE3. Для классов окон, не имеющих базовых, используется макрос DEFINE_RESPONSE_TABLE. Расширения указанной группы макросов различаются видом функции Find(), осуществляющей поиск в массиве структурных пакетов данных конкретного пакета, соответст­вующего пришедшему сообщению, что дает возможность в дальнейшем вызвать прикладную функцию обработки этого сообщения. Если для поступившего сообщения в нашей таблице отклика не нашлось со­ответствия, функция Find() пытается найти такое соответствие во всех базовых классах, имена которых мы предоставляем функции Find() через параметры использованной в программе разновидности макроса DEFINE_RESPONSE_TABLE. В нашем случае сообщения ищутся, кроме класса MyWindow, еще в базо­вом классе TFrameWindow.



Каждый из макросов вида EV_WM_TIMER, описывающих структурные пакеты данных для конкрет­ных сообщений, образует при расширении последовательность из четырех элементов, в которую входят имя обрабатываемого сообщения, код нотификации, имя соответствующего данному сообщению функ­ции-диспетчера и имя прикладной функции, предназначенной для обработки данного сообщения (на­пример, функции EvTimer() в нашем примере).

Функция-диспетчер (все эти функции объявлены в файле \include\owl\dispach.h и описаны в файле \source\owl\dispatch.cpp) служит для преобразования параметров wParam и lParam поступившего сообще­ния в соответствующие типы данных и вызова конкретной функции обработки сообщения с передачей ей этих данных в качестве параметров. В обычной, процедурной программе Windows задачу выделения из параметров сообщения "значимых" данных, а также вызова прикладных функций обработки сообщений выполняет макрос HANDLE_MSG (см. гл. 9). При этом для каждой функции обработки того или иного - сообщения жестко определен свой состав и порядок параметров, однако имена этих функций, которые мы указываем в качестве параметров макросов HANDLE_MSG, могут быть любыми. В библиотеке же OWL имена функций обработки сообщений заранее вписаны в макрорасширения макросов, составляю­щих тело таблицы откликов, и произвольно выбирать их нельзя. Как видно из программы 27-1, содержи­мое таблицы откликов составляется из обозначений вида EV_WM_TIMER или EV_WM_GETMINMAXINFO, где к имени сообщения Windows прибавляется префикс EV_ (от event, со­бытие). Имена же функций обработки формируются из префикса Ev и имени сообщения (без префикса WM_), в котором все отдельные составляющие слова пишутся с прописной буквы (EvTimer, EvGetMin-Maxlnfo, EvGetFont, EvSysKeyUp и т.д.).

Так же, как и при процедурном программировании, заранее трудно угадать состав параметров каж­дой функции обработки сообщения. Перечень стандартных сообщений Windows с указанием формата их функций обработки можно найти в статье Standard Windows Messages интерактивного справочника, вхо­дящего в состав пакета Borland C++.



Обратимся теперь к тексту примера 27-1, который по сути не отличается от программы 19-3.

В классе главного окна MyWindow объявлены функции обработки сообщений WM_PAINT для пере­рисовывания окна, WM_CREATE для выполнения инициализирующих действий, конкретно, установки таймера Windows, WM_TIMER для смены содержимого окна каждую секунду и

Обработка сообщений Windows                                                                                     247

WM_GETMINMAXINFO для придания окну фиксированных размеров. Как и в примерах предыдущей главы, обработка сообщения WM_PAINT выполняется путем замещения функции-заглушки Paint(); для обработки остальных сообщений предусмотрены записи в таблице откликов и соответствующие им функции откликов. В состав членов класса входят также поле szText для хранения текущей даты в сим­вольной форме, предложение с объявлением таблицы откликов (макрос DECLARE_RESPONSE_TABLE) и деструктор класса, который используется для уничтожения таймера, устанавливаемого при создании окна. Переменная szText, используемая только функцией-членом класса OutTime(), объявлена закрытой (private) в соответствии с канонами объектно-ориентированного программирования, требующими мак­симального скрытия данных. По тем же мотивам можно было объявить закрытыми и все функции откли­ка, чтобы у программиста не появилось искушение обратиться к ним не из функций-членов класса Му-Window, а из основной программы. В нашем учебном примере все эти рассуждения, разумеется, не име­ют никакого значения.

В конструкторе класса MyWindow с помощью структурной переменной Attr, являющейся элементом класса TWindow, устанавливаются начальные координаты и размеры окна, а также значение расширен­ного стиля окна WS_EX-TOPMOST, что заставляет наше окно всегда находиться на переднем плане. Размеры окна, будут в дальнейшем установлены в функции обработки сообщения WM_GETMINMAXlNFO, поэтому здесь для них указаны нулевые значения.

В функции обработки сообщения WM_CREATE устанавливается таймер с номером 1 и величиной временного интервала 1000 мс=1с и вызывается прикладная функция OutTime() для вывода в окно теку­щего времени, чтобы избежать вывода на экран в течение первой секунды после запуска приложения пустого окна.



Функция OutTime() была описана в гл. 19. В ней после получения текущего времени и преобразова­ния его в символьную форму вызывается функция-член класса TWindow Invalidate(), которая инициирует сообщение WM_PAINT и перерисовывание окна с новым значением времени. Заметьте, что здесь вызы­вается не функция API Windows InvalidateRect(HWND, RECT FAR*, BOOL), требующая указания деск­риптора перерисовываемого окна, области перерисовывания и значения флаги перекраски фона, а инкап­сулированная в классе TWindow функция Invalidate(bool erase=true), которая может вызываться вообще без параметров, если программиста устраивает задаваемое по умолчанию значение флага перекраски фо­на erase (фон перекрашивается автоматически).

Исходный текст программы с главным меню

На рис. 27.2. приведен результат работы приложения 27-2.



//Приложение 27-2.   Обработка   сообщений от пунктов меню

//Файл 27-2.rс #include   "27-2.h" #include <owl\window.rh> MainMenu MENU{ POPUP  "&Файл"{

MENUITEM  "&О программе",   CM_ABOUT

MENUITEM   "&Выход",   CM EXIT

} POPUP   "&Графики"{

MENUITEM   "&Синус",   CM_SIN

248                                                    Глава 27

MENUITEM "&Косинус", CM_COS } }

//Файл 27-2.h #define CM_ABOUT 101 #define CM_SIN 102 #define CM_COS 103

//Файл 27-2.cpp

#include <owl\framewin.h>

#include "27-2.h"

#include <math.h>

/*Класс приложения, производный от ТApplication*/

class MyApp:public TApplication{

public:

void InitMainWindow();//Замещаем функцию InitMainWindow

};

/*Класс главного окна, производный от

TframeWindow*/ class MyWindow:public TFrameWindow{

double sine[640],cosine[640];//Массивы данных для графиков

bool sinIs,cosIs;//Индикаторы наличия данных для графиков

void Paint(TDC&,bool,TRect&);//Замещаем функцию Paint

void CmAbout();//Функция обработки сообщения от пункта "О программе"



void Cmsin();// Функция обработки сообщения от пункта "Синус"

void CmCos();//Функция обработки сообщения от пункта "Косинус" public:

MyWindow(TWindow*parent,const char far* title);//Конструктор

DECLARE_RESPONSE_TABLE(MyWindow);//Объявляем таблицу откликов

}; DEFINE_RESPONSE_TABLE1(MyWindow,TFrameWindow)  //Описываем

EV_COMMAND(CM_ABOUT,CmAbout),                //таблицу откликов

EV_COMMAND(CM_SIN,CmSin),                   //от пунктов

EV_COMMAND(CM_COS,CmCos),                   //меню

END_RESPONSE_TABLE;/7Завершаем таблицу откликов /*Конструктор класса MyWindow*/ MyWindow::MyWindow(TWindow*parent,const char far* title):TframeWindow

(parent,title){

AssignMenu("MainMenu"); //Установка меню главного окна по идентификатору

// "MainMenu"

sinIs=false; cosIs=false;//Начальные значения индикаторов: данные отсутствуют

}

/*Функции откликов на сообщения*/ void MyWindow::CmAbout(){

MessageBox("Демонстрация математических функций","О программе", MB_ICONINFORMATION);

} void MyWindow::CmSin(){

for(int i=0;i<640;i++)//В цикле по 640 точкам

sine[i]=sin((double)i/20);//вычисляем и сохраняем в sine[] значения синуса

sinIs=true;//Данные для графика синуса есть

Invalidate();//Инициируем сообщение WM_PAINT

} void MyWindow::CmCos(){

for(int i=0;i<640;i++)//В цикле по 640 точкам

cosine[i]=cos((double)i/20);//вычисляем и сохраняем значения косинуса

cosIs=true;//Данные для графика косинуса есть

Invalidate();//Инициируем сообщение WM_PAINT

}

/*Замащающая функция InitMainWindov())*/ void MyApp::InitMainWindow(){

MyWindow* myWin=new MyWindow(0,"Программа 27-2");

SetMainWindow(myWin);

EnableBWCC () ; //Разрешаем загрузку и. использование

BWCC.DLL }

/*Функция обработки сообщения

WM_PAINT*/ void MyWindow: :Paint(TDC&dc,bool,TRect&) {

int y0=150;//Сдвиг начала координат по оси у

dc.MoveTo(0,у0);//Смещаем текущую позицию к началу оси X

dc.LineTo(640,y0);//Проводим ось X



if(sinIs==true)//Если данные по синусу есть,

for(int i=0;i<640;i++)//то в цикле по 640 точкам

Обработка сообщений Windows                                                                                            249

dc.SetPixel(i,y0-(int)(sine[i]*100),TColor::LtBlue);//выводим точки графика if(cosIs==true)//Если данные по косинусу есть,

for (int i = 0; i<640; i++) //то в цикле по

640  точкам dc.SetPixel(i,y0-(int)(cosine[i]*100),TColor::LtGreen);//выводим точки графика

} /*Главная функция приложения OwlMain*/

int OwlMain(int,char*[]){return myApp().Run();}

Обработка сообщений от пунктов меню

Меню главного окна приложения описывается, как обычно, в файле ресурсов .rс. Значения иденти­фикаторов пунктов меню (имена идентификаторов могут быть любыми) определены в файле .h. Для пункта выхода из приложения использован стандартный идентификатор СМ_ЕХIТ (со значением 24310), описанный в файле window.rh. С таким же успехом можно было использовать другое обозначение (на­пример, CM_QUIT) или присвоить идентификатору СМ_ЕХIТ произвольное значение.

Для того, чтобы меню появилось в окне приложения, достаточно вызвать функцию AssignMenu(), принадлежащую классу TFrameWindow, с указанием в качестве параметра имени меню из файла ресур­сов; этот вызов удобнее всего выполнить в





конструкторе класса MyWindow. Как и в слу­чае API Windows, обработку сообщений от "корневых" пунктов меню (в нашем случае это пункты "Файл" и "Графики") Windows берет на себя: при выборе этих пунктов Win­dows отображает на экране соответствующие всплывающие меню (рис. 27.3). Нам же надо обрабатывать сообщения от пунктов всплы­вающих меню (их часто называют командами

меню), для чего необходимо включить в таблицу откликов сообщения EV_COMMAND - по одному на каждую команду меню (обратите внимание на отсутствие в обозначении сообщения EV_COMMAND префикса WM).

В отличие от макросов типа EV_WM_TIMER или EV_WM_GETMINMAXINFO, которые при расши­рении задают имена функций отклика, макрос EV_COMMAND требует указания двух параметров -идентификатора соответствующего пункта меню и имени прикладной функции отклика, предназначен­ной для обработки сообщения от этого пункта:



EV_COMMAND(CM_ABOUT,CmAbout) , EV_COMMAND(CM_SIN,CmSin),...

Обычно функциям отклика назначают имена, схожие с идентификаторами (например, CmAbout для пункта меню с идентификатором CM_ABOUT), однако это не обязательно. Разумеется, для всех вклю­ченных в программу функций отклика необходимо объявить их прототипы (в составе класса MyWindow) и определить содержание самих функций. Исключение составляет команда "Выход". Если для этого пункта меню использовать стандартный идентификатор СМ_ЕХIТ, то всю обработку команды пользова­теля на завершение программы берет на себя Windows. Таким образом, для пункта меню с идентифика­тором CM_EXIT не надо иметь ни функции отклика, ни даже макроса EV_COMMAND в составе табли­цы откликов (разумеется, лишь в том случае, если выбором этого пункта меню мы хотим именно завер­шить программу).

Содержательная часть примера 27-2 относительно проста. В классе MyWindow объявляются два мас­сива чисел с плавающей точкой типа double для записи в них таблиц значений тригонометрических функций sin(x) и cos(x), которые затем будут выведены на экран в виде точечных графиков. Булевы пе­ременные sinIs и cosIs служат в качестве флагов, индицирующих наличие в этих массивах достоверных данных. В функциях отклика CmSin() и CmCos() вычисляются таблицы синуса и косинуса, устанавлива­ются флаги достоверности данных и вызовом функции Invalidate() инициируется посылка в приложение сообщения  WM_PAINT. В функции Paint() отклика на это сообщение на экран выводится сначала ось X (на расстоянии 150 пикселов от верхнего края окна), а затем графики тригонометрических функций с предварительной проверкой для каждой функции состояния флага достоверности. Для наглядности каж­дому графику назначается свой цвет.

Команда меню "О программе" служит, как обычно, для вывода на экран окна (в нашем примере -стандартного окна сообщения) с информацией о данном программном продукте. Для улучшения внеш­него вида окна сообщения (рис. 27.4) в замещенную нами функцию InitMainWindow() включен вызов функции EnableBWCC() класса TApplication, которая загружает библиотеку BWCC (Borland Windows Custom Controls).



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

250                                                                                                                                 Глава 27





sine[] и cosine[] можно было на этапе инициализации, например, в конструкторе MyWindow. В сле­дующем примере, где предусмот­рен вывод любой комбинации графиков по желанию, их вычис­ление в функции Paint() становит­ся более оправданным.

Действия с объектами меню

Базовые операции с меню, рассмотренные в предыдущем примере, не требовали использо­вания классов меню, определен­ных в библиотеке OWL - доста­точно было установить меню с помощью функции AssignMenu() и предусмотреть таблицу и функ­ции откликов на команды меню.

Если же в программу желательно включить элементы динамического управления меню, такие, как до­бавление, удаление или модификация команд меню (прикладного или системного), добавление к пунк­там меню маркеров, гашение недоступных пунктов, вывод меню в произвольное место окна и др., то следует обратиться к классам меню библиотеки OWL. В OWL описаны три класса меню - класс TMenu, в котором определено большинство функций, требуемых для работы с меню, класс TSystemMenu, назна­чение которого видно из названия, и класс TPopupMenu для работы со всплывающими меню. В примере 27-3 будет продемонстрировано использование классов TMenu и TPopupMenu.

Пример 27-3 является развитием предыдущего. В меню "Графики" добавлены команды для вывода на экран еще двух тригонометрических функций - sin(x)/x и cos(x)/x. Предусмотрена возможность ото­бражения на экране любого сочетания четырех доступных графиков, причем вывод графика сопровожда­ется появлением маркера перед соответствующей командой меню, а вторичный выбор этой команды га­сит как маркер, так и сам график. Щелчком правой клавиши мыши активизируется плавающее меню, в котором можно выбрать масштаб вывода графиков. На рис. 27.5 показан вывод приложения 27-3 в одном из вариантов.





//Приложение 27-3.   Действия с объектами меню

//Файл 27-3.rс

//Отличается от примера 27-2 увеличением числа команд (до 4) в пункте "Графики"

//Файл

27-3.h #define CM_ABOUT 101 #define CM_SIN 102 #define CM_COS 103

Обработка сообщений Windows                                                                                     251

#define CM_SINX 104

#define CM_COSX 105 #define CM_200 106 #define CM_100 107 #define CM_50 108

//Файл 27-3.cpp

#include <owl\framewin.h>

#include "27-3.h"

#include <math.h>

/*Класс приложения, производный от Tappllication*/

class MyApp:public TApplication{

public:

void  InitMainWindow();//Замещаем функцию InitMainWindow

};

/* Класс главного окна, производный от TframeWindow*/ class MyWindow:public TFrameWindow{

double sine[640],cosine[640],sinX[640],cosX[640] ;//Массивы данных для 4 графиков

bool sinIs,cosIs,sinXIs,cosXIs;//Индикаторы наличия данных для 4 графиков

int k;//Масштаб по оси у

TMenu* menu;//Объявляем указатель на объект основного меню

TPopupMenu

popupMenu;//Создаем объект плавающего меню

virtual void SetupWindow(); //Замещаем функцию TWindow.: SetupWindow ()

virtual void CleanupWindow() ; //Замещаем фукнцию TWindow::CleanupWindow ()

void Paint(TDC&,bool,TRect&);//Переопределяем функцию Paint

void CmAbout();    //Функции

void CmSin();      //откликов

void

CmCos();      //на команды

void CmSinX();     //основного меню

void CmCosX();     //Функции

void

Cm200();      //откликов

void Cm100();     //на команды

void Cm50();      //плавающего меню

void EvRButtonDown(UINT,TPoint&); public:

MyWindow(TWindow*parent,const char far* title);//Конструктор

DECLARE_RESPONSE_TABLE(MyWindow);//Объявляем таблицу откликов

}; DEFINE_RESPONSE_TABLE1(MyWindow,TFrameWindow)//Описываем таблицу откликов

EV_COMMAND(CM_ABOUT,CmAbout),     Макросы

EV_COMMAND(CM_SIN,CmSin),         для откликов

EV_COMMAND(CM_COS,CmCos),         на пункты

EV_COMMAND(CM_SINX,CmSinX),       основного



EV_COMMAND(CM_COSX,CmCosX),       меню

EV_COMMAND(CM_200,Cm200),         Макросы для откликов

EV_COMMAND(СМ_100,Cml00),         на пункты

EV_COMMAND(СМ_50,Сm50),              плавающего меню

EV_WM_RBUTTONDOWN,

END_RESPONSE_TABLE;//Завершаем таблицу откликов /*Конструктор класса MyWindow*/ MyWindow::MyWindow(TWindow*parent,const char far* title):TframeWindow

(parent,title){

AssignMenu("MainMenu");//Загрузка меню из файла приложения

sinIs=false; cosIs=false; sinXIs=false; cosXIs=false;//Начальные значения

//индикаторов

k=100; // Начальное значение масштаба

popupMenu.AppendMenu(MF_STRING,CM_200,"1.0=200 пикселов");//Формируем

popupMenu.AppendMenu(MF_STRING,CM_100,"1.0=100 пикселов");//плавающее

popupMenu.AppendMenu(MF_STRING,CM_50,"1.0=50 пикселов");//меню из 3 пунктов

}

/*3амещенная функция

SetupWindow*/ void MyWindow::SetupWindow(){

TWindow::SetupWindow();//Вызываем замещенную функцию SetupWindow

menu=new TMenu(HWindow);//Образуем объект класса TMenu

}

/*3амещенная функция

CleanupWindow()*/ void MyWindow::CleanupWindowО{

delete menu;//Удаляем созданный ранее объект меню

TWindow::CleanupWindow();//Вызываем исходную функцию

}

252 .                                              Глава27

/*Функции откликов на сообщения*/

void MyWindow::CmAbout(){

MessageBox("Демонстрация математических функций","О программе",

MB_ICONINFORMATION); } void MyWindow::CmSin(){

int state=menu->GetMenuState(CM_SIN,MF_BYCOMMAND);

if(state==MF_UNCHECKED)(// Если этот пункт меню не выбран

for(int i=0;i<640;i++)//Образовать

sine[i]=sin((double)i/20);//массив данных

sinIs=true;//Установить индикатор наличия данных

menu->CheckMenuItem(CM_SIN,MF_CHECKED);//Пометить команду меню

Invalidate();//Инициировать перерисовку окна

} else{//Если этот пункт меню уже выбран

for(int i=0;i<640;i++)//Очистить

sine[i]=0;//массив данных

sinIs=false;//Сбросить индикатор наличия данных



menu->CheckMenuItem(CM_SIN,MF_UNCHECKED);//Снять маркер

Invalidate();//Перерисовать окно (без этого графика)

} } /*Функции CmCos(), CmSinX() , CmCosX() имеют аналогичное содержимое*/

void MyWindow::Cm200(){//Устанавливаем 200 точек на 1 k=200; Invalidate();//Перерисовываем

void MyWindow::Cm100(){//Устанавливаем 100 точек на 1

k=100;

Invalidate();//Перерисовываем

] void MyWindow::Cm50(){//Устанавливаем 50 точек на 1

к=50;

Invalidate();//Перерисовываем

} void MyWindow::EvRButtonDown(UINT,TPoint& point){

TRect rect;

GetWindowRect(rect);//Получим текущие координаты главного окна

point+=rect.TopLeft();//Смещаем точку вывода меню

popupMenu.TrackPopupMenu(TPM_LEFTALIGN,point,0,HWindow);//Отобразим

//плавающее меню

}

/*3амещающая функция

InitMainWindow() */ void MyApp::InitMainWindow(){

MyWindow* myWin=new MyWindow(0,"Программа

27-3");

SetMainWindow(myWin);

EnableBWCC();//Разрешаем загрузку и использование BWCC.DLL

]                                                                               '

void MyWindow: :Paint (TDC&dc,bool,TRect&) { ...//Аналогично примеру 27-2, но выводятся 4 графика

} /*Главная функция приложения OwlMain*/

int OwlMain(int,char*[]){

return MyApp().Run();

}

Поскольку содержательные части двух последних примеров совпадают, ниже будут описаны только принципиальные отличия приложения 27-3 от предыдущего.

В классе MyWindow объявляется указатель menu на объект класса TMenu для добавления в меню маркеров, а также объект popupMenu класса TPopupMenu для образования плавающего меню, активизи­руемого щелчком правой клавиши мыши. Объект класса TMenu еще предстоит создать; это удобно вы­полнить в замещенной функции SetupWindow() класса TFrameWindow. Функции с именем SetupWindow входят во многие классы, описывающие различные окна (TWindow, TFrameWindow, TButton, TDialog и -др.); все они замещают исходную виртуальную функцию SetupWindow() класса TWindow и служат для выполнения необходимых для данного класса инициализирующих действий. Замещение функции Set-upWindow() в прикладном классе позволяет добавить к системным инициализирующим действиям соб­ственные. При этом, как правило, в замещающей функции необходимо сначала вызвать замещенную, и лишь затем выполнять прикладную инициализацию. Так и сделано в нашем примере (см. определение функции MyWindow::SetupWindow()).



Обработка сообщений Windows                                                                                     253

Выделив " своими руками" память под объект, мы должны ее сами же и освободить; для этого мы за­мещаем еще и функцию CleanupWindow() класса TWindow. В замещающей функции мы сначала удаляем созданные ранее объект с указателем menu, а затем вызываем исходную, замещенную функцию (см. оп­ределение функции MyWindow::CleanupWindow()).

В таблицу откликов, входящую в класс MyWindow, включены макросы EV_COMMAND как для всех ' пунктов основного меню (CM_ABOUT, CM_SIN и др.), так и для плавающего меню задания масштаба (СМ_200, СМ_100 и СМ_50). Соответствующие функции обработки сообщений (Сm200 и др.) объявле­ны выше. В программе также предусмотрена обработка сообщений от правой клавиши мыши (макрос EV_WM_RBUTTONDOWN и функция с предопределенным именем EvRButtonDown()).

Образование плавающего меню распадается на два этапа. В конструкторе класса MyWindow мы за­полняем объявленный ранее и пока пустой объект popupMenu класса TPopupMenu конкретными строка­ми команд. Это делается с помощью функции AppendMenu() класса TMenu (от которого класс TPopup­Menu является производным), в параметрах которой указывается тип каждого пункта меню (MF_STRING, текстовая строка), идентификатор и конкретный текст. Второй этап - активизация пла­вающего меню, т.е. вывод его на экран, осуществляется в функции EvRButtonDown() обработки сообще­ний от правой клавиши мыши. Здесь вызывается функция TrackPopupMenu() класса TPopupMenu, кото­рой передается флаг позиционирования меню TPF_LEFTALIGN (задается положение левого края меню), конкретные координаты (переменная point) и дескриптор окна-владельца, в качестве которого использу­ется открытый член класса TWindow HWindow, который получает свое значение (равное, между прочим, NULL) в процессе создания главного окна. Вместе с сообщением WM_RBUTTONDOWN в программу поступают текущие координаты курсора мыши относительно рабочей области окна приложения, однако функция TrackPopupMenu() выводит плавающее меню в координатах всего экрана. Для смещения меню к нашему окну в функции EvRButtonDown() объявляется прямоугольник rect класса TRect, вызовом функ­ции GetWindowRect() в него засылаются текущие координаты окна приложения, и переменная point должным образом корректируется.



Для сокращения текста приложения в нем не предусмотрена маркировка выбранного в плавающем меню пункта, что, разумеется, снижает наглядность использования этого меню (рис. 27.6).



Для маркировки команд меню "Графики" используется созданные ранее указатель menu на основное меню главного окна. Сама маркировка (а также ее снятие) осуществляется в функциях обработки сооб­щений от команд этого меню CmSin(), CmCos() и др. С помощью функции GetMenuState() программа по­лучает состояние пункта меню, по которому произведен щелчок левой клавишей мыши, и если этот пункт не помечен маркером, то вычисляется таблица значений соответствующей тригонометрической функции, устанавливается флаг индикации готовности данных, с помощью функции CheckMenuItem() данный пункт меню помечается маркером-галочкой и, наконец, вызовом функции Invalidate() иницииру­ется перерисовывание всего окна.

Если результат функции GetMenuState() показывает, что данный пункт меню уже отмечен, то массив значений данной тригонометрической функции затирается, той же функцией CheckMenuItem() маркер удаляется, после чего инициируется перерисовывание экрана, на котором теперь уже не будет удаленно­го графика.

Глава 28 Диалоговые окна

Исходный текст программы с простым модальным диалогом

На рис. 28.1. продемонстрировано функционирование программы 28-1.



//Приложение 28-1. Простейший модальный диалог.

//Файл 28-1.h #define About 100 #define CM_ABOUT 200 #define CM_EXIT 24310

//Файл 28-1.rc #include "28-1.h" MainMenu MENU{ POPUP "Файл")

MENUITEM "О программе...",CM_ABOUT MENUITEM SEPARATOR MENUITEM "Выход",CM_EXIT } }

MyIcon ICON "rabbit.ico" About DIALOG 7, 37, 222, 105

STYLE DS_MODALFRAME|WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU CLASS "bordlg" CAPTION "О программе" FONT 8, "MS Sans Serif"{

CONTROL "",IDOK,"BorBtn",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|

WS_TABSTOP,144,75,37,25

CONTROL "", -1,"BorShade",BSS_HDIP|BSS_LEFT|WS_CHILD|WS_VISIBLE,6,66,206,2 CTEXT "В данной программе создается простейший модальный диалог без" " каких-либо органов управления (не считая кнопки выхода). " "Органы управления диалогом (поля ввода текста, различные кнопки) "



Диалоговые окна                                                                                                                 255

"будут продемонстрированы в последующих примерах.",

-1,9,10,202,47, ss_center|not ws_group

ICON   "MyIcon",-1,30,77,16,16

}

//Файл 28-1.cpp

#include <owl\framewin.h>

#include <owl\dialog.h>

#include  "28-1.h"

/*Класс приложения,   производный oт Tapplication*/

class MyApp:public  TApplication{

public:

void InitMainWindow();//Замещаем функцию InitMainWindow

};

/*Класс главного окна, производный от

TframeWindow*/ class MyWindow:public TFrameWindow{

HICON hIcon;//Дескриптор нашего значка - данное-член класса MyWindow

public:

MyWindow(TWindow*parent,char far*title);

void GetWindowClass(WNDCLASS&);//Замещаем ради установки нашего значка

void CmAbout();//Функция отклика на пункт "О программе"

DECLARE_RESPONSE_TABLE(MyWindow);

); /*Таблица откликов класса MyWindow* /

DEFINE_RESPONSE_TABLE1(MyWindow,TFrameWindow)

EV_COMMAND(CM_ABOUT,CmAbout), END_RESPONSE_TABLE; /*Конструктор главного окна*/ MyWindow::MyWindow(TWindow*parent,char far*title):TFrameWindow(parent,title){

AssignMenu("MainMenu");

hIcon=GetApplication()->TModule::LoadIcon("MyIcon");

}

/* Функции-члены класса MyWindow*/ void MyWindow::CmAbout (){

TDialog* myDlg=new TDialog(this,About);

myDlg->Execute();

} void MyWindow::GetWindowClass(WNDCLASS&wc){

TWindow::GetWindowClass(wc);//Вызываем исходную функцию GetWindowClass

we.hIcon=hIcon;//Устанавливаем наш значок в структуре NNDCLASS

} void MyApp::InitMainWindow(void){

EnableBWCC();// Чтобы приложение работало вне среды Borland C++

SetMainWindow(new MyWindow(0,"Программа 28-1"));

}

/*Главная функция приложения OwlMain*/ int OwlMain(int,char*[]){

return MyApp() . Run () ;

}

Так же, как и при процедурном программировании с помощью вызовов функций API Windows, включение диалоговых окон в OWL-приложение требует, прежде всего, их описания в файле ресурсов. Диалог для данного примера готовился не в текстовом редакторе, а в программе Resource Workshop, что позволило придать диалогу "стиль Borland", не обременяя себя поиском ключевых слов ("BorBtn", "BorShade", BSS_HDIP и др.), описывающих различные оформительские элементы диалогового окна. Применение к диалогу стиля Borland определяется строкой



CLASS   "bordlg"

в начале описания диалога, а также использованием упомянутых выше  классов элементов диалогового окна (например, класс кнопки BorBtn или класс разделительной полосы BorShade) и констант, опреде­ляющих стиль этих элементов (например, BSS_HDIP).

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

256                                                                                                                                 Глава 28





Свойства и возможности диалоговых окон описаны в классе OWL TDialog, производном от TWindow. Для придания диалогу, создаваемому в приложении, необходимых черт, в общем случае в приложении создается класс, производный от TDialog (MyDia-log на рис. 28.2). Этот класс можно дополнить любыми членами, требуемыми для функционирования диалога, прежде всего, функциями отклика на сообщения от органов управления диало­гом. Однако в настоящем примере рассматривается простейший вариант создания диалога, в котором, кроме кнопки ОК, заботу о которой берет на себя OWL, нет никаких органов управления. В этом случае нет необходимости вводить производный класс, дос­таточно создать объект исходного класса TDialog.

В примере 28-1 диалоговое окно, содержащее некоторую информацию о программе, активизируется выбором пункта "О программе" главного меню. Как было описано в предыдущей главе, для включения в приложение главного меню необходимо выполнить следующие операции:



•   включить в файл ресурсов описание меню

•   назначить это меню приложению в конструкторе главного окна функцией AssignMenu()

•   определить в классе главного окна таблицу откликов на сообщения от меню с указанием имен

функций откликов

•   определить в программе сами функции отклика для обслуживанию пунктов меню

В нашем случае в таблице откликов указано, что сообщения WM_COMMAND от пункта меню CM_ABOUT будут обрабатываться функцией класса MyWindow CmAbout().

В функции CmAbout() создается объект класса TDialog с указателем myDlg и для этого объекта вы­зывается функция Execute(), которая создает и обслуживает модальное диалоговое окно (немодальный диалог активизируется с помощью функции TDialog: :Create()). Как известно, для модального диалога ха­рактерно блокирование (до его закрытия) органов управления остальных окон приложения. Все время, пока мы работаем с органами управления модального диалога, функция Execute() является активной. За­крытие модального диалога приводит к завершению этой функции и передаче управления в приложение.

Указатель myDlg на объект класса TDialog используется в программе лишь для вызова для этого объекта функции Execute(), и вводить для него специальное обозначение нет необходимости. Исключив явное именование этого указателя, можно упростить текст функции CmAbout():

void MyWindow::CmAbout(){

new TDialog(this,About)->Execute(); }

Поскольку в рассматриваемом приложении для украшения диалогового окна используется собствен­ный значок, есть смысл назначить его значком всего приложения. Это процедура, подробно описанная в гл. 26, включает две операции: загрузку значка-ресурса из выполнимого модуля приложения и включе­ние его в структуру WNDCLASS. Первая операция выполняется в конструкторе главного окна с помо­щью функции LoadIcon():

hIcon=GetApplication()->TModule::LoadIcon("MyIcon");

Функция LoadIcon() принадлежит классу TModule, и для вызова ее из функции класса MyWindow необходимо указать объект класса TModule или производного от него, для которого она вызывается. В нашем случае речь идет об экземпляре приложения, указатель на который можно получить с помощью функции TWindow::GetApplication(). Как уже отмечалось, если в функции некоторого класса вызывается функция другого класса, необходимо указать, какому классу она принадлежит, что и сделано с помощью конструкции TModule: :LoadIcon().



Помещение в структуру WNDCLASS дескриптора загруженного ранее значка осуществляется в за­мещенной нами функции класса TWindow GetWindowClass() (подробности см. в гл. 26):

void MyWindow::GetWindowClass(WNDCLASS&wc){

TWindow::GetWindowClass(wc);//Вызываем исходную функцию GetWindowClass

we.hIcon=hIcon;//Устанавливаем наш значок в структуре NNDCLASS

}

Дескриптор hIcon введен в класс MyWindow в качестве закрытого члена. Такое построение програм­мы повышает ее наглядность, но несколько увеличивает объем. Поскольку, как и в рассмотренном выше случае с указателем на объект диалогового окна, дескриптор hIcon используется в программе лишь в од­ном месте, можно исключить его явное объявление среди данных-членов класса MyWindow и получить в неявном виде прямо в функции GetWindowClass(), совместив в одном предложении и загрузку ресурса, и занесение его дескриптора в структуру WNDCLASS:

we.hIcon=GetApplication()->TModule::LoadIcon("MyIcon") ;

В этом варианте отпадает необходимость загружать ресурс-значок в конструкторе главного окна.

Диалоговые окна_________________________________________________ 257

Модальный диалог с органами управления и функциями отклика

В примере 28-2 рассматривается простая (и крайне несовершенная) программа, создающая набор "карточек" с данными о сотрудниках некоторого предприятия. Карточки заполняются с помощью мо­дального диалогового окна, в котором предусмотрен ряд органов управления: несколько полей для ввода текста, а также альтернативные и нажимаемые кнопки (рис. 28.3). Созданный набор карточек (базу дан­ных) можно записать на диск в файл с произвольным именем и заданным расширением dbf; можно также прочитать и вывести на экран содержимое файла с базой данных.



При обсуждении текста программы мы коснемся следующих вопросов:

•   создание модального диалога с разнообразными органами управления

•   обработка сообщений от органов управления диалогового окна

•   использование буфера обмена для обмена данными между главным и диалоговым окнами



•   использование стандартных диалогов Windows для чтения и записи файлов

В последующих разделах этой главы в программу 28-2 будут внесены усовершенствования, которые позволят обсудить некоторые дополнительные аспекты организации диалоговых окон.

//Приложение 28-2.   Модальные диалоги

//Файл 28-2.h #define  INPUTDLG  100 #define CM_INPUT  201 #define CM_OPEN  202 #define CM_SAVE  203 #define CM_EXIT  24310 #define  IDC_NAME   101 #define  IDC_JOB  102 #define  IDC_YEAR  103 #define  IDC_M  104 #define  IDC_F  105 #define  IDC_ADD  106 #define MAXENTRIES   20 /*Структура  буфера обмена.*/ typedef  struct{

char nameEdit[20];

char  jobEdit[20];

char yearEdit[5];

bool  mEdit;

bool   fEdit;

}TSB;

//Файл 28-2.rc #include   "28-2.h" MainMenu MENU{ POPUP  "Файл"{

MENUITEM   "Ввод  данных...",CM_INPUT MENUITEM   "Открыть...",CM_OPEN

258                                               Глава 28

MENUITEM "Сохранить...",CM_SAVE MENUITEM SEPARATOR MENUITEM "Выход",CM_EXIT }

}

INPUTDLG DIALOG 7,37,222,130

STYLE DS_MODALFRAME|WS_POPUP| WS_VISIBLE|WS_CAPTION|WS_SYSMENU CLASS "bordlg"

CAPTION "Форма для заполнения карточек" FONT 8, "MS Sans Serif"{

EDITTEXT IDC_NAME,69,12,137,12

RTEXT "Фамилия",-1,11,15, 53, 9

EDITTEXT IDC_JOB,69,35,137,12   Ч

RTEXT "Должность",-1,10,38, 54, 9

EDITTEXT IDC_YEAR,69,58,37,12

RTEXT "Год рождения",-1,11, 61, 54, 9

CONTROL "M",IDC_M,"BUTTON",BS_AUTORADIOBUTTON,123, 58, 20,12

CONTROL "Ж",IDC_F,"BUTTON",BS_AUTORADIOBUTTON, 162, 58,20,12

CONTROL "Добавить",IDC_ADD,"BorBtn",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE,40, 93, 37, 25

CONTROL "Закрыть",IDOK,"BorBtn",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE,145,93,37, 25

CONTROL '",-1,"BorShade",BSS_HDIP|BSS_LEFT|WS_CHILD|WS_VISIBLE,6,80,206,2

}

//Файл 28-2.cpp

#include <owl\framewin.h>



#include <owl\dialog.h>

#include <owl\edit.h>

#include <owl\checkbox.h>

#include <owl\opensave.h>

#include "28-2.h"

#include <stdio.h>

/*Глобальная переменная*/

TSB tsbArray [MAXENTRIES];//Массив карточек

/*Класс приложения, производный от Tapplication*/

class MyApp:public TApplication{

public:

void InitMainWindowt);//Замещаем функцию InitMainWindow

/* Класс главного окна, производный от TframeWindow*/

class MyWindow:public TFrameWindow{

public:

MyWindow(TWindow*parent,char far*title);

void Paint(TDC&,bool,TRect&);//Замещаем функцию Paint ()

void CmInput();//Функция отклика на пункт "Ввод данных"

void CmOpen();//Функция отклика на пункт "Открыть"

void CmSave();//Функция отклика на пункт "Сохранить"

DECLARE_RESPONSE_TABLE(MyWindow);

};

/*Класс окна диалога, производный от Tdialog*/ class MyDialog:public TDialog{

int index;//Номер текущей карточки

TSB tsb;//Структурная переменная для буфера обмена

public:

MyDialog(TWindow*,TResId);

void CmAdd() ;

DECLARE_RESPONSE_TABLE(MyDialog) ;

};

/*Таблица откликов класса MyWindow*/ DEFINE_RESPONSE_TABLE1(MyWindow,TFrameWindow)

EV_COMMAND(CM_INPUT,CmInput),

EV_COMMAND(CM_OPEN,CmOpen),

EV_COMMAND(CM_SAVE,CmSave), END_RESPONSE_TABLE; /*Конструктор главного окна*/ MyWindow::MyWindow(TWindow*parent,char far*title):TFrameWindow(parent,title) {

AssignMenu("MainMenu");

}

/*Функция откликов класса MyWindow*/ void MyWindow::CmInput(){

memset(&tsbArray,0,sizeof(TSB)*MAXENTRIES);//Очистим массив карточек

new MyDialog(this,INPUTDLG)->Execute();//Выполняем диалог

Invalidate();//Активизируем сообщение WM_PAINT для перерисовки главного окна

Диалоговые окна                                         259

}

void MyWindow::CmOpen(){

TOpenSaveDialog::TData fileData(OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|

OFN_HIDEREADONLY,"Базы данных (*.dbf)| *.dbf|"); int result= new TFileOpenDialog(this,fileData)->Execute(); if(result==IDCANCEL)return;



char* ptr=strchr(fileData.FileName,'\0');// Получим указатель на конец имени if(result!=IDOK|jstrcmpi(ptr-3,"DBF")){//Если файл не открылся или не .DBF MessageBox("Неверное имя файла","Info",МВ_ОК); return; }

FILE* fp=fopen(fileData.FileName,"rb"); if(!fp)return;

fread(tsbArray,sizeof(TSB),MAXENTRIES,fp); fclose(fp); Invalidate(); } void MyWindow::CmSave(){

TOpenSaveDialog::TData fileData(OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

"Базы данных (*.dbf)|*.dbf|);

int result= new TFileSaveDialog(this,fileData)->Execute(); if(result==IDCANCEL)return;

char* ptr=strchr(fileData.FileName,'\0');//Получим указатель на конец имени if(result!=IDOK||strcmpi(ptr-3,"DBF")){//Если файл не открылся или не .DBF MessageBox("Неверное имя файла","Info",МВ_ОК); return; }

FILE* fp=fopen(fileData.FileName,"wb"); if(!fp)return;

fwrite(tsbArray,sizeof(TSB),MAXENTRIES,fp); fclose(fp); }

/*Функция Paint()*/

void MyWindow::Paint(TDC&dc,bool,TRect&){ char s[80];

for(int i=0;i<MAXENTRIES;i++){ strcpy(s,tsbArray[i].nameEdit); strcat(s,"  ");

strcat(s,tsbArray[i].jobEdit); strcat(s,"  ");

strcat(s,tsbArray[i].yearEdit); strcat(s,"  "); if(tsbArray[i].mEdit==true)

strcat(s,"м"); else if(tsbArray[i].fEdit==true)

strcat(s,"ж");

dc.TextOut(5,i*20,s); } }

/*Конструктор класса MyDialog*/

MyDialog::MyDialog(TWindow*parent,TResId resId):TDialog(parent,resId){ new TEdit(this,IDC_NAME,sizeof(tsb.nameEdit)); new TEdit(this,IDC_JOB,sizeof(tsb.jobEdit)); new TEdit(this,IDC_YEAR,sizeof(tsb.yearEdit)) ; new TCheckBox(this,IDC_M); new TCheckBox(this,IDC_F);

TransferBuffer=&tsb;//Назначили tsb буфером обмена memset(&tsb,0,sizeof(TSB)); index=0;//Начинаем с карточки #0 }

/*Таблица откликов класса MyDialog*/ DEFINE_RESPONSE_TABLE1(MyDialog,TDialOg)

EV_COMMAND(IDC_ADD,CmAdd), END_RESPONSE_TABLE;

/*Единственная функция отклика класса MyDialog*/ void MyDialog::CmAdd(){ TransferData(tdGetData);



memmove(&tsbArray[index++] ,&tsb, sizeof (TSB)) ; memset(&tsb,0,sizeof(TSB)); TransferData(tdSetData); GetWindowPtr(GetParent())->Invalidate();}

260                                                    Глава 28

/*Замещающая функция InitMainWindow ()*/

void MyApp::InitMainWindow(void){

EnableBWCC();

SetMainWindow(new MyWindow(0,"Программа 28-2"));

}

/*Главная функция приложения OwlMain*/ int OwlMain(int,char*[]){

return MyApp () . Run(); }

Заголовочный файл 28-2.h содержит определения констант-идентификаторов пунктов меню и орга­ нов управления диалогом, константу MAXENTRIES, задающую максимальный объем создаваемой базы данных, а также описание структуры TSB, которая будет использована для обмена данными с диалого­вым окном. Следует отметить, что произвольное имя TSB, определенное с помощью оператора typedef, является типом структуры, а не именем структурной переменной. Структурные переменные типа TSB (целых две) будут объявлены в тексте программы. Состав членов структуры TSB соответствует составу данных, вводимых нами в поля диалогового окна: фамилия сотрудника nameEdit, его должность jobEdit, год рождения в символьной форме yearEdit, а также булевы переменные mEdit и fEdit, говорящие о поле сотрудника. Первая переменная устанавливается в 1 для сотрудников-мужчин, вторая - для женщин.





В файле ресурсов 28-2.гс определена форма главного меню приложения (рис. 28.4), а также содержимое диалогового окна. Диалоговое окно, как и в предыдущем примере, получено с по­мощью программы Resource Workshop и выполнено в стиле Bor­land. Окна ввода текста представляют собой управляющие эле­менты класса EDITTEXT, альтернативные кнопки относятся к классу BUTTON со стилем BS_AUTORADIOBUTTON, для на­жимаемых кнопок использован класс Borland BorBtn, а для пояс­няющих надписей - класс RTEXT (чтобы выровнять надписи по правому краю). Для разнообразия в диалог введена разделяющая полоса в стиле Borland класса BorShade.

Переходя к рассмотрению текста программы, следует сделать общее замечание относительно порядка описания ее составляю­щих. Вообще типичная объектно-ориентированная программа со-



стоит, грубо говоря, из двух частей: объявлении прикладных классов, входящих в программу, и описании функций-членов этих классов, которые часто называют реализацией класса. Объявления классов, где по­сле ключевого слова class перечисляются входящие в класс данные-члены с указанием их типов и функ­ции-члены с указанием их сигнатур, располагаются в начале программы или, еще чаще, выносятся в за­головочный файл. Сама же программа, т.е. файл с расширением .срр, включает тексты функций-членов использованных в программе классов, а так же, разумеется, текст главной функции OwlMain(). Такой по­рядок диктуется правилами языка, требующими сначала объявить объект (например, прототип функ­ции), и лишь затем его описывать (тело функции). Однако при словесном описании деталей программы часто требуется, рассказав, например, о составе того или иного класса, тут же привести хотя бы первона­чальные сведения о его реализации. В результате при изложении назначения и взаимодействия отдель­ных элементов программы приходится постоянно перепрыгивать от одного места программы к другому, что затрудняет поиск в тексте программе описываемых участков, но, видимо, неизбежно.

Вернемся, однако, к тексту примера. Программа начинается с описания массива карточек типа TSB, в которых будет храниться введенная с клавиатуры база данных. Для простоты этот массив имеет фикси­рованную длину. Объявление массива карточек глобальным позволяет обращаться к нему из разных классов программы.

Для обработки сообщений от первых трех пунктов главного меню в таблицу откликов класса главно­го окна MyWindow включены три предложения EV_COMMAND, а в класс главного окна - объявления трех функций отклика CmInput(), CmOpen() и CmSave. Пункт "Выход", которому назначен стандартный идентификатор СМ_ЕХIТ, обрабатывается системой, и для него функция отклика не нужна. Действия, выполняемые функциями отклика, а также назначение функции Paint() главного окна будут описаны позже.

Поскольку класс, описывающий поведение диалогового окна, должен в данном случае иметь специ­фическую таблицу откликов, отвечающую составу органов управления диалогом, он должен быть не классом OWL, а прикладным классом, производным от класса TDialog. В состав членов этого класса, на­званного в программе MyDialog, включены вспомогательная переменная index, структурная переменная tsb типа TSB, которая будет использована для обмена данными с диалогом, конструктор, таблица откли­ков и функция обработки сообщений от кнопки "Добавить" CmAdd(). Объявление таблицы откликов для класса диалога выполняется так же, как и для класса главного окна, за исключением того, что в качестве параметра макроса DECLARE_RESPONSE_TABLE указывается имя соответствующего класса, в данном случае имя MyDialog.



Сама таблица откликов содержит единственный макрос EV_COMMAND (с указанием функции от­клика CmAdd()), так как из всех составляющих диалогового окна мы будем напрямую обрабатывать

Диалоговые окна                                                                                                                 261

только сообщения от кнопки "Добавить". Сообщения от остальных элементов диалога (полей ввода и альтернативных кнопок) будут обрабатываться встроенными средствами OWL, описанными ниже.

При выборе пункта "Ввод данных" главного меню вызывается (через таблицу откликов класса Му-Window) функция CmInput(). В ней обнуляется массив карточек, после чего создается объект диалогово­го окна и для него вызывается функция Execute(). Пользователю предоставляется возможность заполнить карточки базы данных требуемым содержимым. После завершения работы с диалогом (нажатием кнопки "ОК") функция Execute() завершается, управление возвращается в функцию CmInput(), и с помощью вы­зова функции Invalidate() инициируется перерисовка главного окна, в которое выводится информация из всех заполненных карточек базы данных.

Рассмотрим теперь вопрос о взаимодействии с управляющими элементами диалога. В нашем приме­ре требуется извлекать данные из полей ввода диалогового окна (а также направлять в поля ввода дан­ные, если мы хотим заполнять эти поля не с клавиатуры, а программно) и получать информацию о нажа­тии альтернативных кнопок "М" и "Ж". Для реализации встроенных средств коммуникации с управляю­щими элементами диалога необходимо прежде всего определить специальную структуру - буфер обме­на, элементы которого соответствуют данным, поступающим от управляющих элементов. В нашем при­мере эта структура получила наименование TSB (от Transfer Buffer); в нее входят три символьные строки для приема данных из полей ввода и две булевы переменные для получения информации о состоянии альтернативных кнопок. Длина символьных строк, определенных в структуре TSB, задает максимальное число символов, которое можно будет ввести в соответствующую строку. При определении длины строк не следует забывать о завершающем символьную строку нуле; именно из-за него строка yearEdit. для приема года имеет длину не 4, а 5 байт.



Определив буфер обмена и создав в программе соответствующую переменную ( у нас она имеет имя tsb и помещена в класс MyDialog), необходимо для каждого управляющего элемента диалога создать управляющий объект соответствующего класса OWL. Все управляющие классы выводятся из базового класса TControl, который сам является одним из производных класса TWindow (см. рис. 28.5, где показа­на часть структуры классов управляющих элементов, главным образом тех, которые используются в рас­сматриваемом или последующих примерах).



Для поля ввода (класс органа управления EDITTEXT, см. файл ресурсов 28-2.гс) создается объект класса TEdit, для альтернативной кнопки (класс органа управления BUTTON, стиль BS_AUTORADIOBUTTON) - объект класса TCheckBox, для списка - объект класса TListBox, для ком­бинированного окна - объект класса TComboBox и т.д. Создание управляющих объектов удобнее всего выполнить в конструкторе диалогового класса, причем порядок их создания должен строго соответство­вать порядку объявления соответствующих элементов в структуре буфера обмена. Так, для буфера обме­на нашего примера

typedef  struct{

char nameEdit[20]; char  jobEdit[20] ; char yearEdit[5]; bool  mEdit; bool   fEdit; }TSB;

требуется такая последовательность вызова конструкторов управляющих классов:

new TEdit(this,IDC_NAME,sizeof(tsb.nameEdit)); new TEdit(this,IDC_JOB,sizeof(tsb.jobEdit)); new TEdit(this,IDC_YEAR,sizeof(tsb.yearEdit)); new TCheckBox(this,IDC_M); new TCheckBox(this,IDC_F);

262                                                                                                                                  Глава 28

Как видно из приведенного фрагмента, конструкторы управляющих объектов могут иметь разный формат, но все они требуют в качестве первого параметра указатель на класс диалогового окна, в кото­ром эти объекты расположены; поскольку мы создаем объекты в конструкторе этого самого класса, то в качестве указателя на класс удобно использовать указатель this. Вторым параметром выступает иденти­фикатор соответствующего органа управления. Третий параметр, где он есть - это длина передаваемой через буфер строки текста. Заметим, что имена создаваемых объектов мы в программе использовать не будем, поэтому здесь, как и в других аналогичных случаях, вместо полной формы предложения с опера­тором new, например,



TCheckBox*  checkBox1=new TCheckBox(this,IDC_F);

вполне допустима краткая форма без указания имени указателя на создаваемый объект.

Последней операцией по организации взаимодействия с диалогом является назначение нашей струк­турной переменной tsb буфером обмена. Это делается с помощью предложения

TransferBuffer=&tsb;//Назначили tsb буфером обмена

где TransferBuffer - это защищенный член класса TWindow, который рассматривается OWL, как указа­тель на буфер обмена. Присвоив ему адрес нашей структурной переменной tsb, мы тем самым заставили OWL использовать tsb в качестве буфера обмена.

Если для диалогового окна определен буфер обмена, то при создании диалога его органам управле­ния автоматически будет передано исходное содержимое этого буфера. Включенный в конструктор диа­лога вызов функции memset() очищает буфер обмена, в результате чего при появлении на экране диалога его поля будут пустыми. С таким же успехом можно было назначить полям определенные значения, на­пример, для базы данных предприятия с преобладанием мужчин включить в конструктор диалога (после очистки буфера) предложение

tsb.mEdit=true;

а для предприятия с преобладанием женщин - предложение

tsb.mEdit=true;

Обмен данными с диалогом реализуется вызовом функции класса TWindow TransferData(), которая в качестве параметра требует указания одного из двух флагов - tdGetData в случае передачи данных из диалога в буфер обмена и tdSetData при передаче данных из буфера в диалог. В программе обмен данны­ми осуществляется в функции CmAdd() отклика на нажатие кнопки "Добавить". Прежде всего вызовом функции TransferData() данные из полей диалога передаются в буфер обмена tsb, затем функцией mem-move() данные копируются из буфера обмена в массив карточек с наращиванием индекса заполняемого элемента в этом массиве и, наконец, после очистки буфера его содержимое переносится в диалог, очищая поля диалога и облегчая тем самым ввод следующих данных. Если на этом функцию CmAdd() завер­шить, то хотя вводимые с клавиатуры данные будут накапливаться в массиве карточек tsbArray, на экра­не их видно не будет. Для того, чтобы каждая вводимая карточка сразу же отображалась в главном окне, надо вызвать функцию Invalidate() для этого окна. Однако мы, создавая объект главного окна в функции MyApp::InitMainWindow(), не позаботились оставить для будущих ссылок ни имя этого объекта, ни ука­затель на него, что лишило нас возможности вызывать для этого объекта какие-либо функции-члены. Придется, как мы это уже не раз делали, получить значение указателя динамически, для чего в классе TWindow предусмотрен соответствующий набор функций. В последнем предложении функции CmAdd()



GetWindowPtr(GetParent())->Invalidate();

функция GetParent() возвращает значение дескриптора (типа HWND) родительского, т.е. главного окна, а функция GetWindowPtr() с этим дескриптором в качестве аргумента возвращает указатель на главное ок­но, который и используется для вызова функции Invalidate().

Стандартные диалоги Windows

Рассмотрим теперь те части примера 28-2, где вызываются стандартные диалоги Windows для откры­тия и сохранения файла. Как уже отмечалось в гл. 12, в Windows имеется группа стандартных диалогов, служащих для открытия и сохранения файлов, задания характеристик печати, поиска и замены слов и др. Все они поддерживаются соответствующими классами OWL, производными от базового для всех стан­дартных диалогов класса TCommonDialog, который, в свою очередь, выводится из класса TDialog (рис. 28.6).

Как видно из рис. 28.6, диалоги открытия и сохранения файлов реализуются с помощью классов TOpenSaveDialog, TFileOpenDialog и TFileSaveDialog. Рассмотрим их использование на примере функ­ции примера 28-2 CmOpen(), вызываемой при щелчке мышью по пункту меню "Открыть".



Диалоговые окна                                                                                                                 263



Перед тем, как вызывать стандартный диалог Windows, необходимо создать и настроить структуру данных типа TData, которая будет определять свойства этого диалога (у нас эта структурная переменная получила имя fileData). В каждом стандартном диалоге объявлен свой встроенный класс TData, в кото­ром описаны характерные для конкретного диалога данные (в частности, для диалогов открытия или со­хранения файлов к характерным данным относится спецификация файла, а для диалога настройки прин­тера - число печатаемых копий). Вызывая конструктор класса TData для образования переменной fileData

TOpenSaveDialog::TData fileData(OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|

OFN_HIDEREADONLY,"Базы данных (*.dbf)|*.dbf|); .

мы указываем среди его параметров те характерные значения, которые мы хотим поместить в fileData. В нашем случае задаются два параметра: комбинация флагов (проверка существования вводимых пользо­вателем каталогов и имен файлов, а также скрытие контрольной рамки "Только чтение") и тип искомых файлов.



Вызывая далее конструктор класса диалога (в одном предложении с вызовом для этого диалога функции-члена Execute(), приводящим к появлению диалогового окна на экране)

int result= new TFileOpenDialog(this,fileData)->Execute();

мы указываем среди его параметров имя нашей структурной переменной fileData. Среди защищенных данных-членов класса диалога имеется ссылочная переменная Data типа TData, в которую при выполне­нии конструктора записывается адрес созданной в программе структуры fileData, что делает ее доступ­ной программам OWL, реализующим работу диалога. После выбора пользователем в процессе работы с диалогом требуемого файла и закрытия диалога, полная спецификация выбранного файла поступает в элемент FileName переменной fileData, откуда ее можно извлечь и использовать для открытия файла и чтения из него данных.

Целочисленное значение, возвращаемое функцией Execute(), говорит о том, каким образом был за­крыт диалог. При нажатии на кнопку ОК возвращается значение IDOK, при выборе кнопки "Отмена" -значение IDCANCEL. Анализ возвращаемого значения дает возможность завершить функцию CmOpen(), если пользователь завершил диалог, не выбрав никакого файла. Если же файл выбран и диалог завершен с кодом возврата ОК, то выполняется проверка правильности расширения имени файла. Для этого сим­вольный указатель ptr устанавливается на завершающий нуль спецификации файла в переменной fileData, а затем последние три символа спецификации сравниваются с заданным расширением DBF (вы­бранным, разумеется, для наших файлов совершенно произвольно). Если расширение правильное, то файл открывается и все его содержимое читается в массивную переменную tsbArray, состоящую из запи­сей типа TSB.

После закрытия файла вызовом функции Invalidate() инициируется сообщение WM_PAINT для глав­ного окна приложения (поскольку функция CmOpen() принадлежит классу главного окна My Window), в результате чего на экран выводится содержимое выбранного файла с базой данных.



Функция CmSave(), вызываемая при выборе пункта меню "Сохранить" и активизирующая стандарт­ный диалог Windows "Сохранение", отличается от функции CmOpen() только режимом открытия файла (для записи, а не для чтения) и заменой функции fread() на fwrite().

Замещенная функция Paint() просматривает в цикле записи прочитанного файла, собирает данные каждой записи в символьную строку и функцией TextOut() выводит эти строки на экран друг под другом (рис. 28.7). При этом первое данное из каждой записи копируется в строку-приемник функцией strcpy(), очищая (в логическом плане) строку s от предыдущего содержимого, а последующие данные наращива­ют строку с помощью функции strcat(), выполняющей операцию конкатенации. Ради простоты в про­грамме не предусмотрено ни какого-либо форматирования выводимых строк, ни даже анализа числа ре­ально содержащихся в массиве записей.



264                                                                                                                                 Глава 28



Проверка правильности вводимых в диалог данных

Конструируя диалоговое окно с полями ввода текстовой или числовой информации, желательно иметь механизм проверки вводимых данных, чтобы, например исключить случайный ввод вместо фами­лии строки "1936" или вместо года рождения строки "Иван". Библиотека OWL предоставляет несколько классов, специально предназначенных для проверки правильности вводимых данных. Все они имеют в качестве общего прародителя класс TValidator и позволяют проанализировать правильность вводимых данных по различным параметрам - на отсутствие запрещенных символов, соответствие заданному шаб­лону (например, время обычно вводится в формате чч:мм:сс) или попадание вводимого числа в заданный диапазон. Рассмотрим модификацию примера 28-2, позволяющую контролировать правильность вводи­мых в диалог данных. Контролю подвергаются поля фамилии, должности и года рождения.

Принципиальные отличия модифицированной программы заключаются лишь в изменении текста конструктора диалогового окна:



MyDialog::MyDialog(TWindow*parent,TResId resId):TDialog(parent,resId){ TEdit*  edit;//Указатель на класс TEdit

TValidator*  valid;//Указатель на  базовый класс TValidator

edit=new TEdit(this,IDC_NAME,sizeof(tsb.nameEdit));//Создаем управляющий объект valid=new TFilterValidator("А-Яа-яА-za-z.");//Создаем объект фильтра  контроля -edit->SetValidator(valid);// Назначаем этот контроль для управляющему объекта edit=new TEdit(this,IDC_JOB,sizeof(tsb.jobEdit));//Аналогично для второго объекта valid=new TFilterValidator("А-Яа-я");//Фильтр для русских букв edit->SetValidator(valid);

edit=new TEdit(this,IDC_YEAR,sizeof(tsb.yearEdit));//Аналогично для 3-го объекта valid=new TRangeValidator(1900,1999);//Фильтр на  принадлежность диапазону edit->SetValidator(valid);

new TCheckBox(this,IDC_M);//Для кнопок контролировать нечего new TCheckBox(this,IDC_F);

TransferBuffer=&tsb;//Назначили  ts  буфером обмена memset(&tsb,0,sizeof(TSB)); index=0;//Начинаем с карточки #0 }

Прежде всего объявляются указатель на класс TEdit, с помощью которого будут создаваться управ­ляющие объекты для полей ввода и указатель на базовый по отношению ко всем классам контроля класс TValidator для создания объектов-фильтров производных классов (вспомним, что с помощью указателя на базовый класс можно создавать объекты производных классов). Далее для каждого поля ввода выпол­няется однотипная процедура:

•   создается управляющий объект

•   создается объект - контролирующий фильтр требуемого класса (в примере используются фильтры

классов TFilterValidator для контроля символов и TRangeValidator для контроля числового диапазона)

•   для управляющего объекта вызывается функция SetValidator(), в качестве аргумента которой вы­

ступает объект-фильтр

В принципе описанными изменениями можно было бы ограничиться. Программа позволит вводить в поля для фамилии или должности только прописные или строчные буквы (при этом в фамилии допусти­мы как русские, так латинские буквы, а в должности - только русские), а в поле года - только числа от 1900 до 1999. При вводе в поле года неправильных данных после нажатия кнопок "Добавить" или "ОК" на экран будет выведено предупреждающее окно (без текста).



Такой простейший вариант контроля неудобен. Во-первых, довольно странно выглядит пустое пре­дупреждающее окно; во-вторых, с помощью кнопки "ОК" нельзя будет закрыть диалог при пустом поле года, так как при нажатии этой кнопки выполняется проверка правильности содержимого окна года, а пустое окно считается неправильным.

Диалоговые окна                                                                                                                 265

Для заполнения предупреждающего окна разумной надписью в файл ресурсов надо ввести ресурс STRINGTABLE (текстовые строки) с требуемым текстом в указанном ниже формате:

STRINGTABLE{

IDS_VALNOTINRANGE   "Введенное  значение  не  в диапазоне  от  %ld до  %ld." }

Этот ресурс используется той функцией класса контроля, которая выводит на экран предупреждаю­щее окно. Для класса TRangeValidator строка с содержимым предупреждающего окна должна иметь идентификатор IDS_VALNOTINRANGE, значение которого (32522) описано в файле validate.rh. Чтобы не включать весь этот файл в программу, достаточно в файле заголовков повторить строку из него:





#define IDS_VALNOTINRANGE 32522

В результате при вводе неправильного года на эк­ран будет выводиться окно, показанное на рис. 28.8.

Для устранения второго недостатка можно вместо кнопки "ОК" использовать кнопку "Отмена", что дос­тигается путем замены идентификатора ШОК на иден­тификатор IDCANCEL (в этом случае автоматически изменится и цветной символ на кнопке). Нажатие на кнопку "Отмена" не вызывает проверку правильности содержимого полей диалога, и закрыть этой кнопкой диалог можно будет и при отсутствии в поле года како­го-либо содержимого.

Немодальные диалоги

Процедура создания и обслуживания немодального диалога проиллюстрирована в примере 28-4; вы­вод этой программы показан на рис. 28.9.



//Приложение  28-4.   Немодальный диалог

//Файл 28-4.h

#define Dlg                  1

#define CM_VIEW         201



#define  CM_EXIT          24310

#define  IDC_POINTS     101

#define   IDC_CURVE              102

#define  IDC_HISTO               103

#define  FILESIZE

//Файл  28-4.rc #include   "28-4.h" MainMenu MENU{ POPUP   "Файл"{

MENUITEM   "Вид...",CM_VIEW

MENUITEM SEPARATOR

MENUITEM "Выход",CM_EXIT

} }

Dlg DIALOG 100,15,116,66 STYLE WS_CHILD|WS_VISIBLE|WS_CAPTION

266                                                                                                                                 Глава 28

CLASS   "BorDlg_Gray"

CAPTION "Вид графика"

FONT 8, "MS Sans. Serif"{

CONTROL "Точки",IDC_POINTS,"BUTTON",BS_AUTORADIOBUTTON|WS_GROUP, 8,9,60,12

CONTROL "Огибающая",IDC_CURVE,"BUTTON",BS_AUTORADIOBUTTON,8,27,60,12

CONTROL "Гистограмма",IDC_HISTO,"BUTTON",BS_AUTORADIOBUTTON,8,45,60,12

CONTROL "", IDOK,"BorBtn",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|WS_GROUP,72,29,37,25

}

//Файл 28-4.cpp

#include <owl\framewin.h>

#include <owl\dialog.h>

#include  "28-4.h"

#include <stdio.h>

class MyWindow;// Объявим имя класса для последующих ссылок на него

MyWindow* myWin;//Объявим имя указателя на объект главного окна

/*Класс приложения, производный от Tapplication*/

class MyApp:public TApplication{

public:

void InitMainWindow();//Замещаем функцию InitMainWindow

};

/*Класс главного окна, производный от TframeWindow*/ class MyWindow:public TFrameWindow{ public:

int view;//Переключатель вида графика

int data[FILESIZE];//Массив для данных из файла

MyWindow(TWindow*,char far*);

void SetupWindow();//Замещаем функцию SetupWindow

void Paint(TDC&,bool,TRect&);//Замещаем функцию Paint

void GetWindowClass(WNDCLASS&);//Замещаем функцию GetWindowClass

void CmView();//Функция отклика на нажатие кнопки "Вид"

DECLARE_RESPONSE_TABLE(MyWindow);//Объявляем таблицу откликов главного окна



};

/*Класс окна  диалога, производный от Tdialog*/ class MyDialog:public TDialog{ public:

MyDialog(TWindow* parent,TResId resId):TDialog(parent,resId){}//Конструктор

EvInitDialog(HWND);//Замещаем функцию EvInitDialog

void CmPoints();//Функции отклика

void CmCurve();//на нажатие

void CmHisto();//альтернативных кнопок

DECLARE_RESPONSE_TABLE(MyDialog);//Объявляем таблицу откликов диалога

};

/*Таблица откликов класса MyWindow*/ DEFINE_RESPONSE_TABLE1(MyWindow,TFrameWindow)

EV_COMMAND(CM_VIEW,CmView), END_RESPONSE_TABLE; /*Конструктор главного окна*/ MyWindow::MyWindow(TWindow*parent,char far*title):TFrameWindow(parent,title){

AssignMenu("MainMenu");//Назначаем главному окну меню

}

/*3амещающая функция SetupWindow*/ void MyWindow::SetupWindow(){

TWindow::SetupWindow();//Вызываем замещенную функцию SetupWindow

FILE* fp=fopen("28-4.dat","r");//Открываем файл для чтения

for(int i=0;i<FILESIZE;i++)

fscanf(fp,"%d",&data[i]);//Читаем символьные данные в массив data,преобразуя в числа

fclose(fp);//Закрываем файл

}

/*3амещающая функция GetWindowClass*/ void MyWindow::GetWindowClass(WNDCLASS& wc){

TWindow::GetWindowClass(wc)///Вызываем замещенную функцию

wc.style=CS_VREDRAW;//Необходимо, т.к. график рисуется снизу

} void MyWindow::CmView(){

new MyDialog(this,Dig)->Create();//Открываем немодальный диалог!

} void MyWindow::Paint(TDC&dc,bool,TRect&){

TPen myPen(TColor::LtBlue,1);//Устанавливаем синее перо

dc.SelectObject(myPen);// для фигур графика

TBrush myBrush(TColor::LtBlue);//Устанавливаем синюю

Диалоговые окна                                         267

dc.SelectObject(myBrush);// кисть для внутренних областей фигур графика TRect wndRect=GetClientRect();//Получим рабочую область главного окна int i;//Вспомогательная локальная переменная switch(view){//Переключение формы отображения графика case(IDC_POINTS)://Вывод точек for(i=0;i<FILESIZE;i++)

dc.Ellipse(1*10-1-10-2,wndRect.bottom-data[i]-2,1*10+10+2, //Рисуем кружки



wndRect.bottom-data[i]+2); break; case(IDC_CURVE) : //Вывод кривой линии

dc.MoveTo(10,wndRect.bottom-data[0]);//Устанавливаем начальную позицию for(i=1;i<FILESIZE;i++)

dc.LineTo(i*10+10,wndRect.bottom-data[i]);// Проводим линии между точками break; case(IDC_HISTO):

for(i=0;i<FILESIZE;i++)

dc.Rectangle(1*10+10,wndRect.bottom-data[i],

1*10+10+9,wndRect.bottom);//Рисуем прямоугольники break; } }

/*Реализация класса MyDialog*/ DEFINE_RESPONSE_TABLE1(MyDialog,TDialog) EV_COMMAND(IDC_POINTS,CmPoints) , EV_COMMAND(IDC_CURVE,CmCurve) , EV_COMMAND(IDC_HISTO,CmHisto), END_RESPONSE_TABLE; MyDialog::EvInitDialog(HWND hwnd){

TDialog::EvInitDialog(hwnd);//Вызываем замещенную функцию EvInitDialog SendDlgItemMessage(myWin->view,BM_SETCHECK,true,0L);//Нажмем кнопку } void MyDialog::CmPoints(){

myWin->view=IDC_POINTS;//Устанавливаем значение переключателя вида графика myWin->Invalidate();//Инициируем перерисовку главного окна } void MyDialog::CmCurve(){

myWin->view=IDC_CURVE; //Устанавливаем значение переключателя вида графика myWin->Invalidate();//Инициируем перерисовку главного окна } void MyDialog::CmHisto(){

myWin->view=IDC_HISTO; //Устанавливаем значение переключателя вида графика myWin->Invalidate();//Инициируем перерисовку главного окна }

/*Замещающая функция InitMainWindow класса приложения*/ void МуАрр::InitMainWindow(void){ EnableBWCC();

myWin=new MyWindow(0,"Программа 28-4");//Сохранили указатель на главное окно myWin->view=IDC_POINTS;//Начальное значение переключателя вида графика SetMainWindow(myWin);//Установили главное окно }

/*Главная функция приложения OwlMain*/ int OwlMain(int,char*[]){

return MyApp().Run(); }

В файле 28-4.h определяются значения идентификаторов органов управления диалога и задается чис­ло точек на графике (константа FILESIZE). Из файла 28-4.rc видно, что в меню имеется единственный содержательный пункт "Вид...", который используется для активизации немодального диалога (для уничтожения диалогового окна служит его кнопка "ОК"). Три альтернативные кнопки, имеющие стиль BS_AUTORADIOBUTTON, объединены в группу флажками WS_GROUP. Первый флажок определяет первую альтернативную кнопку, второй - первый управляющий элемент за пределами альтернативной группы. Впрочем, при наличии единственной альтернативной группы флажки WS_GROUP можно опус­тить.



В файле 28-4. срр объявлены две глобальные переменные - класс MyWindow и указатель на объект этого класса myWin. Такое объявление (в котором, обратите внимание, фигурирует только имя класса, но нет никакой информации о его содержимом или подчиненности) позволяет в любом месте программы, в частности, в функции любого класса, сослаться на этот объявленный ранее класс или, через его указа­тель, на любые открытые функции-члены и данные-члены этого класса. В нашем примере такая необхо­димость возникнет в функциях отклика класса диалога, где устанавливается текущее значение перемен­ной view и вызывается функция Invalidate() класса главного окна.

268                                                                                                                                  Глава 28

В классе главного окна MyWindow объявляется переменная view, которая будет содержать значение режима отображения графика (IDC_POINT, IDC_CURVE или IDC_HISTO). Было бы изящнее объявить ее не int, a enum; это не сделано для сокращения текста программы. Массив data служит для хранения отображаемых на графике данных. Для создания большей иллюзии полезности программы данные не определены в тексте программы, а читаются из файла с заданным заранее именем 28-4.dat; еще лучше было бы включить в программу стандартный диалог Windows для выбора файла и, тем самым, сделать ее гораздо более универсальной.

Далее в классе MyWindow объявляются замещаемые и новые функции-члены и таблица отклика, со­держащая единственную строку-макрос для отклика на пункт главного меню "Вид.

Класс немодального диалога MyDialog содержит объявление замещаемой функции EvInitDialog(), которая удобна для выполнения необходимых инициализирующих действий, а также трех функций от­клика на нажатие альтернативных кнопок диалога. Соответственно в таблицу откликов класса диалога входят три макроса EV_COMMAND.

Подготовительные для всей программы действия - чтение данных из файла - выполняются в заме­щающей функции SetupWindow(). Данные в файле хранятся в символьной форме, т.е. предполагается, что они были введены с клавиатуры. Для разделения таких данных в файле можно использовать различ­ные символы - двоичный нуль, возврат каретки и др. В файле 28-4 dat данные разделяются символом пробела и для графика, изображенного на рис. 28.9, имеют следующий вид:



130 136 141 147 151 153 154 154 152 149 144 139 133 127 121 115 111 107 105 105

Для чтения данных из файла использована функция fscanf(), преобразующая, в процессе чтения, ка­ждое данное в двоичную форму в соответствии с указанным в аргументах функции шаблоном "%d".

Существенным моментом является назначение классу окна стиля CS_VREDRAW. Это назначение выполняется в замещающей функции GetWindowClass(), использование было подробно описано в гл. 26. Стиль CS_VREDRAW определяет полную перерисовку окна (а не только его области вырезки) при лю­бых манипуляциях с размером окна или перемещением по нему окна диалога. Как было показано в гл. 12, полная перерисовка окна, являющаяся неэффективной с точки зрения расходования процессорного времени, необходима в тех случаях, когда изображение в окне строится относительно его нижней (или правой) границы, что типично для вывода графиков.

Открытие немодального диалога осуществляется в функции отклика на нажатие кнопки "Вид..." CmView(). С помощью оператора new создается безымянный объект класса MyDialog и в том же пред­ложении для него вызывается функция класса TDialog Create(), которая служит для вывода на экран не­модального диалогового окна. Вспомним, что для активизации модального диалога мы использовали функцию Execute(), а для его завершения - функцию DestroyWindow(). Немодальный же диалог завер­шать нет особой необходимости, так как он, будучи выведен на экран, не препятствует использованию любых других органов управления главного окна.

Назначение диалога в данном примере заключается в установлении режима вывода графика, для чего предусмотрены три функции отклика: CmPoints(), CmCurves() и CmHisto(). В каждой из этих функций переключателю режима отображения view присваивается соответствующее значение, после чего вызо­вом функции Invalidate() для объекта главного окна инициируется полная его перерисовка.

Вывод графика осуществляется, как и положено, в функции Paint() главного окна. В ней прежде все­го создаются и загружаются в контекст устройства синее перо и синяя кисть, а затем в зависимости от значения переменной view на экран выводятся либо маленькие (радиусом 2 пиксела) кружки, либо ли­нии, соединяющие точки графика, либо прямоугольники, образующие гистограмму. Число 10, фигури­рующее в координатах вывода, характеризует смещение графика на 10 пикселов относительно левой границы окна. Ради простоты в программе не выполняется никаких действий по масштабированию гра­фика и не анализируется число введенных данных.



Как уже отмечалось, графики рисуются относительно нижней границы окна, реальное значение ко­торой зависит от текущего размера окна на экране. Необходимым элементом такой техники является вы­зов функции GetClientRect(), которая возвращает структуру wndRect типа TRect с текущими размерами рабочей (клиентной) области окна. Значение wndRect.Bottom и определяет нижнюю границу.

Глава 29

Окна и их оформление

Исходный текст программы с порожденными окнами

В примере 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

/*Класс приложения, производный от Tapplication*/

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 идентифи­цируются их именами. Однако при обра­зовании классов, производных от TWin­dow, все они получают имена 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() может использовать для определения выводимой строки индекс plainIn­dex, характеризующий номер перерисовываемого в настоящий момент окна.

В классе 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() определяет длину строки неточно) используется при вызове функции перемещения окна.



Приспособления

В приложении 29- 2 демонстрируется методика создания приспособлений, используемых для ввода в программу конкретных значений настраиваемых переменных. В рассматриваемом примере в главном окне приложения создается квадратное дочернее окно-панель с серым фоном, в которое выводятся т.н. фигуры Лиссажу, получаемые при одновременном изменении х- и у- координат точки по синусоидально­му закону. Если х- и у-координаты изменяются с одинаковой частотой, а сдвиг фаз между ними отсутст­вует, то фигура Лиссажу вырождается в прямую линию, наклоненную к осям под углом 45 градусов. При сдвиге фаз между колебаниями по осям, равным пи/2, кривая представляет собой правильную окружность.





Если же х- и у-частоты не совпа­дают, да еще между ними имеет­ся сдвиг фаз, то образуются ти­пичные кривые разнообразной формы, знакомые любому спе­циалисту по электронике.

Для изменения соотношения частот колебаний по осям х и у используется приспособление-ползунок (класс TSlider), который в конкретном примере позволяет изменять соотношение частот от 1 до 10, а для задания Сдвига фаз между колебаниями - линейка прокрутки (класс TScrollBar), за­дающая сдвиг фаз от 0 до я с ша­гом 1/32 пи. Устанавливаемые с помощью приспособлений значе­ния отношения частот и сдвига фаз отображаются в главном окне над соответствующими приспо­соблениями.

На рис. 29.3 изображен вид окна приложения с примером фигуры Лиссажу.

//Приложение 29-2.   Дочернее      окно,   ползунок и линейка  прокрутки

//Файл  29-2.h

#define ID_FREQUENCYSLIDER                   100

#define ID_FREQUENCYTEXT                     101

#define ID_FREQUENCYLEGEND                   102

#define ID_PHASEBAR                          103

#define ID_PHASETEXT                         104

#define ID_PHASELEGEND                       105

274                                                    Глава 29

//Файл 29-2. rc

#include <owl\slider.rc> //Включение в наш файл изображения ползунка



//Файл 29-2.срр

#include <owl\framewin.h>

#include <owl\slider.h>

#include <owl\static.h>

#include <owl\slider.h>

#include <math.h>

#include "29-2.h"

const float PI=3.1415926;\//Число пи

int kf,ph;//Значения частоты и фазы, снимаемые с приспособлений

/*Класс приложения, производный от Tapplication*/

class MyApp:public TApplication{

public:

void InitMainWindow(); //Замещаем функцию InitMainWindow };

/*Класс дочернего окна- панели Panel, производный от класса Twindow*/ class Panel:public TWindow{ public:

Panel(TWindow* parent,char far* title);

void Paint(TDC&,bool,TRect&);//Замещаем функцию Paint для панели

}; Panel::Panel(TWindow* parent,char far* title):TWindow(parent,title){

Attr.Style=WS_CHILD|WS_VISIBLE|WS_BORDER;;

Attr.X=10;Attr.Y=10;Attr.W=220;Attr.H=240;

SetBkgndColor(TColor::LtGray);

}

/*Класс главного окна, производный от

TframeWindow*/ class MyWindow:public TFrameWindow{ public;

MyWindow(TWindow*parent,char far*title);

void SetupWindow();//Замещаем функцию SetupWindow

TSlider* frequencySlider;//Указатель на объект-ползунок

TScrollBar* phaseBar;//Указатель на объект-линейку прокрутки

TStatic *frequencySliderValue, *phaseBarValue, *frequencyLegend, *phaseLegend;

void UpdateFrequency(UINT=0);//Функция настройки частоты

void UpdatePhase(UINT=0);//Функция настройки фазы

Panel* panel;//Указатель на объект-панель

DECLARE_RESPONSE_TABLE(MyWindow);//Объявляем таблицу откликов от приспособлений

} ; DEFINE_RESPONSE_TABLE1(MyWindow,TFrameWindow)//Таблица откликов от приспособлений

EV_CHILD_NOTIFY^ALL_CODES(ID_FREQUENCYSLIDER,UpdateFrequency),

ЕV_СНILD_NOTIFY_ALL_CODES(ID_PHASEBAR,UpdatePhase), END_RESPONSE_TABLE; /*Функции-члены класса MyWindow*/ MyWindow::MyWindow(TWindow*parent,char far*title):TFrameWindow(parent,title){

panel=new Panel(this,NULL); //Создаем окно-панель без заголовка

frequencySlider=new THSlider(this,ID_FREQUENCYSLIDER, 250, 50,150,32);//Ползунок

phaseBar=new TScrollBar(this,ID_PHASEBAR,250,180,150,0,TRUE);//Линейка прокрутки



frequencySliderValue=new TStatic(this,-1,"",250,20,200,25);

frequencyLegend=new TStatic(this,-1,

"1                              10,250,85,200,25);

phaseBarValue=new TStatic (this, -1,"", 250,150,200,25);

phaseLegend=new TStatic(this,-1,

"0                             32",250,215,200,25);

} void MyWindow::UpdateFrequency(UINT notifyCode){

char str[50];

if(notifyCode==SB_THUMBTRACK||notifyCode==SB_ENDSCROLL) return;

else{

kf=frequencySlider->GetPosition(); wsprintf(str,"Отношение частот = %d",kf); frequencySliderValue->SetText (str);

panel->Invalidate(); }

} void MyWindow::UpdatePhase(UINT){

char str[50];

ph=phaseBar->GetPosition();

wsprintf(str,"Сдвиг фаз = PI/32 * %d",ph);

phaseBarValue->SetText(str);

Окна и их оформление                                                                                                       275

panel->Invalidate(); } /*3амещающая функция SetupWindow*/

void MyWindow::SetupWindow(){

TWindow::SetupWindow();//Вызываем замещенную функцию SetupWindow

frequencySlider->SetRange(1,10);//Нижний и верхний пределы шкалы

freguencySlider->SetRuler(1,TRUE);//Шаг шкалы и фиксация

frequencySlider->SetPosition(1);//Начальное положение ручки

phaseBar->SetRange(0,32);//Нижний и верхний пределы шкалы

phaseBar->SetPosition(16);//Начальное положение ручки

UpdateFrequency();//

UpdatePhase() ;//

}

/*Функция Paint для окна-панели*/ void Panel::Paint(TDC&dc,bool,TRect&){

for(float i=0;i<2*PI*1000;i++){// В цикле из 1000 шагов float x=sin(i/100)*100;//х-координата текущей точки float y=sin((i/100+PI/32*ph)*kf)*100;//у-координага текущей точки dc.Rectangle(x+110,y+110,x+112,y+112);11Квадратики вместо точек }

}

/*3амещающая функция InitWainWindow*/ void MyApp::InitMainWindow(void){

SetMainWindow(new MyWindow(0,"Программа 29-2"));

} /*Главная функция приложения OwlMain*/

int OwlMain(int,char*[]){

return MyApp{).Run();



}

Для создания дочернего окна-панели в программе объявляется класс Panel, производный от TWindow и включающий всего две функции - конструктор и замещающую функцию Paint(). В конструкторе клас­са устанавливается (с помощью структуры Attr) стиль окна - дочернее, видимое и без рамки, а также за­даются его координаты и цвет фона. В функции Paint() в цикле из 1000 шагов вычисляются х- и у-координаты точек фигуры Лиссажу с учетом введенных пользователем значений отношения частот kf и сдвига фаз ph. Каждая точка рисуется в виде квадратика с размером сторон 2 пиксела. Число шагов в цикле и шаг изменения координат выбраны так, чтобы в графики состояли из достаточно большого числа точек. Рассмотренный алгоритм рисования двумерного графика использован по причине его наглядности и, строго говоря, не выдерживает никакой критики. По-настоящему следовало для каждой х-координаты с шагом в 1 пиксел вычислять соответствующую ей у-координату. Это устранило бы наложения и разры­вы точек и позволило бы обойтись гораздо меньшим числом шагов в цикле, однако усложнило бы фор­мулы для вычисления координат.

Главное окно приложения, представленное классом MyWindow, производным от TWindow, содержит в себе, помимо дочернего окна Panel, еще ряд управляющих элементов: горизонтальный ползунок, гори­зонтальную же линейку прокрутки и четыре статических элемента для вывода шкал и поясняющих над­писей. Среди данных-членов класса MyWindow объявлены указатели на все эти элементы, а также две прикладные функции UpdateFrequency() и UpdatePhase(), служащие для съема данных с динамических элементов управления и модификации статических. Эти функции являются функциями отклика на сооб­щения, поступающие от элементов управления.

В таблицу откликов класса MyWindow включены всего два макроса EV_CHILD_NOYIFY_ALL_CODES, каждый из которых предназначен для вызова функции отклика для своего элемента управления при поступлении любого сообщения от этого (дочернего) элемента. Анализ приходящих сообщений, если он нужен, возлагается на функции отклика, при этом нотификационный код сообщения передается макросом в функцию отклика в качестве ее параметра.



В конструкторе класса MyWindow создаются объекты для окна-панели, двух динамических элемен­тов управления классов THSlider и TScrollBar (см. рис. 28.5), а также для трех статических элементов класса TStatic. Для всех объектов указываются их идентификаторы и расположение в главном окне, а для статических элементов - выводимый текст. Ползунок и линейка прокрутки пока получают настройки по умолчанию, которые, скорее всего, не соответствуют тому, что нужно нам в данном конкретном случае.

Настройка динамических элементов управления осуществляется в замещающей функции SetupWin-dow(). Здесь прежде всего необходимо вызвать исходную, замещенную функция класса TWindow, кото­рая создает все порожденные окна (в нашем случае - дочернее окно и элементы управления), для кото­рых в конструкторе класса MyWindow мы создали OWL-объекты. Если позабыть вызвать замещенную функцию SetupWindow(), главное окно останется пустым. Далее вызовом соответствующих функций классов THSlider и TScrollBar устанавливаются требуемые настройки приспособлений - пределы и шаг шкал, а также начальные положения ручек. Наконец, прямым вызовом функций откликов UpdateFre-quency() и UpdatePhase() выполняется начальное снятие значений с элементов управления и формирова­ние изображения в главном окне.

276                                                                                                                                  Глава 29

Функции откликов активизируются при выполнении пользователем каких-либо манипуляций с эле­ментами управления. В них вызовом OWL-функции GetPosition() для соответствующего элемента снима­ется текущее положение его ручки, функцией wspintf() формируется текущее содержимое надписи, и функцией TStatic::SetText() новая надпись выводится в дочернее окно, соответствующее статическому объекту. Для того, чтобы изменения отобразились на экране, вызывается функция Invalidate() для главно­го окна.

Помимо описанных действий, общих для обеих функций отклика, при входе в функцию UpdateFre-quency() выполняется отбраковка двух кодов нотификации - SB_THUMBTRACK и SB_ENDSCROLL. В результате отбрасываются сообщения от ползунка, возникающие, когда пользователь тащит его ручку, а также сообщения, поступающие при отпускании клавиши мыши. Игнорирование этих сообщений замет­но уменьшает мелькание экрана, который перерисовывается только в моменты окончательного выбора нового положения ручки ползунка.



Декорированные окна

В библиотеке OWL предусмотрены средства для создания в окне приложения специальных приспо­соблений - панели, или линейки инструментов, инструментального планшета и линейки, или строки со­стояния (статусной строки). Окно с такого рода приспособлениями принадлежит классу TDecoratedWin-





dow и называется декорированным окном. Как видно из рис. 29.4, этот класс является производным как от класса окон с рамками TFrameWindow, так и от класса TLayoutWindow, обеспечивающего поведение окон, размеры и положение ко­торых взаимозависимы. С помощью класса TLayoutWindow можно, например, создать дочернее окно, которое будет пропорционально изменять свои размеры при изменении размеров родительского окна, или располагаться всегда на определенном расстоянии от его границ; можно задать жест­кую связь между правой границей одного дочернего окна и левой границей другого, что как бы "приклеивает" их друг к другу. В рассматриваемом далее примере эти возможности пригодятся для создания окна-клиента, совпадающего по размерам с главным окном приложения.

Класс TDecoratedFrame, включая в себя все возможности классов TFrameWindow и TLayoutWindow, добавляет к ним еще и средства размещения в окне разного рода приспособлений с заданными характе­ристиками поведения.

Сами приспособления выделены в библиотеке OWL в отдельную группу классов, являющихся про­изводными от базового класса TGadget (рис. 29.5). В рассматриваемом далее примере будут использова­ны классы TButtonGadget и TSeparatorGadget.





Наконец, такие инструменты, как инструментальная линейка, инструментальный планшет или строка состояния образуются с помощью группы классов, производных от базового для них класса TGadget-Window (который сам выводится из класса TWindow, рис. 29.6). Мы приведем примеры использования





классов TToolBox, TMessageBar и TControlBar. Рассмотренный ниже пример 29-3 прило­жения с декорированным окном является пере­работкой примера 28-4, в котором в главное окно приложения выводилось графическое отображение некоторого массива данных, при­чем форму представления графика можно было изменять с помощью немодального диалогово­го окна. В примере 29-3. для изменения формы представления данных, а также цвета графика вместо диалога с кнопками настройки исполь­зуется классический набор инструментов деко­рированного окна (рис. 29.7).









Окна и их оформление                                                     '                                                277

Для создания декорированного окна с достаточно полными функциональными возможностями необ­ходимо выполнить следующие шаги:

•   Описать класс главного окна (в примере 29-3 — MyWindow), который должен быть производным не

от класса TFrameWindow, как обычно, а от класса TDecoratedWindow

•   Описать класс окна-клиента (в примере - MyClient), производный от базового оконного класса

TWindow. Именно окно-клиент будет выступать в дальнейшем в качестве рабочего окна приложения, и

именно для него следует описать таблицу откликов и функцию Paint()

•   Привязать окно-клиент к главному окну приложения, что выполняется путем включения в конст­

руктор главного окна указателя на окно-клиент

•   В программе конструктора главного (декорированного) окна предусмотреть заполнение его необ­

ходимыми приспособлениями

•   Побеспокоиться заранее о разработке файлов .bmp с рисунками для кнопок инструментального

планшета и линейки инструментов и включить ссылки на них в файл ресурсов приложения. В файле ре­

сурсов должна также содержаться таблица строк (ресурс STRINGTABLE), выводимых в линейку состоя­

ния.

Рассмотрим теперь текст приложения 29-3.

//Приложение 29-3. Декорированные  окна

//Файл 29-3.h

#define Dlg                        100

#define  CM_HELP             202

#define  CM_EXIT              24310

#define  CM_POINTS         101

#define  CM_CURVE         102

#define  CM_HISTO           103

#define CM_BLUE              104

#define  CM_GREEN               105

#define  FILESIZE         20

#define  IDB_POINTS        301

#define   IDB_CURVE        302

#define  IDB_HISTO          303

#define  IDB_HELP            304

#define  1DB_BLUE           305

#define  IDB_GREEN         306

//Файл 29-3.rc #include   "29-3.h" MainMenu MENU{ POPUP  "Файл"{



MENUITEM  "Справка",CM_HELP

MENUITEM  SEPARATOR

MENUITEM   "Выход",CM_EXIT

} POPUP   "Вид"{

MENUITEM "Точки",CM_POINTS

MENUITEM "Огибающая",CM_CURVE

MENUITEM "Гистограмма", CM_HISTO

} }

Dlg DIALOG 82, 70, 116, 84

STYLE WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU CLASS "BorDlg"

278                                                                                                                                  Глава 29

CAPTION  "Справка"

FONT 8, "MS Sans Serif"{

CTEXT "В программе демонстрируется организация декорированных окон",-1,8,7,100,36

CONTROL "",IDOK,"BorBtn",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|WS_TABSTOP,40,49,37,25

}

IDB_POINTS BITMAP "29-3pt.bmp" IDB_CURVE BITMAP "29-3cur.bmp" IDB_HISTO BITMAP "29-3hist.bmp" IDB_HELP BITMAP "29-3hlp.bmp" IDB_BLUE BITMAP "29-3blu.bmp" IDB_GREEN BITMAP "29-3gr.bmp" STRINGTABLE{

CM_HELP "Вывод справки"

CM_EXIT "Завершение приложения"

CM_POINTS "График в виде точек"

CM_CURVE "График в виде кривой"

CM_HISTO "График в виде гистограммы"

CM_BLUE "График синего цвета"

CM_GREEN "График зеленого цвета"

}

//Файл 29-3.срр

#include <owl\framewin.h>

#include <owl\decframe.h>

#include <owl\dialog.h>

#include <owl\controlb.h>

#include <owl\toolbox.h>

#include <owl\statusba.h>

#include <owl\buttonga.h>

#include <owl\messageb.h>

#include "29-3.h"

#include <stdio.h>

enum CHOICE{POINTS=CM_POINTS,CURVE=CM_CURVE,HISTO=CM_HISTO}view;

enum COLOR{BLUE=CM_BLUE,GREEN=CM_GREEN}color;

int data[FILESIZE];//Массив для данных из файла

/*Класс приложения, производный от Tapplication*/

class MyApp:public TApplication{

public:

void  InitMainWindow() ;//Замещаем функцию InitMainWindow

};

/*Класс главного окна, производный от TdecoratedFrameWlndow*/ class MyWindow:public TDecoratedFrame{



TControlBar* toolBar;

TToolBox* toolBox;

TMessageBar* statusLine; public:

MyWindow(TWindow*,char far*,TWindow*);

void GetWindowClass(WNDCLASS&);//Замещаем функцию GetWindowClass

};

/*Класс окна-клиента, производный от Twindow*/ class MyClient:public TWindow{ public:

MyClient():TWindow(0,""){};

void Paint(TDC&,bool,TRect&);//Замещаем функцию Paint

void CmHelp();// Набор функций откликов на сообщения от пунктов меню

void CmExit();//Они же используются как функции отклика

void CmPoints{);//на сообщения от кнопок инструментального планшета

void CmCurve();//и линейки инструментов

void CinHisto();

void CmBlue();

void CmGreen();

DECLARE_RESPONSE_TABLE(MyClient);//Объявляем таблицу отклика для окна-клиента

};

/*Таблица откликов класса MyClient*/ DEFINE_RESPONSE_TABLE1(MyClient,TWindow) .

EV_COMMAND(CM_HELP,CmHelp),

EV_COMMAND(CM_POINTS,CmPoints) ,

EV_COMMAND(CM_CURVE,CmCurve),

EV_COMMAND(CM_HISTO,CmHisto),

EV_COMMAND(CM_BLUE,CmBlue),

EV_COMMAND(CM_GREEN,CmGreen), END_RESPONSE_TABLE;

Окна и их оформление                                                                                                       279

/*Конструктор главного окна*/

MyWindow::MyWindow(TWindow* parent,char far* title.TWindow* client):

TDecoratedFrame(parent,title,client,TRUE){ AssignMenu("MainMenu");//Назначаем главное меню

view=POINTS; //Начальные значения переключателей-флагов вида графика соlor=BLUE//и цвета изображения

memset(data,0,sizeof(data));//Обнуляем массив данных toolBar=new TControlBar(this);

toolBar->Insert(*new TButtonGadget(IDB_POINTS,CM_POINTS)); toolBar->Insert(*new TButtonGadget(IDB_CURVE,CM_CURVE)); toolBar->Insert(*new TButtonGadget(IDB_HISTO,CM_HISTO)); toolBar->Insert(*new TSeparatorGadget(6)); toolBar->Insert(*new TButtonGadget(IDB_HELP,CM_HELP));

Insert (*toolBar, TDecoratedFrame: : Top);

toolBar->SetHintMode(TGadgetWindow::EnterHints);

toolBox=new TToolBox(this,1);

toolBox->Insert(*new TButtonGadget(IDB_BLUE,CM_BLUE)); toolBox->Insert(*new TButtonGadget(IDB_GREEN,CM_GREEN)); Insert (*toolBox,TDecoratedFrame::Left); statusLine=new TMessageBar(this); Insert(*statusLine,TDecoratedFrame::Bottom);



/*Функция отклика окна-клиента*/ void MyClient::CmHelp(){

new TDialog(this,Dig)->Execute();//Открываем модальный диалог } void MyClient::CmPoints(){

view=POINTS;//Устанавливаем значение флажка-переключателя Invalidate(); //Инициируем перерисовывание рабочего окна }

void MyClient::CmCurve(){ view=CURVE; Invalidate(); }

void MyClient::CmHisto(){ view=HISTO; Invalidate(); }

void MyClient::CmBlue(){

color=BLUE; Invalidate(); } void MyClient::CmGreen(){

color=GREEN;

Invalidate(); }

/*Функция Paint() класса MyClient*/ void MyClient::Paint(TDC&dc,bool,TRect&){ if(color==BLUE){

TPen myPen(TColor::LtBlue, 1); dc.SelectObject(myPen); TBrush myBrush(TColor::LtBlue); dc.SelectObject(myBrush); } else if(color==GREEN){

TPen myPen(TColor::LtGreen,1); dc.SelectObject(myPen); TBrush myBrush(TColor::LtGreen); dc.SelectObject(myBrush); }

TRect wndRect=GetClientRect();// Получим рабочую область рабочего окна int i;

switch(view){ case(POINTS): for(i=0;i<FILESIZE;i++)

dc.Ellipse(i*10+10-2,wndRect.bottom-data[i]-2,1*104-10+2,

wndRect.bottom-data[i]+2); break; case(CURVE):

dc.MoveTo(10,wndRect.bottom-data[0]); for(i=l;i<FILESIZE;i++)

280                                              Глава 29

dc.LineTo(i*10+10,wndRect.bottom-data[i] ); break;

case(HISTO): for(i=0;i<FILESIZE;i++)

dc.Rectangle(i*10+10,wndReqt.bottom-data[i],i*10-4-10+9,wndRect.bottom); break; } }

/*Замещающая функция GetWindowClass*/ void MyWindow::GetWindowClass(WNDCLASS& wc){ TWindow::GetWindowClass(wc);

wc.style=CS_VREDRAW;//Необходимо, т.к. график рисуется снизу }

/*Замещающая функция InitMainWindow()*/ void MyApp::InitMainWindow(){ EnableBWCC();

SetMainWindow(new MyWindow(0,"Программа 29-3",new MyClient)); FILE* fp=fopen("28-4.dat","r");//Открываем файл для чтения for(int i=0;i<FILESIZE;i++)

fscanf(fp,"%d",&data[i]);//Читаем данные в массив data fclose(fp);//Закрываем файл }

/*Главная функция приложения OwlMain*/ int OwlMain(int,char*[]){



return MyApp().Run(); }

В файле 29-3 .h, как и обычно, описываются константы - идентификаторы пунктов меню (начинаю­щиеся у нас с префикса СМ_), которые будут одновременно использоваться и как идентификаторы соот­ветствующих инструментальных кнопок, а также идентификаторы ресурсов BITMAP (с префиксом IDB_).

Файл 29-3 .rc состоит в данном случае из четырех частей, в которых описывается форма главного ме­ню приложения, форма модального диалогового окна с информацией о программе, список ресурсов BITMAP с указанием имен .bmp-файлов с изображениями для кнопок и, наконец, ресурс STRINGTABLE, представляющий собой перечень строк, которые будут выводиться в линейку состояния, с указанием в качестве их номеров идентификаторов соответствующих пунктов меню (и инструментальных кнопок).

В файле 29-З.срр после довольно длинного перечня включаемых файлов вводятся описания двух пе­речислимых типов, которые будут в дальнейшем использоваться в качестве переключателей-флагов ре­жимов отображения: CHOICE для определения вида графика и COLOR для задания его цвета. В качестве допустимых значений переменных этих типов использованы идентификаторы пунктов меню. Тут же объявлены и конкретные переменные перечислимых типов: view и color.

В класс главного окна MyWindow, производный от TDecoratedFrame, включены указатели на объек­ты типа TControlBar (инструментальная линейка), TToolBox (инструментальный планшет) и TMessage-Ваг (линейка состояния). В классе MyWindow замещена функция GetWindowClass(), что дает возмож­ность добавить в стиль класса бит CS_VREDRAW. Обратите внимание на формат конструктора главного окна

MyWindow(TWindow*,char  far*,TWindow*);

отличающийся от много раз использованного нами конструктора окна с рамкой

MyWindow(TWindow*,char  far*);

В рассматриваемом примере класс MyWindow является производным от класса TDecoratedWindow, в конструкторе которого третий параметр отводится под указатель (типа TWindow*) на окно-клиент. При конструировании объекта главного окна (в функции InitMainWindow()) мы передадим через третий па­раметр этого конструктора указатель тут же и создаваемого объекта окна-клиента:



SetMainWindow(new MyWindow(0,"Программа 29-3",new MyClient));

Далее описывается класс MyClient окна-клиента (рабочего окна приложения). В описание класса включается конструктор, прототипы функций откликов на сообщения от пунктов меню (и инструментов) и сама таблица откликов. Несколько необычно выглядит конструктор этого класса

MyClient():TWindow(0,""){}

Для того, чтобы правильно описать параметры этого конструктора, мы должны рассмотреть возможные формы конструкторов класса TWindow. В этом классе предусмотрены конструкторы двух видов:

TWindow(HWND hWnd, TModule* module = 0);

TWindow(TWindow* parent, const char far* title = 0, TModule* module = 0);

Окна и их оформление                                                                                                       281

Первый конструктор используется как псевдоним для не OWL-окна и нас интересовать не будет. Второй конструктор требует трех параметров, из которых два назначаются по умолчанию и, вроде бы, могут опускаться. Однако мы не можем объявить конструктор класса MyClient в единственным парамет­ром

MyClient():TWindow(0){}

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

MyClient():TWindow(0,0){}

так как и в этом случае двусмысленность не ликвидируется. Таким образом, у нас остаются только две возможности: объявить конструктор с тремя параметрами (три параметра имеет только нужный нам кон­структор TWindow) или указать в качестве второго параметра не нулевой указатель, а указатель на пус­тую строку (поскольку для окна-клиента заголовок не нужен):

MyClient():TWindow(0,0,0){} MyClient():TWindow(0,""){}

Нами выбран второй из этих вариантов.

Теперь о значении первого параметра. Вообще, в качестве первого параметра должен использоваться указатель на родительский модуль, но если значение этого параметра равно 0, то используются опреде­ленные правила умолчания для автоматической подстановки неопределенного параметра. Для порож­денных окон подставляется указатель на модуль родителя, а для окон без родителя - указатель на модуль приложения, что позволяет, в частности, использовать ресурсы приложения. Для нас это не имеет особо­го значения, поскольку окно-клиент не использует ресурсы.



Обратимся теперь к конструктору MyWindow, в котором, собственно, и задается требуемый вид де­ корированного окна. После прикрепления главного меню, задания начальных значений для флагов-переключателей view и color и обнуления массива данных создается (пока пустой) объект инструмен­тальной панели класса TControlBar. Далее этот объект начинает заполняться конкретными приспособле­ниями, для чего используется открытая виртуальная функция базового для инструментов класса TGadgetWindow Insert(), вызываемая для объекта класса TControlBar. Кнопки инструментальной панели создаются, как объекты класса TButtonGadget, в конструкторе которого указывается идентификатор ре­сурса - растрового изображения для кнопки, а также идентификатор самой кнопки (в действительности конструктор класса TButtonGadget имеет большее число параметров, но для остальных в нашем случае можно использовать значения по умолчанию). Создаваемые кнопки будут располагаться друг за другом вплотную. Для отделения одной или нескольких кнопок используется объект класса TSeparatorGadget, в качестве параметра которого указывается расстояние между кнопками (в единицах толщины рамки ок­на). По умолчанию это расстояние равно 6 единицам. Обратите внимание на вид параметра функции In-sert(): она требует в качестве параметра не указатель на объект, а сам объект, который мы получаем с по­мощью снятия ссылки с указателя.

Определив состав инструментальной линейки, ее следует включить в состав декорированного окна вызовом функции Insert() класса TDecoratedFrame. Поскольку обращение к этой функции осуществляется в другой функции-члене того же класса (конкретно - в конструкторе), ее можно (и нужно) вызывать без указания объекта. В качестве параметров этой функции указываются объект инструментальной линейки и место ее расположения. Последний параметр является перечислимым типом, описанным в классе TDe­coratedFrame, что и указано в явной форме. Параметр может принимать естественные значения Top, Bot­tom Left и Right.



Завершающим действием по созданию инструментальной линейки является включение механизма вывода в линейку состояния поясняющих надписей из файла ресурсов. Этот механизм активизируется функцией SetHintMode() с константой EnterHints, если поясняющие надписи должны появляться при прохождении курсора мыши над кнопкой инструментальной линейки, или с константой PressHints, если надписи должны появляться только при нажатии соответствующей кнопки инструментальной линейки.

Аналогично инструментальной линейке создается, заполняется кнопками и включается в состав де­корированного окна инструментальный планшет (класс TTooIBox). При создании объекта инструмен­тального планшета указывается число линеек в нем (у нас - 1). Большее число линеек используется для планшетов с большим числом кнопок . Мы расположили планшет с левой стороны окна.

Наконец, в нижней части окна создается объект линейки состояния (класс TMessageBar). Линейку состояния с большими изобразительными возможностями можно было создать на базе класса TStatusBar.

Функции откликов окна-клиента и функция Paint() того же окна вряд ли нуждаются в особых ком­ментариях. Операция открытия файла (с фиксированным именем) помещена в текст замещающей функ­ции InitMainWindow().

Последнее замечание относительно рисунков на кнопках. Для их подготовки использовалась про­грамма Resource Workshop, которая удобна тем, что позволяет задать определенный размер рисунка. В данном примере для кнопок инструментальной линейки (в верхней части окна приложения) использова­лись 16-цветные рисунки размером 20x20 пикселов; для кнопок планшета (в левой части окна) были для

282                                                                                                                                 Глава 29

разнообразия изготовлены рисунки меньшего (16x16 пикселов) размера. Это хорошо видно на рис. 29.8, где приведен вид окна приложения при выводе графика в виде точек и при активизированном модальном диалоге (пункт меню "Справка" или кнопка с вопросительным знаком).