На рис. 26.3. приведен результат работы приложения, рассматриваемого в этом разделе.
//Пример 26-2. Вывод геометрических фигур
//Файл 26-2.срр
#include <owl\framewin.h>
/*Отображаемые на графике даяние*/
int data[10]={0,20,15,36,50,45,50,70,85,100); /*Константы, описывающие размера изображения*/
const radius=4; //Радиус точек графика
const dx=2Q;//Шаг по X
const margins=10; //Поля графика
const X=dx*9; // Ширина графика из 10 точек
const Y=100;// Высота графика
const XBorder=X+2*margins;/7Высота рамки вокруг графика const YBorder=Y+2*margins; //Ширина рамки вокруг графика /*Объекты классов положения и размеров*/
TSize size(radius*2,radius*2) ;//Квадрат для рисования точек графика TPoint X0Y0(30,10); //Верхний левый угол рамки относительно окна приложения TPoint XmYm=X0Y0.OffsetBy(XBorder,YBorder) ;//Правый нижний угол рамки TRect border(X0Y0,XmYm); //Прямоугольник рамки TRect graph=border.InflatedBy(-margins,-margins);// Прямоугольник графика
232 Глава 26
/*Класс приложения, производный от TApplication (ради InitMainWindow)*/
class MyApp:public TApplication{ public:
void InitMainWindow(); //Замещаем функцию InitMainWindow };
/*Класс главного окна, производный от TFrameWindov (ряди 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 };
/*Замещенная функция InitMainWindow()*/ void MyApp::InitMainWindow(void){
SetMainWindow(new MyWindow(0,"Программа 26-2")); }
/*Замещенная функция Paint ()*/ void MyWindow::Paint(TDC&dc,bool,TRect&){ int i;// Переменная циклов
char ticks[10][2]; //Массив цифр под осью X TPoint р; //Текущая координата для рисования рисок dc.Rectangle(border);//Рисуем рамку for(i=0;i<=9;i++){//В цикле по 10 точкам
p=graph.BottomLeft()+=i*dx; //Текущая координата верхних концов рисок dc.MoveTo(p); / / В цикле перемещаемся по верхним концам рисок dc.LineTo(p.OffsetBy(0,margins)); //и рисуем риски вниз до рамки wsprintf(ticks[i],"%d",i);// Преобразуем цифры в символы dc.TextOut(p.OffsetBy(-3,margins+3),ticks[i]); //Выводим цифры под осью }
dc.MoveTo(graph.TopLeft());// Рисуем верхнюю
dc.LineTo(graph.TopLeft().OffsetBy(-margins,0));// горизонтальную риску dc.MoveTo(graph.BottomLeft ());//Рисуем нижнуюю
dc.LineTo(graph.BottomLeft().OffsetBy(-margins,0));// горизонтальную риску. dc.TextOut(graph.TopLeft().OffsetBy(-margins-25,-7),"100");// Выводим цифры dc.TextOut(graph.BottomLeftО.OffsetBy(-margins-10,-7),"0");/ 1'масштаба TPen pen1(TColor::LtBlue,2); //Создаем синее перо толщиной 2 пиксела dc.SelectObject(pen1);//и выбираем его в контекст устройства
dc.MoveTo(graph.BottomLeft().OffsetBy(0,-data[0])); //Перемещаемся к первой точке for(i=1;i<=9;i++) //В цикле по 9 точкам соединияем линиями точки графика
dc.LineTo(graph.BottomLeft().OffsetBy(i*dx,-data[i])); TPen pen2(TColor::LtMagenta); //Создаем фиолетовое перо dc.SelectObject(pen2);//и выбираем его в контекст устройства TBrush brush(TColor::LtMagenta); //Создаем фиолетовую кисть dc.SelectObject(brush); //и выбираем ее в контекст устройства for(i=0; i<=9;i++) //Рисуем залитые точки графика
dc.Ellipse(graph.BottomLeft().OffsetBy(i*dx-radius,-data[i]-radius),size); }
/*Главная функция приложения OwlMain*/ int OwlMain(int,char*[]){ return MyApp().Run(); }
|
|
В примере 26-2 в главное окно приложения выводится график значений, записанных в массиве целых чисел data. Для упрощения программы эти числа не считываются из файла данных на диске, как это было бы более естественно, а описаны непосредственно в программе; кроме того, не предусмотрено ни масштабирования чисел (считается, что они расположены в диапазоне от 0 до 100), ни изменения их количества. Точки графика соединены прямыми линиями и, кроме того, каждая точка отображается в виде залитого кружка.
Программа по своей структуре аналогична уже рассмотренным, если не считать вид функции InitMainWindow, в которой создается объект класса MyWindow и созданное окно объявляется главным окном приложения. Поскольку указатель на этот объект (в предыдущих примерах он назывался myWin) нигде больше в программе не используется, нет необходимости его создавать отдельным предложением программы. В варианте, использованном в данном примере
SetMainWindow(new MyWindow(0,"Программа 26-2"));
оператор new, выступающий в качестве аргумента функции SetMainWindow(), возвращает значение указателя на создаваемый объект класса MyWindow, и все работает правильно, хотя этот указатель и не присутствует в программе в явном виде.
Обработка сообщения WM_PAINT u интерфейс GDI 233
Для упрощения разработки программы геометрические характеристики изображения записаны в виде символических констант (для целочисленных констант описатель int можно опускать), смысл которых показан на рис. 26.4.
Рис. 26.4. Обозначения точек, линий и прямоугольников для программы 26-2.
В программе 26-2, в частности, демонстрируется использование классов OWL 5.0, служащих для определения размеров и координат точек, линий и прямоугольных областей. Объекты этих классов сами по себе не производят никаких видимых эффектов, однако их удобно использовать в функциях GDI, требующих задания геометрических характеристик. Класс TPoint задает координаты точки, класс TRect -координаты прямоугольника, а класс TSize включает в себя пару чисел int, которые можно использовать для задания каких-либо размеров или смещений. В настоящем примере с помощью объекта класса TSize описаны размеры квадрата, служащего для вывода на экран круглых точек.
Объекты классов положения и размеров описаны в примере 26-2 в начале программы, в области глобальных переменных. С таким же успехом их можно было описать непосредственно в функции Paint(), где только они и используются.
Функция Paint() в принципе выглядит точно так же, как и в традиционных программах и состоит из последовательности вызовов функций GDI, осуществляющих рисование элементов изображения - рамки, рисок, надписей, кружков и т.д. Все эти функции вызываются для объекта контекста устройства dc, созданного конструктором класса TPaintDC, как это было описано в предыдущем разделе. Использованные в программе функции (Rectangle(), MoveTo(), LineTo() и др.) являются инкапсулированными функциями API Windows и не нуждаются в пояснениях. В качестве аргументов этих функций можно указывать обычные координаты точек, как и в API Windows, однако использование объекты классов положения и размеров предоставляет более широкие возможности. При этом функции GDI позволяют указывать геометрические характеристики рисуемых фигур в разных вариантах. Так, для рисования прямоугольной рамки в программе использован вызов
dc.Rectangle(border); //Рисуем рамку
где border - объект класса TRect. С таким же успехом можно было указать координаты прямоугольника в виде двух объектов класса TPoint
dc.Rectangle(X0Y0,XmYm);
или четырьмя целыми числами.
Имея объект класса TRect, можно с. помощью функций-членов этого класса выполнять различные операции над прямоугольником: извлекать его геометрические характеристики (площадь, высоту и ширину, координаты сторон и углов), перемещать его в заданную точку, увеличивать или уменьшать, определять вхождение в него точки с заданными координатами и т.д. Например, использованные в программе функции BottomLeft() и TopLeft() возвращают координаты нижнего левого и верхнего левого углов рамки графика, а функция InflateBy() с отрицательными аргументами позволяет получить из прямоугольника-рамки border прямоугольник graph меньшего размера, описывающий область собственно графика (см. секцию описания объектов классов в начале программы)
|
|
С помощью функций-членов класса TPoint можно найти расстояние данной точки от начала координат окна, а также переместить точку в другое место или определить координаты точки, отстоящей от данной на заданное смещение по осям. Именно последняя функция (носящая имя OffsetBy()) несколько раз используется в настоящем примере. Например, в паре предложений
dc.MoveTo(graph.TopLeft()); // Рисуем верхнюю
dc.LineTo(graph.TopLeft().OffsetBy(-margins,0)); / /горизонтальную риску
234 Глава 26
текущая позиция для данного контекста устройства смещается функцией TopLeft() в левый верхний угол области графика graph, после чего функцией LineTo() проводится прямая линия до точки, координаты которой определяются следующим образом: сначала находится объект класса TPoint, описывающий левый верхний угол области графика (graph.TopLeft()), а затем для него вызывается функция OffsetBy(), которая возвращает точку, смещенную на margins влево по оси х. В результате рисуется верхняя горизонтальная риска длиной margins.
В рассматриваемом примере демонстрируется также задание цвета элементов изображения с помощью класса TColor. В этом классе определены 10 открытых данных-членов для задания 10 основных цветов из системной цветовой палитры Windows:
TColor: Black; //Значение RGB(0,0,0), черный цвет
TColor: Gray; //Значение RGB(128.128,128), серый цвет
TColor: LtBlue; //Значение RGB(0,0,255), синий цвет
TColor: LtCyan; //Значение RGB(0,255,255), бирюзовый цвет
TColor: LtGray; //Значение RGB (192,192,192), светло-серый цвет
TColor: LtGreen; //Значение RGB(0,255,255), зеленый цвет
TColor: LtMagenta; //Значение RGB(255,0,255), фиолетовый цвет
TColor: LtRed; //Значение RGB(255, 0, 0), красный цвет
TColor: LtYellow; //Значение RGB(255,255,0), желтый цвет
|
|
TColor: White; // Значение RGB(255,255,255), белый цвет
Данные-члены класса TColor можно использовать непосредственно в конструкторах перьев или кистей, как это сделано в нашей программе
TPen pen2(TColor::LtMagenta); //Создаем фиолетовое перо
или, если данный цвет используется многократно, можно создать именованный объект класса TColor, присвоив ему требуемое значение цвета, а затем использовать его в функциях, требующих указания цвета:
TColor color(TColor::LtMagenta); TPen pen2(color); TBrush brush(color);
При необходимости использовать в программе оставшиеся 10 цветов системной палитры Windows, их можно задать непосредственным указанием значений красного, синего и зеленого компонентов:
TColor darkBlue(0,0,128); //Создается объект, описывающий темно-синий цвет
TColor darkCyan(0,128,128); //Создается объект, описывающий темно-бирюзовый цвет
TColor lightCream(255,251,240); //Создается объект, описывающий светло-кремовый цвет
Предпочтительным является использование символических обозначений системных цветов, как это было сделано в примере 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(); //Рисуем далее исходным черным пером