Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Разработка Windows - приложений в среде Borland. Учебное пособие
.pdf
Затем в файл diastat.cpp добавьте директивы препроцессора:
#include "workapp.h"
#include <values.h> // здесь определена константа MAXFLOAT
#include <stdio.h> // нам понадобится функция sprintf
Далее в этом же файле (diastat.cpp) определите несколько
вспомогательных функций. Функцию подсчета количества сотрудников, занимающих определенную должность (если второй параметр не задан, будет подсчитано общее число сотрудников):
int CountJob(staff* s, char *job=""){
int count=0;
for(int i=0;i<iMax;i++)
if( ! strcmp(s[i].job,job) || job[0]=='\0') count++;
return count;
}
функцию подсчета оклада сотрудников
должность:
float SumPay(staff* s, char *job=""){
float sum=0;
for(int i=0;i<iMax;i++)
if( ! strcmp(s[i].job,job) || job[0]=='\0') sum+=s[i].pay;
return sum;
}
функцию подсчета максимального оклада сотрудников, занимающих определенную должность:
float MaxPay(staff* s, char *job=""){
float max=0;
for(int i=0;i<iMax;i++)
if( ! strcmp(s[i].job,job) || job[0]=='\0')
if(s[i].pay>max) max=s[i].pay;
return max;
}
111
, занимающих определенную

и функцию подсчета минимального оклада сотрудников, занимающих определенную должность:
float MinPay(staff* s, char *job=""){
float min=MAXFLOAT;
for(int i=0;i<iMax;i++)
if( ! strcmp(s[i].job,job) || job[0]=='\0')
if(s[i].pay<min) min=s[i].pay;
return (min<MAXFLOAT)? min:0;
}
Затем добавим функцию, формирующую строку, содержащую
необходимые статистические данные. Под каждое значение отведем
восемь символов:
char buff[256]; // массив buff – глобальный для мо-
дуля diastat.cpp
char* ShowStatistics(char* job=""){
float sum = SumPay(crew,job), average;
int iCount = CountJob(crew, job);
average = (iCount>0) ? sum/iCount : 0;
sprintf( buff, "%8.0f%8.0f%8.0f%8.0f%8i",
sum, MaxPay(crew,job), MinPay(crew,job), average, iCount);
return buff;
}
Осталось решить, где поместить код, выводящий в окно стати-
стические данные.
стандартные элементы управления, например, в надпись или редактируемое поле, можно перегрузить виртуальную функцию Setup-
Window нашего нового класса TDiaStat. Однако если потребуется
работа с графикой, в частности, если для вывода данных применяются функции контекста устройства TextOut или DrawText, такое
решение будет неудачным – после
ционной системой все «нарисованное» пропадет. Когда возникает
необходимость перерисовать окно, например, при сворачивании окна или изменении его размеров, Windows посылает сообщение
WM_PAINT. В ответ на это сообщение будет вызвана функция
Если ограничиться только выводом результатов в
перерисовки окна диалога опера-
112

EvPaint. Вот в нее мы и поместим наши инструкции. Это тем более
оправдано, что в дальнейшем будут добавлены графики. Если бы мы
пометили операции рисования в код какой-нибудь кнопки, то все
диаграммы стирались бы после, например, перекрытия нашего диалога другим окном. Итак, выберите в окне ClassExpert наш класс
TDiaStat, в
Windows (Windows Messages), раскройте список основных сообще-
ний (Basic Messages):
выберите событие WM_PAINT и перегрузите функцию-обработчик
этого события – контекстное меню → Add handler:
панели событий (Events) раскройте список сообщений
В этом случае также не потребуется вводить имя функции – оно
предопределено. В нижней панели ClassExpert появится заготовка
нужной функции:
void TDiaStat::EvPaint()
{
TDialog::EvPaint();
// INSERT>> Your code here.
}
Добавьте после комментария “// INSERT>> Your code here” ин-
струкции:
TClientDC dc(*this);
113

wsprintf(buff,"Всего записей - %i",iMax);
SetCaption(buff); // меняем заголовок
// выводим статистические данные:
StatTotal -> SetText(ShowStatistics());
StatGuard -> SetText(ShowStatistics("сторож"));
StatSan -> SetText(ShowStatistics("сантехник"));
StatClean -> SetText(ShowStatistics("дворник"));
Весь графический вывод осуществляется методами контекста
устройства. В контексте устройства определены функции выбора
инструментов рисования, таких как перо, кисть шрифт и т.д., функции TextOut или DrawText, предназначенные для вывода текста и
собственно функции рисования графических примитивов
прежде всего, создается контекст устройства dc клиентской области
нашего нового диалогового окна. Клиентская область – это часть
окна не включающая его заголовок. В качестве параметра конструктору класса TClientDC передается объект «окно» – у нас экземпляр
класса TDiaStat. Если ограничиться заданием нужного шрифта в
редакторе ресурсов через окно свойств диалога, о
ниже, то контекст устройства не понадобится.
Еще раз стоит отметить, что данный код можно было поместить
и в функцию SetupWindow класса TDiaStat. Запустим программу,
зачислим несколько сотрудников на должность «сторож» и нажмем
кнопку «Статистика»:
. Поэтому,
чем пойдет речь
114

Неприятный сюрприз! Оказалось, что реальная ширина поля
вывода численных результатов зависит от значения чисел – колонки
с нулями сдвинуты относительно первых двух строк. Это связано с
тем, что по умолчанию для окна диалога выбран шрифт “MS Sans
Serif”. Данный шрифт относится к так называемым пропорциональным шрифтам, у которых ширина символа зависит от
частности, пробел «уже», чем цифра. Решение проблемы просто:
надо выбрать другой шрифт, с равной шириной всех символов. Вызовите в редакторе ресурсов окно свойств самого диалога IDD_-
STATISTICS и на вкладке “Font” шрифт “Courier New Cyr”:
его формы. В
Теперь данные будут отображаться в нужных колонках:
115

Раз уж нам пришлось затронуть вопрос выбора шрифта, рас-
смотрим еще один способ вывода данных в окно с помощью функции контекста устройства TextOut, а заодно и способ задания нового шрифта. Чтобы надписи “StatTotal”, “StatGuard”, “StatSan” и
“StatClean” не перекрывали графический вывод, сделайте их невидимыми – вызовите окно свойств и снимите флажок “Visible”.
До-
бавьте в EvPaint после строчки:
SetCaption(buff);
инструкции:
LOGFONT lf;
memset(&lf,0,sizeof(lf));
lf.lfHeight=16;
lf.lfItalic=1;
strcpy(lf.lfFaceName,"Courier New CYR");
TFont tf(&lf);
dc.SelectObject(tf);
dc.SetTextColor(TColor::LtRed);
dc.SetBkColor(TColor::Sys3dFace);
dc.TextOut(StatTotal->Attr.X,StatTotal->Attr.Y,ShowStatistics());
dc.TextOut(StatGuard->Attr.X,StatGuard->Attr.Y,ShowStatistics
("сторож"));
dc.TextOut(StatSan->Attr.X,
StatSan->Attr.Y,ShowStatistics("сантехник "));
dc.TextOut(StatClean->Attr.X,
StatClean->Attr.Y,ShowStatistics("дворник "));
и закомментируйте (пока не удаляйте) строчки:
StatTotal -> SetText(ShowStatistics());
StatGuard -> SetText(ShowStatistics("сторож"));
StatSan -> SetText(ShowStatistics("сантехник"));
StatClean -> SetText(ShowStatistics("дворник"));
Рассмотрим добавленный нами код. Для создания шрифта с
требуемыми характеристиками вначале мы создали и заполнили
структурную переменную типа LOGFONT. В ней содержится четырнадцать полей, определяющих параметры шрифта. Однако мы не
116

обязаны задавать значения всех полей. Нам нужен непропорциональный шрифт “Courier New CYR” с высотой символов 16 единиц.
Обратите внимание: размер символа задается в единицах, отличных
от тех, которые использовались в окне свойств диалога. Дополнительно задан курсив – поле lfItalic. Остальные поля мы просто обнулили с помощью функции memset. Далее, с помощью конструктора
класса TFont
устройства SelectObject позволяет выбрать нужный инструмент рисования: перо, кисть, шрифт и т.д. Затем задаем красный цвет символов (по умолчанию символы черные) и с помощью функции
SetBkColor устанавливаем фон при рисовании символов. Используем константы для обозначения цвета, определенные в классе TColor
Цвет Sys3dFace определит цвет фона, совпадающий с цветом окна
диалога. Метод контекста устройства TextOut выводит текст в точку
(х, у), координаты которой указываются в качестве первого и второго параметров. Сам текст – это символьная строка, которая задается
третьим параметром. Например, инструкция:
dc.TextOut(92,42, "Привет");
требует вывести слово «Привет» в точку с координатами х
Вместо подбора нужных координат в приведенном выше фраг-
менте кода используются координаты левого верхнего угла (Attr.X
и Attr.Y) созданных нами невидимых надписей (раз уж они есть!).
Запустите программу и вызовите окно статистики. Теперь статистические данные выводятся курсивом красного цвета.
Выберите из рассмотренных двух вариантов вывода данных тот,
который
Ненужный код удалите или закомментируйте. Если выбрали первый
вариант, не забудьте сделать надписи “StatTotal”, “StatGuard”,
“StatSan” и “StatClean” видимыми! Графики расположите в нижней
половине окна, поэтому растяните окно диалога IDD_STATISTICS
примерно вдвое по вертикали. Мы хотим предоставить пользователю возможность выбора типа диаграммы. Для этого поместите
в нижней половине окна группу переключателей:
создан нужный шрифт – объект tf. Метод контекста
.
= 92, у = 42.
более понятен вам, и затем перейдите к выводу графиков.
слева
117

Напомню, для того чтобы переключатели функционировали как
единая группа, то есть мог быть выбран только один переключатель
в рамке, необходимо в свойствах первого переключателя поставить
флажок Group Flag и проверить, чтобы у второго переключателя
свойство Group Flag было отключено. Кроме того, нужно проверить
порядок создания элементов (подробнее смотрите раздел второй
главы «Алгоритмы. Сортировка и поиск
pert имена переключателям «Круговая» и «Гистограмма» - Radio-
Circular и RadioBar, соответственно. Далее перегрузите виртуальную функцию SetupWindow диалога IDD_STATISTICS (класса
TDiaStat) и добавьте в нее инструкции, обеспечивающие выбор кру-
говой диаграммы при появлении диалогового окна статистики:
RadioCircular->Check();
RadioBar->Uncheck();
или проверьте названия идентификаторов переключателей в проекте:
CheckRadioButton (IDC_RADIOBUTTON1,
IDC_RADIOBUTTON2, IDC_RADIOBUTTON1);
В функции CheckRadioButton первый и второй
ют диапазон изменения идентификаторов переключателей в группе.
Третий параметр задает идентификатор выбранного переключателя.
Для того чтобы при выборе типа диаграммы график перерисо-
вался, создайте функцию обработки выбора переключателя: выберите первый переключатель → ClassExpert → событие BN_CLICKED
→ Add handler → введите имя метода BNClickedCircular и в созданную ClassExpert заготовку функции добавьте строчку
Invalidate();
Аналогично, для второго переключателя создайте обработчик
BNClickedBar и введите такую же инструкцию.
Отредактируйте текст модуля TDiaStat.h – добавьте в класс
TDiaStat объявление функции, которая и будет рисовать диаграмму
нужного типа:
»). Присвойте через ClassEx-
параметры зада-
.
118

void ShowDiagram(TDC& dc,TBrush& brush,TRect r,TPoint&
pointB,
TPoint& pointLT,float sum,float angle,char* job);
и в конец модуля TDiaStat.cpp определение этой функции:
void TDiaStat::ShowDiagram (TDC& dc,TBrush& brush,TRect r,
TPoint& pointB,TPoint& pointLT,float sum,float
angle,char* job){
TPoint pointT;
int radius=(r.Width()>r.Height()) ? r.Height()/2 : r.Width()/2;
TPoint pCenter(r.Width()/2, r.top+r.Height()/2);
dc.SelectObject(brush);
if(RadioCircular->GetCheck()==BF_CHECKED){
dc.BeginPath();
dc.MoveTo(pCenter);
dc.AngleArc( pCenter, radius, angle, 360*sum);
dc.LineTo(pCenter);
dc.CloseFigure();
dc.EndPath();
dc.StrokeAndFillPath();
}
else{
pointB. Offset(20,0); pointT=pointB. OffsetBy (-20,-sum*
r. Height());
dc.Rectangle(pointT, pointB);
}
TPoint pointRB;
pointLT.Offset(0,40),pointRB=pointLT.OffsetBy(20,20);
dc.Rectangle(pointLT,pointRB);
dc.TextOut(pointLT.OffsetBy(30,0),job);
}
Разберем алгоритмы, реализованные в теле этой функции. Прежде всего рассмотрим параметры метода. Их, конечно, многовато.
Можно было бы добавить соответствующие объекты в класс, но тогда пришлось бы больше редактировать и
будет вызываться из метода EvPaint, поэтому для простоты изложения все параметры задаются там. Итак, первый параметр dc это кон-
119
объяснять. Данная функция

текст устройства клиентской области окна. Второй параметр brush
задает кисть, которой закрашиваются «объемные» рисунки – прямоугольники, сектора окружности и т.п. Объект r класса TRect определяет прямоугольную область рисования. Параметр pointB задает
координаты нижнего правого угла прямоугольника при построении
гистограмм – выбран нижний угол, поскольку вертикальная координата нижнего угла постоянна
деляет координаты верхнего правого угла прямоугольника при выводе легенды диаграммы. Число sum – это приведенная (деленная на
общую) сумма зарплат по должности. Параметр angle задает начальное значение угла при построении круговой диаграммы. Наконец последний параметр – job определяет должность.
Прежде всего, зададим возможный радиус
диаграммы как минимальное значение из полуширины и полувысоты заданной области рисования. Затем находим координаты центра
(pCenter) этой области и выбираем кисть с помощью метода контекста устройства SelectObject.
Если требуется построить сектор круговой диаграммы (выбран
переключатель RadioCircular), то сначала строим контур сектора –
начало построения BeginPath, окончание – EndPath
задает (без рисования) текущую точку. Метод AngleArc строит дугу.
Первый параметр метода определяет центр окружности, второй параметр – радиус, третий – начальный угол (в градусах) и четвертый
параметр – приращение угла. Дуга рисуется против часовой стрелки! С помощью метода LineTo проводится прямая линия от текущей
точки, до точки заданной
Figure обеспечивает замыкание нарисованного контура. И напоследок метод StrokeAndFillPath очерчивает границы контура выбранным пером (которое выбирается в вызывающей функции EvPaint) и
заливает построенную фигуру выбранной кистью.
Алгоритм построения линейной гистограммы не сложен. В этом
случае считается, что параметр pointB задает координаты нижнего
правого угла
ность). Поэтому сначала устанавливаем нижний угол нового прямоугольника, сдвигая координаты нижнего угла на 20 пикселей вправо
– Offset (20,0), а затем определяем координаты левого верхнего угла
прямоугольника – точку pointT. Метод OffsetBy класса TPoint не
меняет координаты точки pointB в отличие от метода Offset, а возвращает нужное
ранее построенного прямоугольника (другая долж-
значение. Метод контекста устройства Rectangle
. Объект pointLT класса TPoint опре-
(radius) круговой
. Метод MoveTo
параметром метода. Вызов метода Close-
120
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
