Применение GDI+ в отчётах
В девятой версии Visual FoxPro система отчётов полностью
переработана. Теперь во время выполнения отчёт можно «связать» с объектом
ReportListener. Этот объект не только привносит в отчёт элементы
объектно-ориентированного подхода; он позволяет динамически управлять
отчётом. В принципе, при использовании этого объекта вы можете получить
отчёт, совершенно не похожий на тот, который был создан в Конструкторе
отчётов. События, методы и свойства объекта ReportListenerИз достаточно большого количества событий, методов и
свойств объекта ReportListener мы рассмотрим только тот минимальный набор,
который необходим для использования этого объекта совместно с GDIPlus.
Таблица 23.2. Значения параметра BandObjCode
Событие EvaluateContents возникает для каждого
объекта отчёта (типа полей таблицы, выводимой в отчёт). Так, если вы
выводите в отчёт по десять полей в строке, то на одно событие BeforeBand
произойдёт десять событий EvaluateContents.
Таблица 23.3. Свойства объекта ObjProperties
Метод Render выполняет построчное рисование элементов отчёта. Вызывается для каждого поля рисуемой строки. Метод получает большое количество параметров, в том числе номер записи в FRX файле, координаты левого верхнего угла, ширины и высоты области рисования поля и некоторые другие. Если вы дадите в этом методе команду NODEFAULT, то в отчёте ничего нарисовано не будет. При вводе своего кода в этот метод не забудьте вызвать функцию DODEFAULT() для выполнения кода класса-родителя. Методы GetPageWidth и GetPageHeight возвращают размеры страницы отчёта (в точках). Документ отчёта имеет разрешение 960 точек на дюйм, или около сорока точек в миллиметре. Методы не имеют параметров. Свойство GdiPlusGraphics содержит дескриптор объекта Graphics отчёта. Свойство ListenerType определяет, каким образом ReportListener управляет выводом отчёта. Может принимать значения от -1 до 5. Подробности вы можете найти в справочной документации. Создание отчётаЗапустите Мастер отчётов. В окне Wizard Selection
выберите Report Wizard. На первом шаге мастера выберите таблицу
demotable (если вы ещё не создали эту таблицу, то сделайте это сейчас,
воспользовавшись рекомендациями из раздела «Динамическое отображение
диаграмм» в этой главе). Включите в отчёт все поля таблицы. На втором шаге
всё оставьте без изменений, на третьем шаге выберите стиль Ledger.
Завершите выполнение мастера и сохраните отчёт в файле demoreport.frx. Рис. 23.4. Редактирование отчёта demoreport в Конструкторе отчётов Для чего нужна полоса Summary? Получив сообщение о начале этой полосы, мы будем рисовать нашу гистограмму. Почему? Потому что эта полоса обрабатывается сразу после завершения полосы Detail. Конечно, мы все привыкли, что полоса Summary выводится после полосы Detail. Но это совсем не обязательно! Работая с GDIPlus, мы рисуем на листе отчёта, и поэтому можем разместить диаграмму в любом месте (но в пределах листа, конечно)! В методе BeforeBand объекта ReportListener мы всегда имеем возможность перехватить и обработать событие, извещающее нас о том, что пришло время печатать полосу Summary — и приступить к рисованию графика или ещё чего-нибудь. Процедура запуска отчётаВо-первых, мы должны связать отчёт с объектом
ReportListener. На самом деле мы свяжем отчёт с объектом, созданным на базе
нашего собственного класса, который является потомком класса ReportListener.
#DEFINE DETAIL_BAND 4 #DEFINE SUMMARY_BAND 8 PUBLIC oGP, oReportListener cPath = JUSTPATH(SYS(16)) SET DEFAULT TO (cPath) SET CLASSLIB TO vfpgdiplus * Создаём объект GdipImages oGP = CREATEOBJECT("gdipimages") * Создаём объект ReportListener из нашего класса GdipDemoReport oReportListener = CREATEOBJECT("GdipReport") oReportListener.ListenerType = 1 REPORT FORM demoreport PREVIEW OBJECT oReportListener RELEASE oGP, oReportListener Отчёт будет выводиться в окно предварительного просмотра. Класс GdipDemoReportЕсли вы хотите управлять отчётом при помощи объекта
ReportListener, то, как правило, вам придётся создавать специализированные
классы, наследуемые от класса ReportListener, и «заточенные» под конкретный
отчёт. Попытка создать универсальный класс «на все случаи» вряд ли
увенчается успехом. Так, в нашем случае мы будем рисовать гистограмму (подобную
показанной на рис. 23.3). В другом отчёте вы захотите выводить строки
заголовка вертикально или под углом, в третьем отчёте… Ну мало ли чего вы
ещё захотите! DEFINE CLASS GdipReport As ReportListener Свойства класса GdipDemoReportВот описание необходимых свойств: lDetail = .f. && Флаг, определяющий, что обрабатывается полоса Detail nLines = 0 && Счётчик количества строк в полосе Detail nColumns = 0 && Счётчик количества объектов в строке DIMENSION aDates[12,3] && Массив, в который копируются значения полей Метод BeforeBandЭтот метод будет определять тип обрабатываемой полосы отчёта. Если это Detail, то устанавливается в «истину» свойство lDetail. Если это Summary, то вызывается метод ToDraw для рисования гистограммы. Для всех остальных полос свойство lDetail устанавливается в «ложь».
PROCEDURE BeforeBand(tnBandObjCode, tnFRXRecno) DO CASE CASE tnBandObjCode = DETAIL_BAND && Если это полоса Detail this.nLines = this.nLines + 1 && Инкремент счётчика строк this.nColumns = 0 && Сброс счётчика полей строки this.lDetail = .t. CASE tnBandObjCode = SUMMARY_BAND && Если это полоса Summary this.lDetail = .f. this.ToDraw() OTHERWISE && Все остальные полосы this.lDetail = .f. ENDCASE ENDPROC Такое построение метода позволяет вносить изменения в полосу Detail, но никак не влияет на вывод в отчёт содержимого других полос. Метод EvaluateContentsВ методе проверяется значение свойства lDetail, то есть относятся ли полученные данные к полосе Detail. Если да, то вычисляется номер поля; его значение заносится в массив aDates.
PROCEDURE EvaluateContents(tnFRXRecno, toProps) LOCAL lcValue IF this.lDetail this.nColumns = this.nColumns + 1 && Инкремент счётчика полей DO CASE CASE VARTYPE(toProps.value) = "C" this.aDates[this.nLines,this.nColumns] = toProps.text CASE VARTYPE(toProps.value) = "N" this.aDates[this.nLines,this.nColumns] = toProps.value ENDCASE ENDIF ENDPROC Метод RenderНапомню, что именно этот метод «рисует» отчёт. В нашем классе он
используется для вычисления границы области рисования гистограммы.
PROCEDURE Render(nFRXRecNo, nLeft, nTop, nWidth, nHeight, ; nObjectContinuationType, cContentsToBeRendered, GDIPlusImage) DODEFAULT(nFRXRecNo, nLeft, nTop, nWidth, nHeight, nObjectContinuationType, ; cContentsToBeRendered, GDIPlusImage) IF this.lDetail && Если полоса - Detail IF !this.lStart && и если это первое поле первой строки this.nTop = nTop && то запоминаем его верхнюю границу this.lStart = .t. ENDIF * Далее определяем значение координаты по X границы печатаемой таблицы IF this.nLeft < nLeft + nWidth this.nLeft = nLeft + nWidth ENDIF ENDIF ENDPROC Как и все предыдущие методы, этот метод автоматически вызывается для каждого объекта отчёта. Помимо прочего, в метод передаются координаты и размеры областей для рисования объекта. Метод ToDrawА это уже наш, пользовательский, метод. Он вызывается из метода BeforeBand, когда возникает событие начала печати полосы Summary. В этом методе мы должны вычислить координаты всех столбцов гистограммы и нарисовать её.
PROCEDURE ToDraw LOCAL i, lnRow, lnCol, lnMax, lnPageWidth lnRow = ALEN(this.aDates,1) lnCol = ALEN(this.aDates,2) lnMax = 0 * Определение максимального значения FOR i = 1 TO lnRow IF lnMax < this.aDates[i,2] lnMax = this.aDates[i,2] ENDIF IF lnMax < this.aDates[i,3] lnMax = this.aDates[i,3] ENDIF ENDFOR * Метод GetPageWidth возвращает значение ширины страницы в точках * (для отчёта используется DPI 960 точек в дюйме) * Здесь мы отнимаем от полученного значения 500 точек; это будет * отступ от правой границы страницы lnPageWidth = this.GetPageWidth() – 500 lnDirect = lnPageWidth - this.nLeft && Ширина области для рисования * Левая граница области для рисования должна располагаться * правее правого края таблицы. Устанавливаем сдвиг в 100 точек (2,5 мм) lnLeft = this.nLeft + 100 && Левая граница области * Определение нижней границы области рисования. Высота области * принимается равной 2850 точкам. В принципе, её можно увязать * с размерами напечатанной таблицы, выполнив необходимые вычисления * в методе Render lnBottom = this.nTop + 2850 * Рисуем координатные оси lnGraphics = this.GdiPlusGraphics oGP.CreatePen(10) oGP.DrawLine(lnLeft, this.nTop - 100, lnLeft, lnBottom, lnGraphics) oGP.DrawLine(lnLeft, lnBottom, lnLeft + lnDirect + 200, lnBottom, lnGraphics) lnStep = 2850 / lnRow && Шаг для размещения 12 столбцов гистограммы lnWidthRect = 0.6 * lnStep && Толщина столбца lnScale = lnDirect / lnMax && Масштабный коэффициент oGP.CreateSolidBrush(0xFF0088EE) && Создаём кисть (любого цвета) lnTop = this.nTop oGP.SetPenColor(0xFF00AA00) && Делаем перо зелёным FOR i = 1 TO lnRow oGP.SetColorSolidBrush(0xFF0088EE) && Делаем кисть синей oGP.FillRectangle(lnLeft, lnTop, this.aDates[i,2] * lnScale, lnWidthRect, lnGraphics) oGP.SetColorSolidBrush(0xAAAAFF00) && Делаем кисть зелёной и чуть прозрачной oGP.FillRectangle(lnLeft, lnTop + 0.5 * lnWidthRect, this.aDates[i,3] * lnScale, ; lnWidthRect, lnGraphics) oGP.DrawRectangle(lnLeft, lnTop + 0.5 * lnWidthRect, this.aDates[i,3] * lnScale, ; lnWidthRect, lnGraphics) lnTop = lnTop + lnStep ENDFOR oGP.SetPenColor(0xFFBBBBBB) && Делаем перо светло-серым * Рисуем вертикальные линии разметки lnStep = lnDirect / 10 FOR i = lnLeft + lnStep TO lnDirect + lnLeft STEP lnStep oGP.DrawLine(i, this.nTop - 100, i, lnBottom, lnGraphics) ENDFOR * Подготовка к рисованию текста oGP.CreateStringFormat() && Создаём объект StringFormat oGP.SetStringFormatParameter(1) && Режим центрирования строки oGP.CreateFont("Arial", 120) && Ihban Arial высотой 4,9 мм oGP.SetColorSolidBrush(0xFF000000) && Делаем кисть чёрной lnLeft = lnLeft – 300 lnValue = 0 * Рисуем значения разметки FOR i = 1 TO 11 oGP.DrawString(lnLeft, lnBottom + 50, 600, 120, LTRIM(STR(lnValue)), lnGraphics) lnLeft = lnLeft + lnStep lnValue = lnValue + lnMax / 10 ENDFOR ENDPROC Для упрощения кода в отчёте не выводятся наименования
месяцев; вместо этого столбцы гистограммы располагаются напротив строк
таблицы. ENDDEFINE Запустите файл demoreport.prg на выполнение. Появится окно просмотра отчёта, в котором вы увидите примерно то, что показано на рис. 23.5. Рис. 23.5. Окно Report Preview с загруженным отчётом demoreport |