Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Разработка Windows - приложений в среде Borland. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
Затем в файл 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
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]