Динамическое отображение диаграмм

Одна из возможных областей применения GDIPlus — это создание динамически изменяющихся диаграмм и графиков. Представьте себе такую ситуацию, когда диаграмма формируется одновременно с вводом или корректировкой данных в таблице, отображаемой в управляющем элементе Grid. Пользователь меняет данные и сразу видит результат в виде графика. В этом разделе мы рассмотрим один из способов решения этой задачи на примере формы с условным названием «Итоги по продажам».

Создайте таблицу с именем demotable. Записи этой таблицы должны содержать следующие поля:

  • monthname — поле типа Character длиной 9 символов, предназначено для хранения наименования месяца
  • year2005 — поле типа Integer, предназначено для хранения суммы продаж для каждого месяца 2005 года
  • year2006 — поле типа Integer, предназначено для хранения суммы продаж для каждого месяца 2006 года

Введите в таблицу двенадцать строк (по числу месяцев), в поле montname укажите наименования месяцев (январь, февраль и т.д.), а остальные поля заполните любыми положительными числами; ограничим максимальное значение суммы продаж в месяце числом 99999 (предположим, что это тысячи рублей).
Создайте программный файл с именем demochart.prg. Введите в него код из листинга 23.17.

Листинг 23.17. DemoChart.prg. Создание динамических диаграмм

 
 PUBLIC oDPI
 cPath = JUSTPATH(SYS(16))
 SET DEFAULT TO (cPath)
 SET CLASSLIB TO vfpgdiplus
 CLOSE TABLES ALL 
 * Открываем таблицу DEMOTABLE.DBF
 USE demotable IN 0
 * Создаём объект GdipImages и в нём - растр размером 510 на 265 пикселей
 oGPI = CREATEOBJECT("GdipImages")
 oGPI.CreateBitmap(510, 265, 0xFFEEFFEE)
 * Рисуем на растре "фон": наименования месяцев и координатные оси
 lnTop = 10                        && Начальное смещение по вертикали
 lnStep = 20                       && Приращение
 oGPI.CreateSolidBrush(0xFF000000) && Создаём чёрную кисть
 oGPI.CreateFont("Arial", 13, 0)   && Создаём шрифт высотой 13 пикселей
 SELECT demotable
 * Рисуем на растре наименования месяцев 
 * с отступом 5 пикселей от левого края
 SCAN 
    oGPI.DrawString(5, lnTop, 0, 0, ALLTRIM(demotable.monthname))
    lnTop = lnTop + lnStep
 ENDSCAN 
 * Создаём чёрное перо толщиной 1 пиксель ярко-синего цвета
 llStatus = oGPI.CreatePen(1, 0xFF0000FF)
 IF llStatus
 * Рисуем координатные оси, отступив от левого края растра 70 пикселей
 * (это место занято наименованиями месяцев)
    llStatus = oGPI.DrawLine(70,5,70,248)
    llStatus = oGPI.DrawLine(70,248,500,248)
 ENDIF
 IF llStatus 
 * Загружаем форму, на которой будем рисовать диаграмму
    DO FORM DemoChart
 ELSE 
 * Если имела место ошибка, то сообщаем её код
    = MESSAGEBOX("Ошибка" + STR(oGPI.GetStatus()))
 ENDIF 
 RELEASE oGPI
 

В этом коде создаётся растр, который будет использоваться в качестве «фонового рисунка» диаграммы, а в глобальной переменной oGPI запоминается ссылка на объект — экземпляр класса GdipImages.

Следующий шаг — создание формы DemoChart. Эта форма должна быть модальной. Установите размеры клиентской области формы равными 725 (ширина) на 275 (высота) пикселей. В принципе, вы можете выбрать любые другие размеры; в моём примере получилось так. Разместите на форме управляющий элемент Grid, расположенный вплотную у левой границы окна формы; установите его ширину равной 205 пикселям. На пять пикселей правее Grid’а будет располагаться область для вывода диаграммы. Свяжите Grid с таблицей demotable.dbf; запретите редактирование первой колонки (с именами месяцев).

Добавьте в форму свойство oGPW, которое будет использоваться для хранения ссылки на объект — экземпляр класса GdipWindow. Откройте на редактирование метод Init формы и введите в него следующий код:

 
 this.oGPW = CREATEOBJECT("GdipWindow", this)
 IF VARTYPE(this.oGPW) != "O"
    RETURN .F.
 ENDIF 
 

Как вы помните, класс GdipWindow предполагает наличие у формы метода ToDraw, который и выполняет рисование. Добавьте этот метод в форму. Его код приведён в листинге 23.18.

Листинг 23.18. Код метода ToDraw формы DemoChart

 
 LOCAL lnGraphics, llStatus, lnMax, lnValue, lnScale, ;
 lnTop, lnLeft, lnStep
 * Копируем таблицу в курсор и находим
 * максимальное значение для полей year2005 и year2006
 STORE 0 TO lnMax, lnValue, lnScale, lnTop, lnLeft, lnStep
 SELECT * FROM demotable INTO CURSOR t_demo
 GO TOP 
 SCAN 
    IF lnMax < t_demo.year2005
       lnMax = t_demo.year2005
    ENDIF 
    IF lnMax < t_demo.year2006
       lnMax = t_demo.year2006
    ENDIF
 ENDSCAN 
 * Ширина области для рисования гистограммы равна 420 пикселей
 lnRegion = 420
 lnScale = lnRegion / lnMax && Коэффициент масштабирования
 lnTop = 12                 && Начальное смещение от верхней границы области рисования
 lnStep = 20                && Шаг приращения
 * Левая граница гистограмм вычисляется так:
 * смещение до области вывода растра (210 пикселей) + смещение до оси
 * координат X на растре (70 пикселей - см. demochart.prg) + 1 пиксель
 lnLeftRegion = 281 && Левая граница гистограмм
 * Копируем в lnGraphics дескриптор объекта Graphics, связанного с окном
 lnGraphics = this.oGPW.GetGraphics()
 * Рисуем в окне растр; он используется как фоновый рисунок
 oGPI.DrawImage(this.oGPW.GetGraphics(), 210, 3)
 * Изменяем цвет пера на зелёный; перо было создано ранее в demochart.prg
 oGPI.SetPenColor(0xFF00AA00)
 * Рисуем столбцы гистограммы; каждый столбец шириной 11 пикселей
 GO TOP 
 SCAN 
    oGPI.SetColorSolidBrush(0xFF0099FF)       && Синяя кисть
    oGPI.FillRectangle(lnLeftRegion, lnTop, t_demo.year2005 * lnScale, 11, lnGraphics)
    oGPI.DrawRectangle(lnLeftRegion, lnTop, t_demo.year2005 * lnScale, 11, lnGraphics)
    oGPI.SetColorSolidBrush(0xCC55EE00)       && Зелёная чуть прозрачная кисть
    oGPI.FillRectangle(lnLeftRegion, lnTop+6, t_demo.year2006 * lnScale, 11, lnGraphics)
    oGPI.DrawRectangle(lnLeftRegion, lnTop+6, t_demo.year2006 * lnScale, 11, lnGraphics)
    lnTop = lnTop + lnStep
 ENDSCAN 
 * Рисуем вертикальные линии разметки. Будет выведено 10 вертикальных линий разметки
 lnStep = lnRegion / 10
 oGPI.SetPenColor(0x88AAAAAA) && Меняем цвет пера на светло-серый
 FOR lnLeft = lnLeftRegion+lnStep TO 10*(lnLeftRegion+lnStep) STEP lnStep
    oGPI.DrawLine(lnLeft, 10, lnLeft, 250, lnGraphics)
 ENDFOR 
 * Рисуем текст под разметкой (числовые значения)
 oGPI.SetColorSolidBrush(0xFF000000) && Меняем цвет кисти на чёрный
 * Создаём объект StringFormat и устанавливаем режим центрирования текста
 oGPI.CreateStringFormat()
 oGPI.SetStringFormatParameter(1)
 * Определяем инкремент для значений разметки
 lnMax = lnMax / 10
 * В цикле выводим значения разметки
 lnLeft = 255
 FOR i = 1 TO 11
    oGPI.DrawString(lnLeft, 253, 50, 16, LTRIM(STR(lnValue)), lnGraphics)
    lnLeft = lnLeft + lnStep
    lnValue = lnValue + lnMax
 ENDFOR
 

Сохраните форму и запустите файл demochart.prg на выполнение. Вы должны увидеть примерно следующее:

Рис.23.3. Вид формы DemoChart

Измените несколько значений полей в таблице. Гистограмма тот час же изменится.

Замечание
Управляющий элемент Grid при перемещении по его строкам постоянно посылает Windows многократные сообщения WM_ERASEBKGND, адресованные окну, в котором этот Grid отображается. Поэтому, с одной стороны, при перемещении по строкам таблицы гистограмма «мерцает» из-за того, что окно формы закрашивается фоновым цветом, а с другой стороны, не требуется вызывать метод ToDraw формы, например, из событий LostFocus текстовых боксов, потому что этот метод и так будет вызван из метода EventHandler объекта GdipWindow.