Министерство образования РФ

Шадринский Государственный педагогический институт

 

 

 

 

 

 

Слинкин Д.А.

 

 

 

Программирование.

 

ЧАСТЬ 2

 

Методы программирования на турбо-паскале

 

 

 

 

Учебное пособие

для студентов Вузов

 

 

 

 

 

 

 

Шадринск 2000


 

681.3.066.3

C47

 

 

Слинкин Д.А.

Программирование. Часть 2. Методы программирования на Турбо-Паскале: Учебное пособие для студентов вузов. Шадринск: Шадринский пединститут 2000. – 140 с.

 

 

Рекомендовано к печати кафедрой теории и методики информатики Шадринского пединститута, протокол №5 от 30.03.2000.

Вторая часть учебного пособия посвящена как более сложным элементам языка Турбо-Паскаль 7.0, так и основным методам, используемым при создании больших программных систем. Рассматриваются также методы отладки программ. Каждая тема иллюстрируется примерами и набором задач для самостоятельного решения. На всем протяжении изложения реализуется большой программный проект – векторный графический редактор (GRED).

Пособие предназначено для студентов физико-математических факультетов и факультетов информатики, может быть использовано в качестве методического пособия для учителей информатики старших классов средней школы, в классах с углубленным изучением информатики, а также для всех, кто в своей деятельности сталкивается с программированием персональных компьютеров.

 

 

Рецензенты:         канд. физ.-мат. наук Пирогов В.Ю.

канд. физ.-мат. наук Долженко И.В.

 

 

 

 

ISBN 5-87818-219-X

 

 

© Шадринский пединститут 2000

© Слинкин Д.А. 2000


 

Оглавление

Оглавление                                                                                                           3

Введение                                                                                                                6

Глава 2.          Методы программирования  на Турбо-Паскале                6

Программный проект (фаза 0)                                                                                                                            6

2.1            Графика.                                                                                                                                               10

2.1.1          Графические режимы.                                                                                                                 10

2.1.1.1            Характеристики графических режимов                                                                          10

2.1.1.2            Видеоадаптеры EGA и VGA                                                                                                11

2.1.1.3            Этапы работы в графическом режиме                                                                           12

2.1.2          Программирование в графическом режиме                                                                          13

2.1.2.1            Инициализация и завершение работы  с графикой, видеоадаптер и видеорежимы                                     13

Задания для раздела  "Инициализация и завершение работы  с графикой, видеоадаптер и видеорежимы" (1-4).       16

2.1.2.2            Анализ ошибок                                                                                                                     16

Задания для раздела "Анализ ошибок" (5-6).                                                                                        17

2.1.2.3            Графический указатель                                                                                                       17

Задания для раздела "Графический указатель" (7-9).                                                                         18

2.1.2.4            Графические инструменты                                                                                                18

2.1.2.4.1         Свойства карандаша                                                                                                        19

Задания для раздела "Свойства карандаша" (10-14).                                                                       21

Программный проект (1)                                                                                                                      21

2.1.2.4.2         Свойства кисти                                                                                                                  22

Задания для раздела "Свойства кисти" (15-18).                                                                                 23

Программный проект (2)                                                                                                                      23

2.1.2.5            Фигуры                                                                                                                                    24

2.1.2.5.1         Точки, линии, многоугольники                                                                                    24

Задания для раздела "Точки, линии, многоугольники" (19-23).                                                   26

Программный проект (3)                                                                                                                      26

2.1.2.5.2         Дуги, окружности, эллипсы                                                                                           27

Задания для раздела "Дуги, окружности, эллипсы" (24-27).                                                         29

Программный проект (4)                                                                                                                      30

2.1.2.5.3         Заполнения                                                                                                                        30

Задания для раздела "Заполнения" (28-32).                                                                                       32

2.1.2.6            Вывод текста                                                                                                                          33

Задания для раздела "Вывод текста" (33-37).                                                                                         37

Программный проект (5)                                                                                                                           37

2.1.2.7            Сохранение и выдача изображений                                                                                 37

Задания для раздела  "Сохранение и выдача изображений" (38-40).                                               38

Программный проект (6)                                                                                                                      39

2.1.2.8            Холст                                                                                                                                        39

Задания для раздела "Холст" (41-44).                                                                                                      41

2.1.2.9            Палитры                                                                                                                                  42

Задания для раздела "Палитры" (45-48).                                                                                                 44

2.1.2.10          Регистрация нестандартных графических драйверов и шрифтов.                            44

2.1.2.11          Инкапсуляция файлов графических  драйверов и шрифтов в исполняемый файл                       46

2.2            Объектно-ориентированное программирование (ООП)                                                          48

2.2.1          Основные парадигмы ООП.                                                                                                        48

2.2.2          Реализация ООП средствами Турбо-Паскаля.                                                                        49

2.2.2.1            Класс Турбо-Паскаля                                                                                                           49

Программный проект (7)                                                                                                                           57

Задания для программного проекта (49-59).                                                                                         65

2.2.2.2            Динамические объекты.                                                                                                     66

2.2.2.3            Модуль OBJECTS.                                                                                                                 69

2.2.2.3.1         Потоки                                                                                                                                 70

2.2.2.3.1.1         Хранение данных в потоках                                                                                    73

Задания для раздела "Хранение данных в потоках" (60-63)                                                       74

2.2.2.3.1.2         Хранение объектов в потоках.                                                                               75

Программный проект (8)                                                                                                                      76

Задания для программного проекта (64-68)                                                                                      78

2.2.2.3.2         Коллекции                                                                                                                          78

Программный проект (9)                                                                                                                      81

Задания для программного проекта (69-72)                                                                                      82

2.2.2.3.3         Ресурсы                                                                                                                              83

Задания для раздела "Ресурсы" (73-75)                                                                                              85

2.3            Отладка программ.                                                                                                                           87

2.3.1          Виды ошибок                                                                                                                                  87

Задания для раздела "Виды ошибок"  (76-78)                                                                                            89

2.3.2          Констатация и локализация ошибок                                                                                          90

2.3.3          Использование встроенного отладчика.                                                                                   91

2.4            Разработка больших программ.                                                                                                      93

2.4.1          Общие принципы разработки  программ.                                                                               93

2.4.1.1            Метод организации «сверху-вниз».                                                                                 93

2.4.1.2            Метод организации «снизу-вверх».                                                                                 93

2.4.1.3            Достоинства и недостатки обоих  методов:                                                                    94

2.4.2          Концепции разработки больших программных проектов (БПП).                                      96

2.4.2.1            Руководство программным проектом и коллектив программистов.                      96

2.4.2.2            Концептуальное единство проекта.                                                                                  98

2.4.2.3            Ошибки при реализации проекта.                                                                                    98

Задания для раздела "Концепции разработки больших программных проектов (БПП)." (79-80) 99

2.4.3          Событийная модель программного проекта.                                                                         99

2.4.3.1            Понятие события при разработке больших программных проектов.                      99

2.4.3.2            Реализация механизма получения и  обработки событий в однозадачной среде.                        100

2.4.3.3            Реализация механизма получения и  обработки событий в многозадачной среде.                      101

2.4.3.4            Пример реализации получения и обработки событий в однозадачной среде.   103

Программный проект (10)                                                                                                                       107

2.4.4          Объектно-событийная модель программы.                                                                          108

2.4.4.1            Объектная модель программы                                                                                        108

2.4.4.2            Объединение объектной и событийной модели программ.                                    109

Программный проект (11)                                                                                                                       112

2.4.4.3            Режим работы объекта в объектно-событийной модели                                         120

Программный проект (12)                                                                                                                       121

Задания для программного проекта (81-91)                                                                                        132

2.4.5          Особенности отладки больших программных проектов (БПП), основанных на объектно-событийной модели.           133

2.4.5.1            Синтаксические ошибки в БПП.                                                                                      133

2.4.5.2            Семантические ошибки в БПП.                                                                                       134

2.4.5.2.1         Предупреждение семантических ошибок в БПП.                                                   134

2.4.5.2.2         Констатация семантических ошибок в БПП.                                                           134

2.4.5.2.3         Локализация семантических ошибок в БПП.                                                           135

2.4.5.2.4         Методы локализации семантических ошибок в БПП.                                           136

Задания для раздела "Методы локализации семантических ошибок в БПП." (92-93)           139

Список литературы.                                                                                   140

 

 


 

Введение

Продолжая серию учебных пособий "Программирование", мы предлагаем Вам приступить к изучению более сложных элементов языка программирования Турбо-Паскаль, относящимся к синтаксису и семантике языка (ООП – объектно-ориентированное программирование), к возможностям специализированных библиотек (графика), а также к общим принципам и положениям, без знания которых невозможно создание серьезных программных приложений (отладка и разработка больших программ). Методы программирования, излагаемые в учебном пособии, активно используются в настоящее время в профессиональном программировании.


 

Глава 2. Методы программирования
на Турбо-Паскале

Программный проект (фаза 0)

К данному моменту предполагается, что Вам известны синтаксис и семантика языка программирования Турбо-Паскаль, изложенные в первой части учебного пособия. Поэтому для более серьезной иллюстрации изучаемого материала, вместо решения небольших задач (которых, в отличии от первой части учебного пособия, всего около 10) мы воспользуемся методом, широко распространенным в обучении профессиональному программированию, а именно – созданием большого программного проекта (векторного графического редактора GRED), рассчитанного на один семестр и охватывающего в той или иной степени все изучаемые в пособии темы.

Ниже приводятся краткое описание и возможности редактора:

1.       Графический редактор позволяет манипулировать простейшими графическими примитивами, как то – отрезками, окружностями, прямоугольниками, эллипсами, многоугольниками, заливками различных видов и т.д. Под манипуляциями понимаются интерактивное создание, удаление, изменение местоположения, размеров и других атрибутов графических примитивов.

2.       Редактор позволяет хранить (запись и чтение) на диске полученный набор примитивов в специализированном формате, а также сохранять в текстовом файле набор вызовов графических процедур и функций. Таким образом, подготовленный в редакторе рисунок можно затем использовать в любой программе на Паскале.

3.       Редактор имеет достаточный для выполнения своих функций набор элементов управления – зависимые и независимые кнопки, поля для выбора цвета и других атрибутов графических примитивов, поля ввода и вывода текста, средства скроллинга, меню и т.д.

4.       Основное средство управления – манипулятор "мышь". Для ввода текстовой информации и управления с помощью "горячих клавиш" используется клавиатура.

К сожалению, объем учебного пособия не позволяет полностью реализовать все требуемые функции графического редактора, однако основные принципы будут подробно разобраны. Таким образом недостающие элементы Вы сможете запрограммировать самостоятельно. Ниже приводится примерный вид редактора:

Естественно, что в процессе модификации, улучшения и расширения возможностей редактора его вид может быть изменен.

Для работы нам понадобится несколько процедур и функций, реализующих взаимодействие с мышью, управление вводом строки в графическом режиме, вывод сообщений и т.д. Их создание является не сложной, но объемной и рутинной задачей. Поэтому данные процедуры и функции, сосредоточенные в трех модулях, будут предоставлены в готовом виде (полностью текст модулей находится на сайте http://shadrinsk.zaural.ru/~sda/teaching/programms/gred/index.html и в авторском разделе web-сервера кафедры ТМИ Шадринского государственного педагогического института).

Модуль Mouse.pas:

Используемые функции и процедуры:

function ChkAndReset: Boolean;

Инициализация мыши, возвращает true при нормальном завершении

procedure ShowMouse;

Показывает курсор мыши

procedure HideMouse;

Скрывает курсор мыши

procedure GetMouseState(var X,Y:integer; var Left,Right:boolean);

Получает информацию о состоянии мыши – координаты и статус нажатия клавиш

Модуль Inter.pas:

Используемые функции и процедуры:

procedure NormalizedRect(var r:TRect);

Нормализует переданный прямоугольник. Класс TRect подробно рассмотрен в фазах 3 и 7 программного проекта.

function LineIntersectRect(var oLine,oRect:TRect):boolean;

Возвращает ИСТИНУ, если линия пересекает прямоугольник. Подробнее см. фазу 7 программного проекта.

function Exists(Filename:string):boolean;

Возвращает ИСТИНУ, если файл существует на диске

function InputString(MaxLen:byte; Caption:string;
var inString:string):boolean;

Организует ввод строки в графическом режиме.

MaxLen - максимальное количество отображаемых символов во вводимой строке

Caption - заголовок

InString - первоначальное значение и введенная строка

procedure ShowBar(x1,y1,x2,y2:integer; Color:word; Raised:boolean);

Выводит на экран выпуклый или вдавленный прямоугольник в графическом режиме. Используется обычно для прорисовки кнопок

x1,y1,x2,y2 – координаты прямоугольника

Color – его цвет

Raised – если true, то прямоугольник является выпуклым, иначе- вдавленным

procedure Messagebox(Caption:string);

Выводит в графическом режиме сообщение в центре экрана.

Модуль SimplFont.pas:

Процедуры и функции данного модуля используются в функции InputString и процедуре Messagebox модуля inter.pas.


 

2.1 Графика.

2.1.1        Графические режимы.

2.1.1.1              Характеристики графических режимов

ТП позволяет использовать различные графические режимы, доступные на данном типе видеоадаптера. Для каждого из видеоадаптеров требуется соответствующий драйвер в виде файла с расширением BGI (Borland Graphics Interface).

Например, если на компьютере установлен видеоадаптер VGA, то для поддержки графического режима требуется наличие на диске файла egavga.bgi.

Графический драйвер предоставляет самый низкоуровневый интерфейс для взаимодействия с экраном в графическом режиме. Все графические процедуры и функции ТП обращаются не напрямую к видеопамяти, а процедурам и функциям драйвера, которые скрыты от программиста. Таким образом обеспечивается унификация процесса программирования в произвольном графическом режиме.

Каждый графический режим имеет несколько характеристик:

1. Разрешение. Под разрешением понимается количество точек на экране по осям X и Y. Существует несколько стандартов разрешений графического режима - 320*200, 640*200, 640*350, 640*480, 800*600, 1024*768 и т.д. Расположение осей в ТП отличается от математического стандарта.

2. Набор цветов. Под набором цветов понимается максимальное количество цветов, которые могут быть отображены в одной точке. Существует несколько стандартных значений для набора цветов, кратных байту или части байта: 2 цвета (1 бит), 4 цвета (2 бита), 16 цветов (4 бита), 256 цветов (1 байт), 65536 цветов (2 байта), 16777216 цветов (3 байта) и т.д. В дальнейшем мы будем работать с 16-ти цветными режимами, хотя при наличии соответствующего драйвера ТП поддерживает любой из вышеперечисленных наборов цветов.

3. Количество страниц. Видеостраницей называется область памяти компьютера, отображаемой на экране (такую память называют видеопамятью). Т.о., обращаясь к некоторой области памяти, мы автоматически обращаемся к экрану.

На самом деле данный процесс более сложен. Например, один байт оперативной памяти в режиме 640*480 (16 цветов) адресует сразу 8 точек, хотя, казалось бы, адресовать одним байтом более двух точек в этом режиме в принципе невозможно. Установив любой SVGA режим (640*480,256 и выше), мы в один момент времени будем обращаться к одной точке, а в другой, используя тот же адрес памяти, уже к другой точке. Однако все эти нюансы не имеют значения для программиста-прикладника, так как взаимодействие с экраном ведется с помощью драйвера BGI, что позволяет полностью унифицировать программу, сделать ее независимой от типа видеоадаптера компьютера.

В зависимости от объема видеопамяти, разрешения и количества цветов можно обращаться к одной, двум, четырем и более графическим страницам. Одна из графических страниц всегда является видимой (ее содержимое видно на экране), а другая - активной (то есть на нее идет весь графический вывод). ТП позволяет использовать как видимые, так и активные страницы.

Задача 1. Объем видеопамяти некоторого видеоадаптера составляет 256 килобайт. Сколько видеостраниц может поддерживать видеоадаптер в шестнадцатицветных режимах 640*200, 640*350 и 640*480?

Рассмотрим режим 640*200. В каждом байте видеопамяти для шестнадцатицветного режима может храниться 2 точки. Количество точек на экране – 640*200=128000. Т.о. для хранения одной страницы видеопамяти требуется 128000/2=64000 байт, что составляет 64000/1024=62.5 килобайта. Получаем, что в 256 килобайтах видеопамяти можно разместить 256 div 62.5 = 4 видеостраницы.

Для режимов 640*350 и 640*480 решите задачу самостоятельно.

 

2.1.1.2              Видеоадаптеры EGA и VGA

Для установки графического режима требуется:

1.     Наличие на диске соответствующего файла драйвера.

2.     Константа драйвера, если драйвер является стандартным (т.е. входит в поставку ТП)

3.     Константа режима (т.к. один видеоадаптер может поддерживать несколько графических режимов)

В нижеприведенной таблице рассмотрены наиболее распространенные графические режимы, поддерживаемые видеоадаптерами EGA и VGA.

 

Драйвер,
константа драйвера

Типы видеоадаптеров.

Константы
режимов.

Характеристика

egavga.bgi

EGA

EGA

EGALo

 

EGAHI

640х200 16 цветов, 4 стр.

640х350 16 цветов, 2 стр.

egavga.bgi

VGA

VGA

VGALo

 

VGAMed

 

VGAHI

 

640х200 16 цветов, 4 стр.

640х350 16 цветов, 2 стр.

640х480 16 цветов, 1 стр.

 

Многие видеоадаптеры поддерживают графические режимы других видеосистем. Например, видеоадаптер VGA поддерживает все графические режимы EGA.

При изучении графики мы будем пользоваться 16-цветными режимами. Это ограничивает возможности манипуляции реалистичными и фото- изображениями, зато упрощает изучение остальных разделов графики.

2.1.1.3              Этапы работы в графическом режиме

Процесс программирования в графическом режиме содержит нескольких этапов:

1.     Инициализация графического режима. Под инициализацией графического режима подразумевается переход из текстового в графический режим или из одного графического режима - в другой.

2.     Работа в графическом режиме. Программирование в графическом режиме заключается в вызове процедур и функций рисования, а также настройки свойств инструментов и области рисования.

3.      Завершение работы в графическом режиме. Под этим подразумевается окончательный переход из графического режима тестовый перед завершением работы программы. В процессе программирования в графическом режиме разрешается также временный переход в текстовый режим с последующим восстановлением графического.

 

2.1.2        Программирование в графическом режиме

Графические процедуры, функции, переменные, константы и типы, реализующие этапы работы в графическом режиме, сосредоточены в модуле GRAPH.TPU.

2.1.2.1               Инициализация и завершение работы
с графикой, видеоадаптер и видеорежимы

 

Инициализация

procedure InitGraph (var GraphDriver:Integer; var GraphMode: integer; PathToDriver: string);

Инициализирует графический режим

procedure SetGraphMode(Mode: Integer);

Устанавливает новый графический режим для текущего видеоадаптера

 

Завершение

procedure CloseGraph;

Завершает работу с графикой

procedure RestoreCrtMode;

Временный переход в текстовый режим

 

Информация о графическом драйвере и видеорежимах

procedure DetectGraph(var GraphDriver, GraphMode: Integer);

Возвращает тип видеодрайвера и максимально возможный (наилучший) графический режим для установленного на компьютере видеоадаптера

function GetDriverName: string;

Возвращает имя текущего видеодрайвера

function GetModeName(ModNumber: integer): string;

Возвращает информацию о видеорежиме текущего видеодрайвера

function GetGraphMode: integer;

Возвращает текущий видеорежиме

function GetМахMode: integer;

Возвращает номер максимально возможного видеорежима для текущего видеоадаптера

procedure GetModeRange(GraphDriver:Integer; var LoMode, HiMode:Integer);

Возвращает диапазон возможных значений видеорежимов для видеоадаптера

 

Цвета

function GetMaxColor:word;

Возвращает максимальный номер цвета, доступного в текущем графического режима.

 

Координатная система

function GetMaxX:integer;

Возвращает максимальную X-координату

function GetMaxY:integer;

Возвращает максимальную Y-координату

 

Для инициализации графического режима существует процедура InitGraph:

procedure InitGraph (var GraphDriver:Integer; var GraphMode: Integer; PathToDriver: string);

GraphDriver - константа драйвера

GraphMode - константа режима

PathToDriver - путь для драйвера

Задача 2. Покажите все варианты инициализации графического режима 640x350 16 цветов на видеоадаптере VGA.

Решение 1:

var grM,grD:integer;

 begin

 grD:=EGA; {устанавливаем константу драйвера}

 grM:=EGAHi; {устанавливаем константу графического режима}

 InitGraph(grd,grm,''); {инициализируем графический режим}

 ... {здесь будет вызов графических процедур и функций}

 CloseGraph;

 end;

Решение 2:

var grM,grD:integer;

 begin

 grD:=VGA;

 grM:=VGAMed;

 InitGraph(grd,grm,'');

 ...

 CloseGraph;

 end;

Как видим, в обоих случаях путем для драйвера является пустая строка. Т.о. подразумевается, что файл драйвера (egavga.bgi) находится в текущем каталоге.

Если на компьютере установлен видеоадаптер EGA, то использование второго метода будет ошибочным. Если установлен адаптер VGA, то согласно схеме он поддерживает все режимы EGA, и оба метода сработают верно.

 

Если требуется, чтобы программа сама определяла графические драйвер и режим, можно вместо имени адаптера передать константу Detect, определенную в модуле GRAPH. Таким образом, произойдет инициализация максимально возможного графического режима для данного типа адаптера. В переменные GraphDriver и GraphMode будут помещены соответственно тип определенного адаптера и номер установленного режима. Альтернативный способ автоматического определения типа графического драйвера и лучшего (максимально возможного) видеорежима состоит в использовании процедуры DetectGraph:

procedure DetectGraph(var GraphDriver, GraphMode: Integer);

Процедура сохранит в переданных переменных GraphDriver и GraphMode соответственно тип видеодрайвера и максимально возможного для него видеорежима.

После установки графического режима существует возможность как перехода в другой графический режим, поддерживаемый данным типом видеоадаптера (SetGraphMode), так и временный переход в текстовый режим (RestoreCrtMode) с последующим восстановлением графического (также SetGraphMode). И в том и другом случае не происходит перераспределения или освобождения динамической памяти, выделенной для графического драйвера при инициализации графического режима процедурой InitGraph. Обе процедуры производят очистку экрана. То есть, не следует рассчитывать, например, на то, что при временном выходе в текстовый режим содержимое графического экрана останется без изменений.

Завершение работы с графикой происходит с помощью вызова процедуры CloseGraph. При этом освобождается динамическая память, выделенная для графического драйвера и выполняется переход в текстовый режим.

При инициализации графического режима ТП выделяет определенный объем динамической памяти для загрузки файла графического драйвера. До момента завершения работы графикой данная область памяти не используется в других целях (т.е. при работе с указателями и динамическими переменными). Поэтому надо быть осторожным при использовании директивы компилятора $M, с помощью которой устанавливаются объем динамической памяти, занимаемый программой, а также размер стека. При выделении слишком малого объема динамической памяти инициализация графического режима завершится ошибкой.

Одной из характеристик любого графического режима является разрешение экрана. Левый верхний угол экрана имеет координаты (0,0), правый нижний - (GetMaxX, GetMaxY), где GetMaxX, и GetMaxY - функции получения максимальных для текущего графического режима координат X и Y.

Например в режиме 640x480 координатная сиcтема (0,0)-(639,479), то есть функция GetMaxX возвратит 639, а GetMaxY - 479.

 

Задания для раздела
"Инициализация и завершение работы
с графикой, видеоадаптер и видеорежимы
" (
1-4).

Задание 1. Определите и выведите на экран тип видеоадаптера и максимально для него возможный графический режим. Решите задачу с инициализацией и без инициализации графики.

Задание 2. Создайте программу, последовательно (по нажатию на любую клавишу) переключающуюся между всеми возможными графическими режимами видеоадаптера, выводящую на экран при каждом переключении тип видеоадаптера, текущий графический режим и максимальное количество цветов для данного видеорежима.

Задание 3. Создайте программу, аналогичную предыдущей, с тем отличием, что после вывода информации на графический экран происходит временный переход в текстовый режим, где также осуществляется вывод той же самой информации. Переключение между режимами – по нажатию на любую клавишу.

Задание 4. По введенному с клавиатуры номеру видеорежима произвести переключение в данный графический режим. Если такой графический режим не поддерживается видеоадаптером, выдать соответствующее сообщение.

 

2.1.2.2              Анализ ошибок

Получение информации об ошибке

function GraphResult:integer;

Возвращает номер ошибки

function GraphErrorMsg(ErrCode:integer):string;

Возвращает информацию об ошибке по ее номеру.

Обращение к графическим процедурам может закончиться ошибкой.

Например, при попытке вызова процедуры построения окружности до инициализации графического режима произойдет ошибка. Может не хватить оперативной памяти для закраски некоторой области, размещения графического драйвера или шрифта. Могут отсутствовать файлы шрифтов и графических драйверов и т.д.

При возникновении графических ошибок остановки программы не происходит. Программист обязан сам предусмотреть обработку ошибок, используя для этого две функции:

GraphResult возвращает одну из констант, определяющих номер ошибки (число от -1 до -14) или grOk (0), если ошибки не произошло. Результат, возвращаемый GraphResult, чаще всего проверяют на равенство grOk и выводят соответствующее сообщение, если ошибка имеет место.

GraphErrorMsg возвращает по номеру ошибки информацию о ней в строке. Данную функцию используют чаще всего в комбинации с GraphResult для формирования сообщения об ошибке.

 

Задания для раздела "Анализ ошибок" (5-6).

Задание 5. Создайте программу, в которой при неверной инициализации графического режима будет выдано сообщение о соответствующей ошибке.

Задание 6. Выведите на экран информацию о всех графических ошибках.

 

2.1.2.3              Графический указатель

 

 

Графический указатель

procedure MoveTo(X,Y:integer);

Перемещение графического указателя.

procedure MoveRel(DX,DY:integer);

Перемещение графического указателя относительно его текущего местоположения.

function GetX:integer;

function GetY:integer;

Получение координат графического указателя.

 

В графическом режиме существует аналог курсора, который называется графическим указателем (CP - current pointer) и невидим на экране. Некоторые процедуры рисования используют и изменяют его координаты. Существуют специальные процедуры перемещения графического указателя, не связанные с рисованием (MoveTo, MoveRel), а также функции для получения координат CP (GetX, GetY).

Задания для раздела "Графический указатель" (7-9).

В заданиях ниже для рисования разрешается пользоваться только процедурой LineTo(x,y:integer), которая чертит прямую линию от текущего местоположения графического указателя в точку X,Y, а затем перемещает туда же указатель.

Задание 7. Создать процедуру CreateHome(x,y:integer), которая рисует следующую картинку:

Придерживаться следующего правила – "не отрывать карандаша", т.е. начало каждой следующей линии должно совпадать с концом предыдущей, а также не рисовать одну и ту же линию два и более раз. Проверить работу процедуры, вызвав ее несколько раз с различными координатами X и Y.

Задание 8. Определить тип запись, содержащий: 1) информацию о координатах точки, 2) числовое поле, отвечающее за действие над данной точкой. Значения числового поля: 0 – требуется провести линию в эту точку, 1 – требуется переместить курсор в эту точку. Определить и заполнить массив из нескольких записей данного типа. Программа должна перебрать все элементы массива, совершая требуемые действия над каждой точкой.

Задание 9. Параметрическое уравнение окружности выглядит следующим образом: x=R*sin(t); y=R*cos(t), где R – радиус окружности, t-параметр, пробегающий значения от 0 до 360 градусов, x и y – координаты точки окружности. По введенному значению радиуса нарисовать окружность в центре экрана.

 

2.1.2.4              Графические инструменты

Свойства карандаша

procedure SetColor(Color: Word);

Установка цвета линий.

function GetColor: Word;

Получение текущего цвета линий.

procedure SetLineStyle (LineStyle: Word; Pattern: Word; Thickness: Word);

Установка стиля линий.

procedure GetLineSettings(var LineInfo: LineSettingsType);

Получение текущего стиля линий.

procedure SetWriteMode(WriteMode: Integer);

Установка режима вывода линий на экран

 

Свойства кисти

procedure SetFillStyle(Pattern: Word; Color: Word);

Выбор встроенного стиля закраски и установка цвета.

Procedure SetFillPattern(Pattern: FillPatternType; Color: Word);

Выбор пользовательского стиля закраски и установка цвета

Procedure GetFillSettings(var FillInfo: FillSettingsType);

Получение текущего стиля и цвета закраски

Procedure GetFillPattern(var FillPattern: FillPatternType);

Получение текущего пользовательского стиля закраски

 

Если рассматривать экран или некоторую область экрана как холст, то рисование в ТП осуществляется на нем двумя инструментами - карандашом и кистью. Карандашом рисуют линии, различные кривые и текст, кистью производят закраску области.

Как карандаш, так и кисть обладают целым набором свойств.

Свойства карандаша

Свойства кисти

1.     Цвет линии

2.     Стиль линии (тонкая или толстая (1 или 3 пикселя), сплошная, прерывистая, состоящая из точек-тире и т.д.)

3.     Режим вывода линии на экран (тип логической операции между линией и содержимым экрана)

1.     Цвет закраски

2.     Стиль закраски (определяется содержимым битовой матрицы 8´8 точек)

 

2.1.2.4.1          Свойства карандаша

Для управления цветом линий служит процедура SetColor, параметром которой является устанавливаемый цвет. Получить текущий цвет линий можно с помощью функции GetColor.

Для управления типом и толщиной линий (сплошная, точками или тип определенный пользователем) служит процедура SetLineStyle:

procedure SetLineStyle (LineStyle: Word; Pattern: Word; Thickness: Word);

где

LineStyle - одна из констант SolidLn=0, DottedLn=1, CenterLn=2, DashedLn=3 или UserBitLn=4 (линия, определенная пользователем).

Pattern - число, двоичное изображение которого определяет тип линии, определенной пользователем. Если LineStyle не равно UserBitLn, то значение Pattern может быть любым.

Thickness - константа NormWidth или ThickWidth, определяющая толщину линии (1 или 3 пикселя):

Получить информацию о текущем установленном стиле линий можно с помощью процедуры GetLineSettings:

procedure GetLineSettings(var LineInfo: LineSettingsType);

где LineSettingsType - это тип записи, определенный в модуле Graph.

LineSettingsType = record

LineStyle: Word; {стиль линии}

Pattern: Word; {шаблон для пользовательского стиля}

Thickness: Word; {толщина}

end;

 

Для установки режима вывода на экран используется процедура SetWriteMode.

procedure SetWriteMode(WriteMode: Integer);

где WriteMode - одна из следующих констант:

Константа

Значение

Тип логической операции

Краткое описание

NormalPut

0

MOV

Вывод на экран, с игнорированием его содержимого

CopyPut

0

MOV

Аналог NormalPut

XORPut

1

XOR

Вывод на экран с операцией XOR

OrPut

2

OR

Вывод на экран с операцией OR

AndPut

3

AND

Вывод на экран с операцией AND

NotPut

4

NOT

Инвертирование выводимых линий, содержимое экрана не учитывается

 

Задания для раздела "Свойства карандаша" (10-14).

При решении заданий разрешается пользоваться уже известной процедурой LineTo, а также процедурой Line(x1,y1,x2,y2:integer), которая рисует линию из точки (x1,y1) в точку (x2,y2).

Задание 10. Создать программу, выводящую на экран 8 линий различных стилей (см. рисунок выше).

Задание 11. Нарисовать 8 концентрических окружностей различных стилей в центре экрана (см. задание 9 на стр. 18).

Задание 12. Создать "бегущую" секундную стрелку в центре экрана, совершающую полный оборот за 60 секунд (см. задание 9 на стр. 18). Запрещается изменять цвет линий для затирания предыдущей стрелки, пользуйтесь установкой режима вывода на экран.

Задание 13. Нарисуйте "решетку" по следующим правилам: "решетка" заполняет собой весь экран (вне зависимости от разрешения установленного графического режима) и содержит 29 линий по горизонтали и вертикали. Цвета линий распределяются следующим образом: 1, 2, … 14, 15, 14, … 2, 1.

Задание 14. Выполните предыдущее задание со следующими изменениями: цвет всех линий одинаков, однако тип горизонтальных линий имеет значение 10010101, а тип вертикальных – инверсию от этого значения.

 

Программный проект (1)

Наш графический редактор будет состоять из нескольких модулей. Все, связанное с графическими примитивами, сосредоточим в модуле GrPrim.pas.

Каждый графический примитив, использующий для рисования карандаш, обладает набором данных, определяющих свойства карандаша. Определим такой тип:

{ модуль GrPrim.pas }

type

prLineStyle=record

  linestyle, pattern, thickness, color: word;

end;

Префикс pr в названии типа здесь и в дальнейшем будет означать "параметр".

2.1.2.4.2          Свойства кисти

Для управления типом заполнения используются две процедуры:

procedure SetFillStyle(Pattern: Word; Color: Word);

Color - цвет заливки.

Pattern - одна из констант, определенных в модуле GRAPH: EmptyFill=0, SolidFill=1, LineFill=2, LtSlashFill=3, SlashFill=4, BkSlashFill=5, LtBkSlashFill=6, HatchFill=7, XHatchFill=8, InterleaveFill=9, WideDotFill=10, CloseDotFill=11:

Существует константа заполнения UserFill=12, определяющая пользовательский стиль. В этом случае вид заполнения определяется процедурой SetFillPattern:

procedure SetFillPattern(Pattern: FillPatternType; Color: Word)

где FillPatternType предопределен в модуле GRAPH как

type FÌillPattenType = array[1..8] of byte;

Таким образом Pattern определяет битовый образ 8х8 точек (8 бит в каждом байте, 8 байт в массиве).

ТП позволяет для установки пользовательского стиля использовать только процедуру SetFillPattern. Т.о. в процедуре SetFillStyle никогда не используется константа UserFill (ее применение будет рассмотрено ниже).

 

Для получения текущего стиля закраски используется процедура GetFillSettings:

procedure GetFillSettings(var FillInfo: FillSettingsType);

где FillInfo - это информация о стиле и цвете заполнения типа FillSettingsType:

FillSettingsType = record

Pattern : Word;

Color : Word;

end;

Если текущий стиль заполнения является пользовательским, то значение поля Pattern равно UserFill. В этом случае получить действительный битовый образ можно с помощью процедуры GetFillPattern:

procedure GetFillPattern(var FillPattern: FillPatternType);

где FillPattern - массив для сохранения битового образа (определение типа FillPatternType см. выше)

Задания для раздела "Свойства кисти" (15-18).

При решении заданий разрешается пользоваться процедурой Bar(x1,y1,x2,y2:integer), которая рисует закрашенный прямоугольник с координатами (x1,y1)-(x2,y2).

Задание 15. Создать программу, выводящую на экран 12 квадратов различных стилей (см. рисунок выше).

Задание 16. Создать программу, заполняющую экран прямоугольниками различных размеров, типов и цветов, причем координаты каждого прямоугольника не должны выходить за пределы экрана. Типы, цвета и размеры прямоугольников определяются случайным образом.

Задание 17. Нарисовать 16 "концентрических" (от центра экрана) прямоугольников различных цветов, двух чередующихся типов – 10 и 11.

Задание 18. Нарисовать "каскадом" 12 прямоугольников различных типов. Правый нижний угол каждого находится в правом нижнем углу экрана. Левый верхний угол первого прямоугольника – в левом верхнем углу экрана, левый верхний последнего – в центре экрана. Расстояния между левыми верхними углами двух последовательно стоящих прямоугольников одинаковы. Программа должна верно работать вне зависимости от разрешения экрана

 

Программный проект (2)

Аналогично свойствам карандаша, определим тип данных, отвечающих за свойства кисти:

{ модуль GrPrim.pas }

type

prFillStyle=record

   Pattern: word;

   UserPattern: FillPatternType;

   Color: word;

  end;

Процедуры, устанавливающие стиль заливки с помощью записи данного типа, должны использовать либо SetFillStyle, либо SetFillPattern в зависимости от значения поля Pattern записи.

 

2.1.2.5              Фигуры

Существует множество процедур для рисования и заполнения фигур, включая точки, линии, окружности, дуги, эллипсы, прямоугольники, многоугольники, полосы, трехмерные полосы и сектора для построения диаграмм.

2.1.2.5.1          Точки, линии, многоугольники

Точки

procedure PutPixel(X, Y: Integer; Pixel: Word);

Вывод точки.

function GetPixel(X,Y: Integer): Word;

Получение точки.

 

Линии

procedure Line(x1, y1, x2, y2: Integer);

Рисование линии.

procedure LineTo(X, Y: Integer);

Рисование линии от текущей позиции CP (в абсолютных координатах).

procedure LineRel(Dx, Dy: Integer);

Рисование линии от текущей позиции CP (в относительных координатах).

 

Многоугольники

procedure Rectangle(x1, y1, x2, y2: Integer);

Рисование прямоугольника.

procedure DrawPoly(NumPoints: Word; var PolyPoints);

Рисование ломаной линии.

 

Для работы с точками в графическом режиме используются 2 процедуры (функции).

procedure PutPixel(X, Y: Integer; Pixel: Word);

function GetPixel(X,Y: Integer): Word;

Процедура PutPixel ставит точку цветом Pixel в координаты X, Y (текущий установленный стиль не используется). Функция GetPixel возвращает цвет точки в координатах X, Y.

 

Для рисования линий используются 3 процедуры (каждая использует текущий установленный цвет и стиль карандаша, см. процедуры SetColor, SetLineStyle в параграфе 2.1.2.4.1):

procedure Line(x1, y1, x2, y2: Integer);

procedure LineTo(X, Y: Integer);

procedure LineRel(Dx, Dy: Integer);

Процедура Line рисует линию из точки (X1,Y1) в точку (X2,Y2).

Процедура LineTo рисует линию от текущего местоположения графического курсора в точку (X,Y).

Процедура LineRel рисует линию от текущего местоположения графического курсора в точку (GetX+DX,GetY+DY).

Каждая из процедур рисования линий по окончании действия перемещает CP в конечную точку: line - в (X2,Y2); lineTo - в (X,Y); LineRel - в (GetX+DX,GetY+DY).

 

Для рисования многоугольников в ТП предусмотрены 2 процедуры (каждая использует текущий установленный цвет и стиль карандаша, см. процедуры SetColor, SetLineStyle в параграфе 2.1.2.4.1):

procedure Rectangle(x1, y1, x2, y2: Integer);

procedure DrawPoly(NumPoints: Word; var PolyPoints);

Процедура Rectangle рисует прямоугольник с координатами левого верхнего угла (X1,Y1) и правого нижнего - (X2,Y2).

Процедура DrawPoly рисует ломаную линию. Количество узловых точек для ломаной определяется параметром NumPoints, PolyPoints - это массив произвольной длины, заполненный координатами точек в следующем формате:

var PolyPoints: array[1.. NumPoints] of PointType;

где PointType - это тип записи:

PointType = record

X, Y : integer;

end;

Если координаты первой и последней точек совпадают, то DrawPoly рисует замкнутый многоугольник, иначе - ломаную линию.

 

Задания для раздела "Точки, линии, многоугольники" (19-23).

Задание 19. Заполнить массив точек типа PointType (не менее 4) и нарисовать многоугольник с помощью DrawPoly. Затем произвести сдвиг каждой точки многоугольника вниз на 10 пикселей и нарисовать его без использования DrawPoly.

Задание 20. Создать тип записи, включающий координаты и цвет точки. Случайным образом заполнить массив из 5000 таких записей (координаты любой точки должны быть в пределах экрана, цвет – от 1 до 15). Прорисовать все точки с небольшой задержкой между каждыми двумя. Затем, таким же образом, но в обратном порядке стереть все точки с экрана (цвет прорисовки - 0).

Задание 21. Выполнить предыдущее задание для произвольных линий. Не использовать цвет 0 для стирания линий (воспользуйтесь режимом вывода на экран).

Задание 22. Ввести координаты прямоугольника. Нарисовать прямоугольник несколько раз разными цветами, причем каждый раз использовать для прорисовки только один вид процедур рисования. Таким образом последовательно будут нарисованы прямоугольники с помощью только PutPixel, только LineTo, только LineRel и т.д.

Задание 23. Преобразовать процедуру CreateHome из задания 7 (стр. 18) следующим образом: procedure CreateHome(x,y;integer; Scale:real), где Scale – масштабный коэффициент. Масштабирование вести относительно начальной точки (x,y). Рисовать с использованием DrawPoly.

Программный проект (3)

Определим типы данных, необходимые для хранения информации о координатах точек, линий и многоугольников.

Для этого воспользуемся, во первых, двумя типами, определенными в модуле Objects:

TPoint=object

  X,Y:integer;

end;

TRect=object

  A,B:TPoint;

end;

Зарезервированное слово object означает, что данный тип является объектным типом, о котором будет подробно рассказано в параграфе Объектно-ориентированное программирование (ООП). На текущий момент нам достаточно знать, что классы TPoint и TRect практически эквивалентны типу запись.

{ модуль GrPrim.pas }

type

{точка}

  prPoint=TPoint;

{прямоугольник, отрезок}

  prRect=TRect;

{многоугольник}

  prPoints=array[1..1000] of prPoint;

  pprPoints=^pprPoints;

  prPoly=record

   number:word;

   ppoints:pprpoints;

  end;

 

2.1.2.5.2          Дуги, окружности, эллипсы

Кривые

Procedure Circle(X,Y: Integer; Radius: Word);

Рисует окружность

Procedure Arc(X,Y; Integer; StAngle, EndAngle, Radius; Word);

Рисует дугу окружности

Procedure Ellipse(X, Y: Integer; StAngle, EndAngle: Word; Xradius, YRadius: Word);

Рисует дугу эллипса

 

Сопутствующие процедуры

Procedure GetArcCoords(var ArcCoords: ArcCoordsType);

Возвращает координаты завершения последней операции рисования дуги окружности или эллипса

Procedure GetAspectRatio(var Xasp, Yasp: Word);

Возвращает два числа, по которым может быть рассчитано соотношение сторон экрана

Procedure SetAspectRatio(Xasp, Yasp: Word);

Изменяет установленное при инициализации графического режима соотношение сторон экрана

Для рисования окружностей в ТП используется процедура
Circle:

Procedure Circle(X,Y: Integer; Radius: Word);

где (X,Y) - координаты центра окружности, а Radius - радиус окружности. Процедура Circle всегда рисует правильную окружность, учитывая при этом соотношение сторон экрана (см. GetAspectRatio).

Для рисования дуги окружности используется процедура Arc:

Procedure Arc(X,Y; Integer; StAngle, EndAngle, Radius; Word);

где (X,Y) - координаты центра дуги окружности, а Radius - радиус дуги окружности. Параметры StAngle и EndAngle представляют собой начальный и конечный угол дуги в градусах, при этом начальный угол может оказаться больше конечного угла. Процедура Arc всегда рисует правильную дугу окружности, учитывая при этом соотношение сторон экрана (см. GetAspectRatio).

Для рисования дуги эллипса используется процедура Ellipse:

Procedure Ellipse(X, Y: Integer; StAngle, EndAngle: Word; Xradius, YRadius: Word);

где (X,Y) - координаты центра дуги эллипса, Xradius, YRadius - радиусы полуосей дуги эллипса. Параметры StAngle и EndAngle представляют собой начальный и конечный угол дуги в градусах, при этом начальный угол может оказаться больше конечного угла.

Естественно, что с помощью дуг окружностей и эллипса можно нарисовать как целую окружность, так и целый эллипс. Для этого значения начального и конечного угла (StAngle, EndAngle) должны составлять соответственно 0 и 360 градусов.

В интерактивной помощи ТП указано, что процедуры рисования кривых не используют никакие параметры карандаша, кроме цвета (SetColor). На самом деле это не так. Кривые используют такой параметр стиля линий, как толщину (другие параметры в SetLineStyle могут иметь произвольное значение). Если толщина линий установлена в 3, то кривые также используют режим вывода линий на экран (SetWriteMode). Этот факт будет использован в графическом редакторе для интерактивной прорисовки окружностей, эллипсов и дуг.

 

Очень часто требуется продолжить нарисованную дугу эллипса или окружности другой дугой или прямой линией. Для получения информации о последней операции рисования дуги можно воспользоваться процедурой GetArcCoords:

Procedure GetArcCoords(var ArcCoords: ArcCoordsType);

где ArcCoordsType является типом записи, определенным в модуле Graph:

ArcCoordsType = record

 X, Y, {координаты центра дуги}

 Xstart, Ystart, {координаты начала дуги}

 Xend, Yend : integer; {координаты конца дуги}

 end;

 

Попытка нарисовать окружность или дугу окружности с помощью дуги эллипса, передавая одинаковые радиусы большой и малой полуосей, закончится неудачей. Дело в том, что экран ЭВМ в графическом режиме содержит различное количество точек по горизонтали и вертикали (разрешения 640*350, 640*480, 800*600 и т.д.). Потому эллипс с одинаковыми полуосями будет выглядеть вытянутым по вертикали. Для получения соотношения сторон экрана можно воспользоваться процедурой GetAspectRatio:

Procedure GetAspectRatio(var Xasp, Yasp: Word);

где в переменных Xasp и Yasp возвращаются минимальные целые значения, простым делением которых друг на друга можно найти коэффициент соотношения сторон экрана.

Если нарисованные окружности выглядят эллипсами, это означает, что на компьютере используется нестандартный монитор, либо ТП неверно определил соотношение сторон экрана при инициализации графического режима. В этом случае может помочь процедура SetAspectRatio, которая изменяет текущее соотношение сторон экрана.

 

Задания для раздела "Дуги, окружности, эллипсы" (24-27).

Задание 24. В верхней части экрана нарисуйте 15 произвольных окружностей различными цветами с помощью процедуры Circle. В средней части экрана –с помощью процедуры Arc. В нижней части экрана – с помощью процедуры Ellipse. Рисунки должны быть идентичными.

Задание 25. Нарисовать волнистую линию, состоящую из N полуокружностей:

 ………

Задание 26. Нарисовать N случайных дуг эллипса, причем центр каждой последующей дуги находится в точке окончания предыдущей. Случайным является центр первой дуги и радиусы всех дуг. Дуги не должны выходить за пределы экрана.

Задание 27. Не пользуясь никакими процедурами рисования, кроме рисования окружности, создайте программу, результатом выполнения которой будет следующий рисунок:

Программный проект (4)

Определим типы данных, необходимые для хранения информации о координатах окружностей и эллипсов. Типы для дуг определите самостоятельно.

{ модуль GrPrim.pas }

type

{ окружность }

  prCircle=record

   x,y:integer;

   radius:integer;

  end;

{ эллипс }

  prEllipse=record

   x,y:integer;

   XRad,YRad:integer;

  end;

 

2.1.2.5.3          Заполнения

Закрашенные многоугольники

procedure Bar(x1, y1, x2, y2: Integer);

Рисует закрашенный прямоугольник

procedure Bar3D(x1, y1, x2, y2: Integer; Depth: Word; Top: Boolean);

Рисует закрашенный трехмерный столбец

procedure FillPoly(NumPoints: Word; var PolyPoints);

Рисует закрашенный многоугольник

Закрашенные криволинейные фигуры

procedure FillEllipse(X, Y: Integer; XRadius, YRadius: Word)

Рисует закрашенный эллипс

procedure PieSlice(X, Y: Integer; StAngle, EndAngle, Radius: Word);

Рисует закрашенный круговой сектор

procedure Sector(X, Y: Integer; StAngle, EndAngle, XRadius, YRadius: Word);

Рисует закрашенный эллиптический сектор

Заливка

procedure FloodFill(X, Y: Integer; Border: Word);

Закрашивает ограниченную область

 

Все процедуры заполнений используют текущий цвет и стиль кисти (см. процедуры SetFillStyle и SetFillPattern)

Для рисования закрашенных многоугольников в ТП предусмотрены 2 процедуры:

procedure Bar(x1, y1, x2, y2: Integer);

procedure FillPoly(NumPoints: Word; var PolyPoints);

Процедура Bar рисует закрашенный прямоугольник с координатами левого верхнего угла (X1,Y1) и правого нижнего - (X2,Y2).

Процедура FillPoly рисует закрашенный многоугольник. Количество узловых точек для ломаной определяется параметром NumPoints, PolyPoints - это массив произвольной длины, заполненный координатами точек в следующем формате:

var PolyPoints: array[1.. NumPoints] of PointType;

где PointType - это тип записи:

PointType = record

X, Y : integer;

end;

Координаты первой и последней точек могут не совпадать, в этом случае процедура FillPoly сама формирует многоугольник, соединяя первую и последнюю точки.

Для создания столбчатых диаграмм используется процедура Bar3D:

procedure Bar3D(x1, y1, x2, y2: Integer; Depth: Word; Top: Boolean);

где (X1,Y1)-(X2,Y2) - координаты верхнего левого и правого нижнего углов передней грани столбца, Depth - глубина столбца (Z - координата), Top - параметр, отвечающий за прорисовку верхней грани столбца (если Top=true, то верхняя грань рисуется). Последний параметр очень полезен, когда требуется "поставить" друг на друга несколько столбцов. Bar3D - единственная процедура, которая рисует, используя одновременно как свойства кисти, так и свойства карандаша. Карандашом обводятся контуры столбца, кистью закрашивается передняя грань.

 

Для создания закрашенного эллипса используется процедура FillEllipse:

procedure FillEllipse(X, Y: Integer; XRadius, YRadius: Word)

где (X, Y) - координаты центра эллипса, XRadius, YRadius - радиусы полуосей. Учитывая соотношение сторон экрана (GetAspectRatio), можно легко нарисовать закрашенную окружность.

Для создания кругового и эллиптического сектора используются соответственно процедуры PieSlice и Sector

procedure PieSlice(X, Y: Integer; StAngle, EndAngle, Radius: Word);

procedure Sector(X, Y: Integer; StAngle, EndAngle, XRadius, YRadius: Word);

где (X,Y) - координаты центра сектора, StAngle и EndAngle -начальный и конечный угол сектора в градусах, Radius - радиус кругового сектора, а Xradius, YRadius - радиусы полуосей эллиптического сектора.

 

Для закраски произвольной, ограниченной границей некоторого цвета области используется процедура FloodFill:

procedure FloodFill(X, Y: Integer; Border: Word);

где (X, Y) - координаты начала заливки, Border - цвет границы. К сожалению, ТП не поддерживает заливку одноцветной области (т.е. – до произвольной границы).

Задания для раздела "Заполнения" (28-32).

Задание 28. Даны 5 чисел. Построить (одну под другой) столбчатую (Bar3D) и круговую диаграммы для данного набора чисел.

Задание 29. Вывести на экран N случайных треугольников зеленого цвета, окантованных синим цветом.

Задание 30. Нарисовать стандартную мишень в центре экрана:

Задание 31. Нарисовать 5 концентрических колец в центре экрана:

Задание 32. Нарисовать на экране N фигур случайных размеров и местоположения, каждая из которых выглядит следующим образом (закрашенные прямоугольник и два вписанных в него полуэллипса):

2.1.2.6              Вывод текста

Стиль шрифта

procedure SetTextStyle(Font, Direction: Word; CharSize: Word);

Устанавливает текущий шрифт, направление вывода и размер символов.

procedure SetTextJustify(Horiz, Vert: Word);

Устанавливает выравнивание выводимой строки относительно точки вывода.

procedure SetUserCharSize(MultX, DivX, MultY, DivY: Word);

Устанавливает пользовательский размер символов.

procedure GetTextSettings(var TextInfo: TextSettingsType);

Возвращает стиль шрифта.

function TextWidth(TextString: string): Word;

Возвращает ширину переданной строки в пикселях.

function TextHeight(TextString: string): Word;

Возвращает высоту переданной строки в пикселях.

Вывод текста

procedure OutText(TextString: string);

Выводит на экран строку с позиции CP.

procedure OutTextXY(X,Y: Integer; TextString: string);

Выводит на экран строку.

Пользовательский шрифт

function InstallUserFont(FontFileName: string ): Integer;

Регистрирует пользовательский шрифт и возвращает идентификационное значение для данного шрифта.

 

ТП для вывода текста использует 2 вида шрифтов - растровый и векторный. Под растровым понимается шрифт, символы которого строятся с помощью матрицы точек, в результате чего очень плохо масштабируются. Векторные шрифты имеют специальный формат и состоят из графических фигур, чаще всего - из отрезков, соединенных в определенной последовательности. Масштабирование таких шрифтов приводит только к изменению расстояния между ключевыми точками фигур, в результате чего увеличенный или уменьшенный символ выглядит также, как и нормальный.

Растровый шрифт, используемый ТП, является на самом деле шрифтом операционной системы. Т.о. только от операционной системы и установленных системных драйверов зависит, будут ли отображаться русские символы в программе, или нет. Программа на ТП, использующая встроенный системный шрифт, не будет универсальной, независимой от свойств операционной системы. По умолчанию программа использует именно этот шрифт. Его идентификационный номер = 0.

Векторные шрифты ТП хранятся в специальных файлах с расширением ".CHR" и должны присутствовать в каталоге программы (об исключениях из этого правила будет рассказано позднее). ТП "знает" от 10 стандартных шрифтах, каждый из которых имеет свой идентификационный номер (от 1 до 10).

 

Идентификационный номер

Имя файла шрифта

Характеристика

Вид

1

TRIP.CHR

Утроенный шрифт

2

LITT.CHR

Уменьшенный шрифт

3

SANS.CHR

Прямой шрифт

4

GOTH.CHR

Готический шрифт

5

SCRI.CHR

Рукописный шрифт

6

SIMP.CHR

Одноштриховый шрифт

7

TSCR.CHR

Наклонный шрифт "Таймс"

8

LCOM.CHR

Шрифт "Таймс"

9

EURO.CHR

Шрифт "Courier"

10

BOLD.CHR

Крупный двухштриховый шрифт

 

Если шрифт не входит в данный список, то зарегистрировать его и в дальнейшем использовать позволяет процедура InstallUserFont:

Function InstallUserFont(FontFileName: string ): Integer;

где FontFileName - имя файла шрифта. Возвращаемое значение представляет собой идентификационный номер шрифта.

Выводимый на экран в графическом режим текст из свойств карандаша использует только цвет (см. SetColor). Другие параметры текста устанавливаются целым набором процедур.

Основной процедурой для установки стиля шрифта является процедура SetTextStyle:

Procedure SetTextStyle(Font, Direction: Word; CharSize: Word);

где Font - идентификационный номер шрифта, Direction - направление вывода текста (HorizDir=0 - горизонтально, VertDir=1 - вертикально), CharSize - размер символов (от 1 до 7). По умолчанию используются значения Font=0, Direction= HorizDir, CharSize=1.

ТП позволяет выравнивать (смещать) выводимый текст относительно точки вывода с помощью процедуры SetTextJustify:

Procedure SetTextJustify(Horiz, Vert: Word);

где Horiz - горизонтальное выравнивание текста (LeftText=0 - точка вывода находится слева от текста, CenterText=1 - в центре текста, RightText=2 - справа от текста), Vert - вертикальное выравнивание (BottomText=0 - точка вывода находится сверху от текста, CenterText=1 - в центре текста, TopText=2 - справа от текста). По умолчанию используются значения LeftText и TopText.

Для получения информации о текущем стиле шрифта используется процедура GetTextSettings:

Procedure GetTextSettings(var TextInfo: TextettingsType);

где TextettingsType – тип записи, определенный в модуле Graph:

 TextSettingsType = record

 Font : Word; {номер установленного шрифта}

 Direction    : Word; {направление вывода}

 CharSize    : Word; {размер}

 Horiz          : Word; {горизонтальное выравнивание}

 Vert           : Word; {вертикальное выравнивание }

 end;

ТП позволяет непропорционально изменять размер символов векторных шрифтов. Для этого используется процедура SetUserCharSize:

Procedure SetUserCharSize(MultX, DivX, MulY, DivY: Word);

Параметры процедуры используются следующим образом: ширина символов увеличивается в MultX/DivX раз, а высота – MulY/DivY раз. После использования SetUserCharSize определить текущий размер шрифта с помощью процедуры GetTextSettings становится невозможно (поле TextInfo.CharSize = 0).

Для получения высоты и ширины выводимых на экран символов (строк) используются функции TextWidth и TextHeight:

Function TextWidth(TextString: string): Word;

Function TextHeight(TextString: string): Word;

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

Вывод текста на экран, с учетом всех установленных свойств шрифта, включая тип, цвет, размер и выравнивание шрифта осуществляется в ТП с помощью процедур OutText и OutTextXY:

Procedure OutText(TextString: string);

Procedure OutTextXY(X,Y: Integer; TextString: string);

Первая из них выводит на экран строку с позиции графического курсора CP (при этом курсор перемещается в конец строки), а вторая - от точки (X,Y).

Векторные шрифты обладают множеством достоинств по сравнению с растровыми, однако скорость вывода текста с их помощью оставляет желать лучшего. Дополнительно, в ТП отсутствует процедуры вывода многострочного текста, ввода строки в графическом режиме и т.п. Для решения данной проблемы автором был создан модуль SimpFont.pas, который позволяет манипулировать растровыми шрифтами и активно используется в разрабатываемом графическом редакторе.

 

Задания для раздела "Вывод текста" (33-37).

Задание 33. Вывести на экран слово "Font" в столбец всеми шрифтами, как показано в таблице выше.

Задание 34. Выполнить предыдущее задание, увеличивая каждый раз размер шрифта в полтора раза.

Задание 35. В центре экрана нарисовать оси координат. Вывести на пересечении осей некоторую фразу количество раз, достаточное, чтобы показать все возможные виды выравнивания шрифта относительно точки вывода. После прорисовки очередной фразы делать задержку, затем очищать экран, выводить оси координат и фразу уже с другим выравниванием.

Задание 36. Дано: текстовый файл, номер шрифта и его размер. Вывести содержимое текстового файла на экран соответствующим шрифтом и размером (без скроллинга).

Задание 37. Выполнить предыдущее задание, повернув текст на 90 градусов против часовой стрелки.

Программный проект (5)

Определим типы данных, необходимые для хранения информации о строке в графическом режиме.

{ модуль GrPrim.pas }

type

{ текст }

  prTextStyle=record

   font,direction,horiz,vert:word;

   size:word;

   mulx,divx,muly,divy:word;

   str:string;

  end;

 

2.1.2.7              Сохранение и выдача изображений

Стиль шрифта

function ImageSize(x1, y1, x2, y2: Integer): Word;

Возвращает размер оперативной памяти, требуемой для сохранения изображения

procedure GetImage(x1, y1, x2, y2: Integer; var BitMap);

Копирует изображение с экрана в оперативную память.

procedure PutImage(X, Y: Integer; var BitMap; BitBlt: Word);

Выводит ранее сохраненное изображение из оперативной памяти на экрана.

 

ТП позволяет работать с прямоугольными блоками изображения, копируя их с экрана в оперативную память и обратно, причем вывод на экран осуществляется с использованием набора логических операций.

До копирования блока с экрана в память, требуется заранее определить ее объем. Дело в том, что в различных графических режимах для сохранения одного и того же блока требуются различные объемы оперативной памяти. Функция ImageSize возвращает требуемое значение, учитывая текущий видеорежим:

function ImageSize(x1, y1, x2, y2: Integer): Word;

где (x1, y1)-(x2, y2) - это координаты левого верхнего и правого нижнего углов копируемого в дальнейшем блока. Возвращаемый функцией результат имеет тип Word. Это означает, что размер блока не может превышать 64 килобайта. Данное ограничение не позволяет сохранить в памяти весь экран (в большинстве графических режимах) одной операцией.

Для копирования блока с экрана в оперативную память предназначена процедура GetImage:

procedure GetImage(x1, y1, x2, y2: Integer; var BitMap);

где (x1, y1)-(x2, y2) - это координаты левого верхнего и правого нижнего углов копируемого блока. BitMap - переменная (чаще всего - массив, динамически выделенный из кучи), в которую происходит копирование блока.

Для вывода блока из памяти на экран используется процедура PutImage:

procedure PutImage(X, Y: Integer; var BitMap; BitBlt: Word);

где (X, Y) -координаты точки вывода (левого верхнего угла блока), BitMap - переменная, в которой сохранен блок, BitBlt - логическая операция между блоком и содержимым экрана, на которое накладывается блок (NormalPut, CopyPut, XORPut, OrPut, AndPut или NotPut - см. параграф 2.1.2.4.1 Свойства карандаша).

Задания для раздела
"Сохранение и выдача изображений" (
38-40).

Задание 38. "Размножить" набор концентрических окружностей из задания 31 (стр. 32). Количество копий вводится с клавиатуры, их местоположение выбирается случайным образом.

Задание 39. Создать две программы. Первая из них сохраняет в файле изображение "домика" из задания 7 (стр. 18), вторая загружает изображение из файла и последовательно (слева - направо, сверху - вниз) заполняет им экран.

Задание 40. Создать любое небольшое неоднородное изображение (например – задания 30-32), разделить его на 4 равных прямоугольника (рассечением осями по центру рисунка) и произвести (с небольшой задержкой) 4 циклических перестановки указанных квадратов (либо по-, либо против часовой стрелки)

 

Программный проект (6)

Определим типы данных, необходимые для хранения информации об изображениях.

{ модуль GrPrim.pas }

type

{ изображение }

  prPicture=record

   Size:word;

   pBitmap:Pointer;

  end;

Теперь, зная процедуры рисования в Турбо-Паскале, можно определить, какие графические примитивы будут использованы в редакторе.

{ константы видов фигур }

const

TypePoint=0;

TypeLine=1;

TypeRect=2;

TypeBox=3;

TypePoly=4;

TypePolyFill=5;

TypePicture=6;

TypeText=7;

TypeCircle=8;

TypeCircleArc=9;

TypeCircleFill=10;

TypeEllipse=11;

TypeEllipseFill=12;

TypeCircleEllipse=13;

TypeFill=14;

 

2.1.2.8              Холст

Цвет холста

procedure SetBkColor(Color:word);

Устанавливает цвет фона холста.

function GetBkColor:word;

Возвращает цвет фона холста.

 

Окна

procedure SetViewPort(x1, y1, x2, y2: Integer; Clip: Boolean);

Устанавливает окно вывода.

procedure GetViewSettings(var ViewPort: ViewPortType);

Возвращает параметры текущего окна вывода.

 

Очистка холста

procedure ClearViewPort;

Очищает текущее окно

procedure ClearDevice;

Очищает экран

 

Страницы

procedure SetActivePage(Page: Word);

Устанавливает активную страницу.

procedure SetVisualPage(Page: Word);

Устанавливает видимую страницу.

Холстом в ТП называется рабочая область экрана, на которой происходит рисование. Одной из характеристик холста является его цвет (цвет фона), код которого равен 0. По умолчанию цвет с кодом 0 - это черный цвет, изменить который можно с помощью процедуры SetBkColor:

procedure SetBkColor(Color:word);

где Color - это код цвета, который в дальнейшем будет использоваться для фона холста (к сожалению, при этом становится недоступным исходный черный цвет, что может быть исправлено либо вызовом SetBkColor(0), либо переустановкой палитры). Важнейшей особенностью цвета фона является то, что при его изменении мгновенно меняется цвет всех точек с кодом 0.

Для получения текущего цвета фона используется функция GetBkColor: function GetBkColor:word;

 

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

Определение графического окна происходит в ТП с помощью процедуры SetViewPort:

procedure SetViewPort(x1, y1, x2, y2: Integer; Clip: Boolean);

где (x1, y1)-(x2, y2) - координаты окна, Clip - параметр, отвечающий за отсечение графического вывода на границах окна. Отсечение происходит, если Clip = true. При установке окна графический курсор перемещается в его левый верхний угол (т.е., с учетом относительности координат, в точку (0,0)).

Для получения информации об установленном окне используется процедура GetViewSettings:

procedure GetViewSettings(var ViewPort: ViewPortType);

где ViewPortType - тип записи, определенный в модуле Graph:

 ViewPortType = record

  x1, y1, x2, y2 : integer; {координаты окна}

  Clip : Boolean; {регулятор отсечения}

 end;

ТП позволяет очищать, т.е. заполнять цветом фона, как отдельное окно, так и весь экран. Процедура ClearViewPort очищает окно, а ClearDevice – экран.

В некоторых графических режимах (VGA или EGA 640*350, 640*200, а также Hercules) ТП позволяет эффективно использовать графические страницы. Чаще всего они применяются для создания анимации. Определить активную страницу, на которую происходит весь графический вывод, можно с помощью процедуры SetActivePage. Аналогично, с помощью процедуры SetVisualPage, устанавливается видимая страница:

procedure SetActivePage(Page: Word);

procedure SetVisualPage(Page: Word);

где Page – номер устанавливаемой страницы (начиная с нуля).

 

Задания для раздела "Холст" (41-44).

Задание 41. Создайте процедуру DrawSimpleCircle(x,y:integer), которая рисует окружность с центром в точке (x,y) и радиусом 30. Для рисования внутри данной процедуры должен быть использован только один оператор:
Circle(0,0,30). Любые другие действия могут быть произвольными. Проверьте действие процедуры.

Задание 42. Нарисуйте на экране N случайных дуг окружностей, пользуясь для рисования только процедурой Circle.

Задание 43. Создайте программу, в которой две фразы двигаются одновременно: одна сверху вниз с верхней части экрана, вторая – снизу вверх с нижней части экрана. Движение заканчивается, когда первая фраза будет внизу экрана, а вторая – вверху. Мультипликация должна быть отчетливой, без мерцания. Воспользуйтесь режимом 640*350 и работой с видеостраницами.

Задание 44. Воспользовавшись файлом из задания 39 (стр. 38) создайте программу, в которой два "домика" плавно перемещаются от стенке к стенке, один – по горизонтали, другой – по вертикали. Выход из программы – по нажатию на любую клавишу. Мультипликация должна быть отчетливой, без мерцания. Воспользуйтесь режимом 640*350 и работой с видеостраницами.

 

2.1.2.9              Палитры

Установка палитры

procedure SetPalette(ColorNum: Word; Color: Shortint);

Устанавливает цвет палитры

procedure SetAllPalette(var Palette);

Изменяет сразу несколько элементов палитры.

procedure SetRGBPalette(ColorNum, RedValue, GreenValue, BlueValue: Integer);

Изменяет элемент палитры на произвольный цвет.

 

Получение палитры

procedure GetPalette(var Palette: PaletteType);

Возвращает текущую палитру.

procedure GetDefaultPalette(var Palette: PaletteType);

Возвращает стандартную палитру

function GetPaletteSize: Integer;

Возвращает количество элементов палитры

 

Таблица констант стандартных цветов модуля Graph
(аналогично модулю
CRT)

Black=0 (черный)

Blue=1 (темно-синий)

Green=2 (темно-зеленый)

Cyan=3 (бирюзовый)

Red=4 (красный)

Magenta=5 (фиолетовый)

Brown=6 (коричневый)

LightGray=7 (серый)

DarkGray=8 (темно-серый)

LightBlue=9 (синий)

LightGreen=10 (светло-зеленый)

LightCyan=11 (св.-бирюзовый)

LightRed=12 (розовый)

LightMagenta=13 (малиновый)

Yellow=14 (желтый)

White=15 (белый)

 

Каждый стандартный цвет имеет в ТП свой код. Модуль Graph (как и модуль CRT) определяет 16 цветовых констант, мнемонические имена которых соответствует отображаемому цвету, как показано в таблице выше.

Совокупность отображаемых на экране цветов называется палитрой, которую можно рассматривать как массив цветов. При установке цвета карандаша или кисти, в процедуры SetColor, SetFillStyle и SetFillPattern передается не код цвета, а индекс элемента палитры. Т.о. цвет, индекс которого равен, например, 1, может быть как темно-синим, как предусмотрено по умолчанию, так и любым другим, при изменении элемента палитры с индексом 1. Одним из полезных свойств палитры является то, что изменение какой-либо цвета автоматически сказывается на всех пикселях экрана с данным цветом. Это свойство удобно использовать, например, когда требуется, чтобы сложный по структуре, но немногоцветный рисунок был сразу же показан на экране, минуя все стадии прорисовки. Для этого достаточно изменить в цвет фона все используемые в рисунке цвета, осуществить прорисовку, а затем восстановить все цвета в их первоначальные значения.

Для установки элемента палитры в один из стандартных цветов используется процедура SetPalette:

procedure SetPalette(ColorNum: Word; Color: Shortint);

где ColorNum - индекс палитры, Color - один из стандартных цветов (см. таблицу выше).

Для изменения сразу нескольких элементов палитры используются процедура SetAllPalette:

procedure SetAllPalette(var Palette);

где Palette - структура следующего типа:

const MaxColors = 15;

 type

 PaletteType = record

  Size: Byte;

  Colors: array[0..MaxColors] of Shortint;

 end;

MaxColors - количество цветов в текущем графическом режиме. Size - количество изменяемых элементов палитры, Colors - массив стандартных цветов. Если некоторый элемент массива меньше нуля, то соответствующий элемент палитры не изменяется.

Так как параметр Palette является нетипированным, то в процедуру SetAllPalette может быть передана структура любого типа, которая интерпретируется следующим образом: первый байт определяет количество цветов в палитре, остальные - устанавливаемые цвета.

Процедура SetAllPalette действительна для всех графических режимов, количество цветов в которых 16 и менее.

Для изменения элемента палитры на произвольный, нестандартный цвет используется процедура SetRGBPalette:

procedure SetRGBPalette (ColorNum, RedValue, GreenValue, BlueValue: Integer);

где ColorNum - индекс элемента палитры, RedValue, GreenValue, BlueValue - числа, соответствующие насыщенности красного, зеленого и синего в создаваемом цвете. В параметрах RedValue, GreenValue и BlueValue процедурой SetRGBPalette используются только младшие 6 битов (значения от 0 до 63).

Процедура действительна только для адаптеров VGA и IBM8514.

Для получения текущей палитры используется процедура GetPalette:

procedure GetPalette(var Palette: PaletteType);

где Palette - передаваемая в процедуру структура, в которой возвращается текущая палитра (см. SetAllPalette).

Для получения первоначальной, устанавливаемой по умолчанию палитры используется процедура GetDefaultPalette:

Для получения количества элементов палитры используется функция GetPaletteSize:

function GetPaletteSize: Integer;

Данная функция всегда возвращает значение GetMaxColor+1.

 

Задания для раздела "Палитры" (45-48).

Задание 45. Создать произвольный многоцветный рисунок (например – задачи 16, 22, 24 и др.). Заставить пропадать и восстанавливаться изображение с периодичностью 1 раз в секунду. Прорисовка должна быть однократной.

Задание 46. Решить предыдущую задачу со следующими изменениями – с периодичностью 1 раз в секунду должен пропадать только один цвет. По окончании цвета должны быть восстановлены аналогичным образом.

Задание 47. Нарисовать 15 прямоугольников 15-ю оттенками зеленого цвета.

Задание 48. Создать "бегущую" полосу из 15 стоящих рядом прямоугольников различных цветов (первый прямоугольник изменяет свой цвет на 15-й, второй – на первый, третий – на второй и т.д.).

 

2.1.2.10        Регистрация нестандартных графических драйверов и шрифтов.

Регистрация драйвера

function InstallUserDriver(Name: string; AutoDetectPtr: pointer): integer;

Регистрирует нестандартный графический драйвер

Регистрация шрифта

function InstallUserFont(FontFileName: string ): Integer;

Регистрирует нестандартный шрифт

 

ТП поддерживает нестандартные, созданные сторонними производителями графические драйвера BGI и шрифты CHR.

Для включения в таблицу BGI-драйверов нового, нестандартного драйвера, используется функция InstallUserDriver:

function InstallUserDriver(Name: string; AutoDetectPtr: pointer): integer;

где Name - имя файла драйвера, AutoDetectPtr - адрес функции автоопределения типа видеоадаптера. При возникновении ошибки функция InstallUserDriver возвращает значение <0, в противном случае - идентификационный номер зарегистрированного графического драйвера.

Текст функции AutoDetectPtr должен поставляться вместе с файлом графического драйвера и выглядеть примерно следующим образом:

{$F+}

function DetectDriver: Integer;

 var Found: Boolean;

 begin

 DetectDriver := grError; {Предполагаем, что тип адаптера не подходит}

 Found := ... {Тип адаптера подходит?}

 if not Found then Exit; {Нет, не подходит}

 DetectDriver := 3; {Да, подходит, возвращаем рекомендуемый графический режим }

end;

{$F-}

Если драйвер не имеет функции авторегистрации, то вторым параметром можно передавать Nil.

 

Для регистрации нестандартного шрифта используется функция InstallUserFont:

function InstallUserFont(FontFileName: string ): Integer;

где FontFileName - имя файла шрифта. При возникновении ошибки функция возвращает значение <0, в противном случае - номер идентификационный номер зарегистрированного шрифта.

Функции InstallUserFont и InstallUserDriver должны быть в общем случае использованы до инициализации графического режима процедурой InitGraph.

 

2.1.2.11        Инкапсуляция файлов графических
 драйверов и шрифтов в исполняемый файл

 

Инкапсуляция драйвера

function RegisterBGIdriver(driver: pointer): Integer;

Регистрирует загруженный в оперативную память графический драйвер

Инкапсуляция шрифта

function RegisterBGIfont(Font: pointer): Integer;

Регистрирует загруженный в оперативную память шрифт

 

Значительные неудобства в графических программах на ТП доставляют графические драйверы и шрифты, которые приходится копировать вместе с программой при изменении ее местоположения. Для решения данной проблемы можно скомпоновать файл драйвера или шрифта c исполняемым файлом, а затем воспользоваться специальными функциями регистрации. Функции регистрации можно использовать также при "ручной" (т.е. методами работы с файлами) загрузке шрифтов или драйверов в оперативную память.

Для компоновки шрифтов или драйверов с программой следует сначала перевести их в объектный вид (т.е. в файл с расширением ".OBJ") с помощью утилиты BINOBJ.EXE, входящей в состав ТП:

binobj.exe trip2.chr trip2 trip2font

где первый параметр - имя файла шрифта или драйвера, второй - имя получаемого OBJ-файла, третий - глобальное имя процедуры, которое позднее будет использоваться для регистрации шрифта или драйвера.

Затем следует скомпоновать получившийся объектный файл с программой в разделе объявления процедур и функций. При этом также следует определить внешнюю процедуру с именем, использованным при формировании OBJ-файла:

procedure trip2font;external; {определение процедуры}

{$L trip2.obj} {компоновка с программой}

И, наконец, для регистрации скомпонованного шрифта или драйвера следует воспользоваться функциями RegisterBGIfont и RegisterBGIdriver:

function RegisterBGIfont(Font: pointer): Integer;

function RegisterBGIdriver(driver: pointer): Integer;

где Font и driver являются указателями на глобальные процедуры скомпонованных шрифта/драйвера, а возвращаемое функцией значение содержит либо код ошибки (<0), либо идентификационный номер шрифта/драйвера. Полученный идентификационный номер можно использовать при инициализации графического режима процедурой InitGraph (для драйвера) или установки шрифта
SetTextStyle (для шрифта).

 


 

2.2 Объектно-ориентированное программирование (ООП)

 

2.2.1        Основные парадигмы ООП.

 

Объектно-ориентированное программирование (сокращенно ООР) является методом программирования, основанном на понятиях класса (объектного типа) и объекта (экземпляра класса).

Классом (объектным типом) называется специальный тип данных, включающий в себя как данные, так и методы обработки этих данных. В некотором смысле класс очень похож на тип записи ТП.

Объектом (экземпляром класса) называется переменная объектного типа.

Полями класса называются данные класса.

Методами класса называются процедуры и функции класса, с помощью которых ведется обработка полей.

 

Объектно-ориентированное программирование характеризуют три основных свойства классов:

- ИНКАПСУЛЯЦИЯ. Это объединение данных со средствами их обработки. Таким образом можно сказать, что класс - это множество с определенными на нем операциями.

- НАСЛЕДОВАНИЕ. Это определение класса и затем использование его для построения иерархии производных классов, причем каждый производный класс ("потомок") наследует доступ к коду и данным всех своих "прародителей" и добавляет свои собственные.

- ПОЛИМОРФИЗМ. Это переопределение классом-потомком методов предка таким образом, что полностью или частично изменяются реакции предка на те или иные события, меняется действие методов класса прародителя (прародителей). Под переопределением понимается определение в потомке метода с таким же именем, как и в предке.

 


 

2.2.2        Реализация ООП средствами Турбо-Паскаля.

2.2.2.1              Класс Турбо-Паскаля

 

Класс ::=

"object" ["("класс_предок")"]

{

["private" | "public"]

поля

заголовки_методов

}

"end".

Поля ::= определение_переменных. (см. часть 1, параграф 1.3.3.3)

Заголовки_методов ::=

{заг_проц_или_функции ";" ["virtual" [целое_число]]}

Заголовки процедур и функций определяются стандартно (см. часть 1, параграфы 1.3.3.3 и 1.8.1)

Раздел public предназначен для полей и данных, которые видны программисту как внутри текущего модуля, так и вне его. Данный раздел определен по умолчанию. Раздел private предназначен для личных данных класса. Его содержимое доступно только внутри модуля, в котором описан класс.

После определения класса программист обязан определить (в разделе процедур и функций) тела всех методов класса. Определение методов класса очень схоже с определением процедур и функций. Единственное отличие заключается в имени класса и точке, которые ставятся перед именем метода.

Определение_метода_класса::=

("procedure" ИД_класса "." ИД_процедуры

["(" объявление_параметра

{";" объявление_параметра }")"] ";")

|

("function" ИД_класса "." ИД_функции

["(" объявление_параметра

{";" объявление_параметра }")"] ":" ИД_типа ";").

Объявление параметров происходит стандартно (см. часть 1, параграф 1.8.1)

Извне доступ к полям и методам переменной любого класса осуществляется аналогично доступу к полям записи (имя переменной отделяется от метода или поля точкой). Внутри метода класса доступ к полям и методам этого же класса осуществляется напрямую, без использования точки.

Если класс-потомок определяет метод с таким же именем, как внутри предка, то такое действие называется переопределением метода. Тогда внутри метода класса-потомка можно вызвать метод класса-предка с использованием зарезервированного слова inherited.

 

Задача 3. Определить класс Личность и порожденный от него класс Студент.

Type

 Person = object {Класс Личность }

Age:integer; {возраст}

Man:boolean; {пол}

procedure Init(_Age:integer; _Man:boolean); {метод создания человека (заполнение полей)}

function IsWorking:boolean; {Метод-функция, определяющая, может ли человек работать (по возрасту) или нет}

function InfoString:string; {возвращает фразу о типе класса}

function IsWorkingString:string; {возвращает фразу о трудоспособности}

function ManString:string; {возвращает фразу о поле}

function AgeString:string; {возвращает фразу о возрасте}

 end;

 

PersonStudent = object (Person) {Класс Студент, обладает всеми свойствами Личности }

 procedure Init(_Age:integer; _Man:boolean); {перекрывающий метод, добавляет возможность вывода сообщения, если происходит попытка создать нетрудоспособного студента }

 function InfoString:string; {перекрывающий метод, возвращает фразу о типе класса}

end;

 

{ раздел объявления процедур и функций }

{ методы класса Person }

procedure Person.Init(_Age:integer; _Man:boolean);

 begin

 Age:=_Age;

 Man:=_Man;

 end;

 

function Person.IsWorking:boolean;

 begin

 IsWorking:=true;

 if age<14 then IsWorking:=false

else

 if man then begin

 if age>60 then IsWorking:=false;

 end

 else if age>55 then IsWorking:=false;

 end;

 

function Person.IsWorkingString:string;

var s:string;

begin

if IsWorking then s:='' else s:='не ';

if Man then IsWorkingString:=s+'трудоспособен'

else IsWorkingString:=s+'трудоспособна'

end;

 

function Person.ManString:string;

begin if Man then ManString:='пол - мужской'

else ManString:='пол - женский'; end;

 

function Person.AgeString:string;

var s:string;

begin

str(age,s);

case age mod 10 of

1: s := s+' год';

2,3,4: s := s+' года'

else s := s+' лет';

end;

AgeString:='возраст – '+s;

end;

 

function Person.InfoString:string;

begin InfoString:='Этокласс "Личность"'; end;

 

{ метод класса PersonStudent }

procedure PersonStudent.Init(_Age:integer; _Man:boolean);

begin

 inherited init(_age,_man);

 if not isworking then writeln(age, ‘ – Возраст не студенческий’);

end;

 

function PersonStudent.InfoString:string;

begin InfoString:='Это – класс "Студент"'; end;

 

 

{ переменные для основной программы }

var a,b,c:Person; a1,b1:PersonStudent;

 

{основная программа}

 

begin

writeln('Создаем личность – десятилетний мальчик');

 a.init(10,true);

 writeln(a.InfoString);

 writeln(a.IsManString); writeln(a.IsAgeString); writeln(a.IsWorkingString);

 readln;

writeln('Создаем личность – восемнадцатилетняя девушка');

 b.init(18,false);

 writeln(b.InfoString);

 writeln(b.IsManString); writeln(b.IsAgeString); writeln(b.IsWorkingString);

 readln;

writeln('Создаем личность – семидесятилетний мужчина');

 c.init(70,true);

 writeln(c.InfoString);

 writeln(c.IsManString); writeln(c.IsAgeString); writeln(c.IsWorkingString);

 readln;

 

writeln('Создаем студента – десятилетний мальчик ???');

 a1.init(10,true);

 writeln(a1.InfoString);

 writeln(a1.IsManString); writeln(a1.IsAgeString); writeln(a1.IsWorkingString);

 readln;

 

writeln('Создаем студента – двадцатилетний юноша');

 b1.init(20,true);

 writeln(b1.InfoString);

 writeln(b1.IsManString); writeln(b1.IsAgeString); writeln(b1.IsWorkingString);

 readln;

end.

 

Внутри любого метода класса существует возможность использовать псевдопеременную self, которая собственно и является данным объектом. Для получения указателя на текущий объект можно воспользоваться оператором @: @self.

Методы классов могут быть статическими и виртуальными процедурами и функциями. Виртуальный метод обычно переопределяется внутри объекта-потомка в рамках полиморфизма (допускается также переопределение статических методов, однако свойства объекта-предка при этом не изменяются). Статические методы никак не выделяются. Виртуальные методы выделяются с помощью зарезервированного слова "virtual", которое ставится после заголовка метода внутри класса. За словом "virtual" может следовать целое число, с помощью которого изменяется внутренний способ вызова метода. Такие методы называются динамическими. Динамические и виртуальные методы отличаются друг от друга только способом вызова: виртуальные вызываются быстрее, но при их использовании объект занимает больше места в оперативной памяти, динамические вызываются медленнее, но экономят память. В дальнейшем, говоря о виртуальных методах, мы будем подразумевать как виртуальные, так и динамические методы.

Если метод однажды определен виртуальным, то во всех потомках при переопределении он также обязан быть виртуальным (аналогично для динамических методов). При этом количество и тип параметров в методах потомка и предка обязаны совпадать "один в один". При переопределении статических методов параметры могут не совпадать. Если однажды в иерархии классов статический метод переопределен виртуальным, то его дальнейшее переопределение возможно только по правилам виртуальных методов.

Обращение к виртуальным методам производится через особую таблицу виртуальных методов (VMT), определенную для каждого класса. Такое обращение позволяет реализовать полиморфизм, но замедляет вызов методов.

 

Объектный тип ТП поддерживает также специальные методы-процедуры, которые называются констракторами и дестракторами.

Констрактором называется специальная процедура – метод класса, с помощью которой производится инициализация объекта. Констракторы вызываются перед началом работы с объектом. Констракторы не могут быть виртуальными. Внутри тела констрактора обычно первым же оператором вызывают констрактор предка. Если класс имеет виртуальные методы, то перед их вызовом обязательно должен быть вызван констрактор, который дополнительно к инициализации объекта свяжет его с таблицей виртуальных методов класса. Специальная процедура fail позволяет прервать выполнение констрактора и провести деинициализацию, если произошла ошибка.

Дестрактором называется специальная процедура – метод класса, с помощью которой производится завершение работы с объектом. После вызова дестрактора структура объекта разрушается вместе с таблицей виртуальных методов. Дестракторы чаще всего бывают виртуальными.

Определение переменных объектного типа и обращение к ним происходит аналогично записям (то есть с помощью символа "." или оператора With).

Присваивание объектов друг другу разрешается в случае, если объекты принадлежат к одному и тому же классу, или в случае, когда в левой части присваивания стоит объект, являющийся предком второго объекта. В процессе присваивание копируется только содержимое полей объекта, т.е. автоматического вызова констрактора объекта левой части присваивания не происходит, и последующий вызов виртуальных методов такого объекта (если, конечно он заранее не был инициализирован констрактором) завершится ошибкой. Серьезные проблемы могут также возникнуть при попытке скопировать содержимое динамического объекта, действительный тип которого, как будет показано ниже, часто не совпадает с типом переменной, в которой хранится объект. Поэтому в присваивании для объектов следует придерживаться следующего правила: присваивание возможно только для статических объектов одного класса, в котором отсутствуют виртуальные методы.

 

Задача 4. Создать два объекта - точка и двоеточие на текстовом экране. Предусмотреть показ, скрытие и перемещение объекта.

uses crt;

type

 txtPoint=object {объект "точка"}

  cod : byte;

  x,y : byte; {координаты точки}

  procedure init; {инициализировать точку}

  procedure switchon; {включить точку}

  procedure switchoff; {выключить точку}

  procedure move_to(newA,newB: byte); {переместить точку}

  end;

 

 txt2Point=object(txtPoint) {объект "двоеточие" - наследник объекта точка}

  procedure init; {инициализировать двоеточие}

  procedure switchon; {включить точку}

  procedure switchoff; {выключить точку}

 end;

{------------------------------------------------}

 procedure txtPoint.init;

 begin

  x:=1; y:=1; cod:=46; {код точки}

 end;

 procedure txtPoint.switchon;

 begin

  gotoxy(x,y); write(chr(cod));

 end;

 procedure txtPoint.switchoff;

 begin

  gotoxy(x,y); write(' ');

 end;

 procedure txtPoint.move_to(newA,newB: byte);

 begin

  switchoff;

  x:=newa; y:=newb;

  switchon;

 end;

{------------------------------------------------}

 procedure txt2Point.init;

 begin

  x:=40; y:=12; cod:=58; {код двоеточия}

 end;

 procedure txt2Point.switchon;

 begin

  gotoxy(x,y); write(chr(cod)+chr(cod)+chr(cod));

 end;

 procedure txt2Point.switchoff;

 begin

  gotoxy(x,y); write(' ');

 end;

{------------------------------------------------}

var

 vPoint: txtPoint;

 v2Point: txt2Point;

 c:char;

begin

 clrscr;

 vPoint.init; {инициализировать объект - "точка"}

 v2Point.init; {инициализировать объект - "двоеточие"}

 vPoint.switchon; {высветить точку}

 v2Point.switchon; {высветить двоеточие}

 c:=readkey; {ждем нажатие клавиши}

{ ---- начало некорректно работающего блока ---- }

 vPoint.move_to(1,1); {переместить точку}

 v2Point.move_to(10,10); {переместить двоеточие}

{ ---- конец некорректно работающего блока ---- }

 c:=readkey; {ждем нажатие клавиши}

end.

 

Подробнее рассмотрим процедуру txtPoint.move_to.

procedure txtPoint.move_to(newA,newB: byte);

 begin

 switchoff; { гашение точки }

 x:=newa; y:=newb;

 switchon; { показ точки }

 end;

Методы точки switchoff и switchon являются статическими, а не виртуальными, в результате чего в основной программе появляется некорректно работающий блок:

 vPoint.move_to(1,1); {переместить точку}

 v2Point.move_to(10,10); {переместить двоеточие}

где из двух вызовов методов правильно работает только первый. Второй вместо перемещения двоеточия перемещает точку (гасится двоеточие и на новом месте показывается точка).

Для решения проблемы следует определить методы switchoff и switchon виртуальными:

txtPoint=object {объект "точка"}

 

 procedure switchon; virtual; {включить точку}

 procedure switchoff; virtual; {выключить точку}

 

end;

 

txt2Point=object(txtPoint) {объект "двоеточие" }

 

 procedure switchon; virtual;{включить точку}

 procedure switchoff; virtual;{выключить точку}

 

end;

Тогда в методе txtPoint.move_to будет происходить вызов не switchoff и switchon точки, а switchoff и switchon того экземпляра класса, для которого вызывается метод.

 

Любой класс может иметь любое количество потомков, но только одного непосредственного прародителя. При этом любой из потомков имеет доступ ко всем полям и методам прародителей (исключение составляет раздел private, когда предок и потомок находятся в разных модулях).

Принципы ООП гласят, что непосредственно к полям класса или объекта следует обращаться как можно реже и где только возможно для доступа к полям должны быть использованы методы класса или объекта.

Программный проект (7)

Наиболее эффективным методом организации графических примитивов в редакторе будет объектно-ориентированный подход. Прежде всего требуется создать первоначальный, абстрактный графический класс, в котором будут инкапсулированы основные поля и методы, общие для всех примитивов.

{ модуль GrPrim.pas }

type

{ абстрактный графический класс }

   pGrObject=^TGrObject;

   tgrObject=Object(tObject)

    constructor Init; { инициализирует объект }

    function Gettype:integer;virtual;

     { возвращает вид класса (константы определены выше) }

    procedure Show;virtual; { показывает объект }

    procedure XorShow;virtual;

     { показывает объект с операцией XOR (используется для

       интерактивного перемещения и изменения размеров объекта) }

    procedure Move(dx,dy:integer);virtual;{перемещает объект на DX,DY}

    function PointIn(x,y:integer):boolean;virtual;

     { возвращает TRUE, если точка с координатами (X,Y)

       попала в пределы объекта }

    procedure SaveASText(var f:text);virtual;

     {запись в текстовом формате}

    constructor load(var s:tstream); {чтение из потока}

    procedure store(var s:tstream); {запись в поток}

    procedure disable; { запрещает показ примитива из коллекции }

    procedure enable; { разрешает показ примитива из коллекции }

    private

     Disabled:boolean;

   end;

Большинство из этих методов являются абстрактными, то есть никогда не вызываются для экземпляра класса tgrObject, и перекрываются в классах-потомках. Собственно, никогда не будет создан экземпляр класса tgrObject, хотя указатели на данный класс будут использоваться повсеместно. Класс tgrObject порожден от TObject, определенном в модуле Objects. Класс TObject является базовым для любых классов, которые в дальнейшем будут использовать мощную иерархию классов модуля Objects (см. параграф 2.2.2.3)

Рассмотрим некоторые методы класса.

function TGrObject.Gettype:integer;virtual;

Функция, возвращающая одну из констант определенных в фазе 6 на стр. 38. Данная функция обязательно должна быть перекрыта в потомках класса TGrObject. С ее помощью мы будем определять, о переменной какого конкретно класса идет речь. Дело в том, что, как будет показано в следующем параграфе, могут существовать переменные-ссылки одного класса, указывающие на экземпляр класса-потомка от данного класса. Таким образом, мы будем работать в большинстве случаев с переменными типа PGrObject, которые, на самом деле, указывают на реальные графические примитивы – линии, окружности и т.д. В результате такого свойства классов, очень часто нельзя точно определить, на переменную какого класса ссылается тот или иной указатель. Для решения данной проблемы и будет использована функция GetType.

procedure TGrObject.Show;virtual;

Процедура, визуализирующая объект. Обязательно должна перекрываться в потомках. Каждый примитив показывает себя на экране отличным от других примитивов способом.

procedure TGrObject.XorShow;virtual;

Для интерактивного построения примитивов в графическом редакторе, мы будем пользоваться XorShow. С ее помощью можно нарисовать изображение, а вторым вызовом стереть его, полностью восстановив первоначальный фон. Так будет достигнут эффект мультипликации при построении и перемещении примитивов. Процедура XorShow может не перекрываться в примитивах, использующих при своей визуализации только стиль линии и режим вывода на экран. Это касается линий и многоугольников.

procedure TGrObject.Move(dx,dy:integer);virtual;

Перемещает примитив относительно его текущего местоположения на dx, dy. В обязательном порядке перекрывается в потомках.

function TGrObject.PointIn(x,y:integer):boolean;virtual;

Используется при интерактивном выборе примитива на экране в графическом редакторе. Возвращает Истину, если переданная точка (обычно - координаты мыши), попадает в пределы примитива. Всегда перекрывается в потомках.

procedure TGrObject.SaveASText(var f:text);virtual;

Сохраняет примитив в текстовом файле в виде вызовов графических процедур и функций. Предполагается, что файл заранее открыт для записи.

constructor TGrObject.load(var s:tstream);

procedure TGrObject.store(var s:tstream);

Чтение и запись примитива в своем собственном формате. Более подробно будет рассмотрено при изучении потоков (параграф 2.2.2.3.1).

procedure TGrObject.disable;

procedure TGrObject.enable;

Запрещает и разрешает показ примитива из графической коллекции. Понятие коллекции будет рассмотрено в параграфе 2.2.2.3.2, а на текущий момент нам достаточно знать, что коллекция – это объектный тип, в котором могут хранится другие объекты, своего рода аналог динамического массива в ООП. В дальнейшем мы создадим специализированную коллекцию, которая будет хранить в себе и манипулировать набором графических примитивов. Методы disable и enable изменяют значение поля disabled, которое анализируется при показе всех примитивов в коллекции.

Теперь нам известно достаточно, чтобы создать примитивы "Точка" и "Линия", а также реализовать все методы абстрактного графического примитива:

{ модуль GrPrim.pas }

{ класс "Точка" }

   PGrPoint=^TGrPoint;

   TGrPoint=Object(TGRObject)

    point: prPoint; {координаты точки}

    color: word; {ее цвет}

    constructor init(x,y:integer;c:word);

    procedure Show;virtual;

    function Gettype:integer;virtual;

    procedure Move(dx,dy:integer);virtual;

    function PointIn(x,y:integer):boolean;virtual;

    procedure saveASText(var f:text);virtual;

    constructor load(var s:tstream);

    procedure store(var s:tstream);

   end;

 

{ абстрактный промежуточный класс, предназначенный для хранения и обработки стиля линий }

   TAbstractLS=Object(TGRObject)

    Style:prLineStyle; {стиль}

    constructor Init(ls,up,tn,c:word);

    procedure SetLS; { установка стиля }

    procedure SaveASText(var f:text);virtual;

    constructor load(var s:tstream);

    procedure store(var s:tstream);

   end;

 

{ класс "Отрезок" ("Линия") }

   PGrLine=^TGrLine;

   TGrLine=Object(TAbstractLS)

    CoordLine:prRect; {координаты отрезка}

    constructor init(x1,y1,x2,y2:integer;ls,up,tn,c:word);

    procedure Show;virtual;

    function Gettype:integer;virtual;

    procedure Move(dx,dy:integer);virtual;

    function PointIn(x,y:integer):boolean;virtual;

    constructor load(var s:tstream);

    procedure store(var s:tstream);

    procedure saveASText(var f:text);virtual;

   end;

implementation

uses inter;

{ ------------ TGrObject ------------- }

    procedure TGRObject.disable;

     begin disabled:=true; end;

 

    procedure TGRObject.enable;

     begin disabled:=false; end;

 

    procedure TGRObject.XorShow;

     begin SetWriteMode(XorPut); show; SetWriteMode(NormalPut); end;

 

    constructor TGRObject.Init;

     begin Inherited Init; end;

 

    function TGRObject.GetType:integer;

     begin GetType:=-1; end;

 

    procedure TGRObject.show;

     begin abstract; end;

 

    procedure TGRObject.Move(dx,dy:integer);

     begin abstract; end;

 

    function TGRObject.PointIn(x,y:integer):boolean;

     begin abstract; end;

 

    procedure TGRObject.saveAsText(var f:text);

     begin abstract; end;

 

    constructor TGRObject.load(var s:tstream);

      begin  s.Read(disabled,sizeof(disabled));  end;

 

    procedure TGRObject.store(var s:tstream);

     begin s.Write(disabled,sizeof(disabled));  end;

 

  { ------------ TAbstractLS ------------- }

    constructor TAbstractLS.Init(ls,up,tn,c:word);

     begin

      inherited init;

      with style do begin

         color:=c; thickness:=tn; linestyle:=ls; pattern:=up;

      end;

     end;

 

    procedure TAbstractLS.SetLS;

     begin

       with Style do begin

        SetLineStyle(linestyle,pattern,thickness); SetColor(color);

       end;

     end;

 

     procedure TAbstractLS.saveAsText(var f:text);

      begin

       with style do begin

        writeln (f,

         '    SetLineStyle(',LineStyle,',',Pattern,',',Thickness,')',

         ';','{Установка стиля линий}');

        writeln (f,'    SetColor(',Color,')',';','{Установка цвета линий}');

       end;

      end;

 

    constructor TAbstractLS.load(var s:tstream);

     begin

       inherited load(s); s.read(style,sizeof (style));

     end;

 

    procedure TAbstractLS.store(var s:tstream);

     begin

       inherited store(s); s.write(style,sizeof (style));

     end;

 

  { ------------ TGrPoint ------------- }

    constructor TGrPoint.Init(x,y:integer;c:word);

      begin Inherited Init; point.x:=x; point.y:=y; color:=c; end;

 

    procedure TGrPoint.Show;

       begin with Point do PutPixel(x,y,color); end;

 

    function TGrPoint.GetType:integer;

      begin GetType:=Typepoint; end;

 

    procedure TGrPoint.Move(dx,dy:integer);

      begin with Point do begin inc(x,dx);inc(y,dy); end; end;

 

    function TGrPoint.PointIn(x,y:integer):boolean;

       var r:trect;

      begin r.assign(x,y,x,y); r.grow(2,2); pointIn:=r.contains(point); end;

 

    constructor TGrPoint.load(var s:tstream);

      begin

       inherited load(s); s.read(point,sizeof (point)); s.read(color,2);

      end;

 

    procedure TGrPoint.store(var s:tstream);

      begin

       inherited store(s); s.write(point,sizeof (point));

       s.write(color,sizeof (color));

      end;

 

    procedure TGrPoint.saveAsText(var f:text);

      begin

       writeln(f,'    {Рисуется точка}');

       with point do writeln (f,'    putpixel','(cx+',x,',','cy+',y,',',color,');');

      end;

 

{ ------------ TGrLine ------------- }

      constructor TGrLine.Init(x1,y1,x2,y2:integer;ls,up,tn,c:word);

      begin

       Inherited Init(ls,up,tn,c); CoordLine.assign(x1,y1,x2,y2);

      end;

 

    procedure TGrLine.Show;

      begin SetLS; with CoordLine do line(a.x,a.y,b.x,b.y); end;

 

    function TGrLine.GetType:integer;

      begin GetType:=Typeline; end;

 

    procedure TGrLine.Move(dx,dy:integer);

      begin CoordLine.Move(dx,dy); end;

 

    function TGrLine.PointIn(x,y:integer):boolean;

       var r:prrect;

      begin

       r.assign(x,y,x,y); r.grow(2,2);

       pointIn:=LineIntersectRect(CoordLine,r);

      end;

 

    constructor TGrLine.load(var s:tstream);

      begin

       inherited load(s);

       s.read(CoordLine, sizeof (CoordLine));

      end;

 

      procedure TGrLine.store(var s:tstream);

      begin

       inherited store(s);

       s.write(CoordLine,sizeof (CoordLine));

      end;

 

      procedure TGrLine.saveAsText(var f:text);

      begin

       writeln(f,'    {Рисуется линия}');

       inherited saveAsText(f);

       with CoordLine do

         writeln (f,'     Line(cx+',a.x,',','cy+',a.y,',','cx+',b.x,',','cy+',b.y,')',';');

       writeln(f);

      end;

Рассмотрим подробнее некоторые из методов, при реализации которых могут возникать различные вопросы.

1) Обращает на себя внимание активное использование методов класса prRect, который на самом деле, как видно из фазы 3 программного проекта, является классом TRect,определенным в модуле Objects.

Вот так выглядит его заголовок:

TRect = object

    A, B: TPoint;

    procedure Assign(XA, YA, XB, YB: Integer);

    procedure Copy(R: TRect);

    procedure Move(ADX, ADY: Integer);

    procedure Grow(ADX, ADY: Integer);

    procedure Intersect(R: TRect);

    procedure Union(R: TRect);

    function Contains(P: TPoint): Boolean;

    function Equals(R: TRect): Boolean;

    function Empty: Boolean;

  end;

Как видно из заголовка, класс обладает множеством методов для манипуляции над прямоугольником. Рассмотрим некоторые из них:

procedure TRect.Assign(XA, YA, XB, YB: Integer);

Заполняет поля A и B прямоугольника соответственно значениями XA, YA и XB, YB.

procedure TRect.Move(ADX, ADY: Integer);

Перемещает прямоугольник на ADX, ADY

procedure TRect.Grow(ADX, ADY: Integer);

Увеличивает (уменьшает) прямоугольник на ADX, ADY относительно его центра.

function TRect.Contains(P: TPoint): Boolean;

Возвращает ИСТИНУ, если переданная точка находится внутри прямоугольника.

Многие методы класса TRect подразумевают, что в точке A находится левый верхний угол прямоугольника, а в точке B – правый нижний. При интерактивном создании примитива "прямоугольник" данное правило не выполняется примерно в 75% случаев, в результате чего методы TRect будут работать неверно. Для решения данной проблемы можно произвести "нормализацию" нестандартного прямоугольника процедурой NormalizedRect из модуля inter.pas (см. фазу 0 программного проекта)

2) Конструкторы Init. Обязателен вызов конструктора предка перед установкой полей:

constructor TGrLine.Init(x1,y1,x2,y2:integer;ls,up,tn,c:word);

 begin Inherited Init(ls,up,tn,c); CoordLine.assign(x1,y1,x2,y2); end;

Как видно из примера, вызов конструктора предка инициализирует поле Style, а только затем инициализируется поле CoordLine.

 

3) Особенности методов PointIn.

Метод PointIn используется для интерактивного выбора графического примитива манипулятором "мышь". Используя подобный "ручной" выбор примитива, очень сложно попасть курсором точно на точку или линию. Поэтому будем считать, что точка выбора является на самом деле небольшим (5*5 точек) прямоугольником:

function TGrPoint.PointIn(x,y:integer):boolean;

       var r:trect;

      begin r.assign(x,y,x,y); r.grow(2,2); pointIn:=r.contains(point); end;

function TGrLine.PointIn(x,y:integer):boolean;

       var r:prrect;

      begin

       r.assign(x,y,x,y); r.grow(2,2);

       pointIn:=LineIntersectRect(CoordLine,r);

      end;

Функция LineIntersectRect, используемая в методе TGrLine.PointIn, позволяет определить, пересекает ли линия прямоугольник. Функция определена в модуле inter.pas (см. фазу 0 программного проекта) и имеет следующий заголовок:

function LineIntersectRect(var oLine,oRect:TRect):boolean;

где oLine – координаты линии, oRect – координаты прямоугольника.

 

Задания для программного проекта (49-59).

Задание 49. Создайте программу, в которой инициализируются, показываются и деинициализируются три объекта класса TGrPoint и два объекта класса TGrLine. Координаты и цвет примитивов выбираются случайным образом.

Задание 50. Создайте и отобразите объект класса TGrLine. Случайным образом заполняйте экран точками до тех пор, пока очередная точка не окажется на созданной линии. Нарисуйте окружность радиусом в 5 единиц и центром в последней точке.

Задание 51. Используя модуль Mouse.pas (см. фазу 0 программного проекта) и программную заготовку вида:

uses mouse, graph, …;

var x,y:integer; left,right:boolean;

const Quit:boolean=false;

begin

 {инициализация графики}

 

 ChkAndReset;

 repeat

  GetMouseState(x,y,left,right);

  {набор действий}

 until Quit;

 readln;

 CloseGraph;

end.

создать программу с двумя объектами TGrLine, выход из которой производится по щелчку мыши на одной из линий.

Задание 52. Используя объект класса TGrLine для секундной стрелки, организуйте секундомер в центре экрана (см. задание 11).

Задание 53*[1]. Определите класс TGrRect (прямоугольник)[2]. Создайте программу для проверки правильности работы всех его методов, в том числе и методов предков (для проверки метода PointIn можете воспользоваться заготовкой из задания 51). Не определяйте методов Load и Store.

Задание 54*. Решите задание 53 для класса TGrCircle (окружность)

Задание 55*. Решите задание 53 для класса TGrEllipse (эллипс)

Задание 56*. Решите задание 53 для класса TGrPoly (ломаная)

Задание 57*. Решите задание 53 для классов TGrBar (закрашенный прямоугольник), TGrFillCircle (закрашенная окружность), TGrFillEllipse (закрашенный эллипс).

Задание 58**[3]. Решите задание 53 для класса TGrFillPoly (закрашенный многоугольник). Особую сложность в реализации представляет собой метод PointIn.

Задание 59**. Решите задание 53 для классов TGrArcCircle (дуга окружности), TGrArcEllipse (дуга эллипса), TGrText (строка), TGrPicture (рисунок), TGrFill (заливка).

 

2.2.2.2              Динамические объекты.

 

Как и любые переменные, объекты в ТП могут быть динамическими, т.е. в виде указателей на объекты. Использование динамических объектов позволяет во всей полноте применять свойство полиморфизма.

На текущий момент в модуле GrPrim определено несколько таких указателей:

{ модуль GrPrim.pas }

  type

pGrObject=^TGrObject;

pGrPoint=^TGrPoint;

pGrLine=^TGrLine;

Для выделения памяти под динамический объект стандартная процедура New расширена в двух направлениях:

1) Выделение памяти под объект происходит одновременно с вызовом констрактора объекта. При это процедура New принимает два параметра – указатель на объект и вызов констрактора для типа данного объекта.

Создадим программу, в которой определяются три динамических объекта различных типов, последовательно инициализируются различными методами, показываются и перемещаются по экрану.

uses GrPrim, graph, crt;

 var pA:pGrObject; pB:pPoint; pC:PLine;

 gd, gm:integer;

begin

 gd:=detect; initgraph(gd,gm,''); if graphresult<>grOk then halt(1);

New(pB,Init(34,40,15));

 {создается динамический объект "Точка" в координатах 34,40 белым цветом}

 ...

2) Выделение памяти и вызов констрактора может производиться для одного объекта, а присвоение указателя - объекту, обычно являющемуся предком данного. В данном случае New является функцией, возвращающей указатель на объект.

 ...

 pC:=new(pGrline,init(10,10,100,250,0,0,1,14);

{создается динамический объект "Линия" в координатах 10,10,100,250, сплошная, нормальной толщины, желтым цветом}

 pA:=new(pGrline,init(100,100,20,305,1,0,3,15);

{создается динамический объект "Линия" в координатах 100,100,20,305, точечная, толстая, белым цветом. В результате pA (указатель на абстрактный графический примитив) указывает, в действительности, на объект "Линия"}

 ...

Второй способ выделения памяти является более предпочтительным, так как способствует унификации взаимодействия с иерархией классов.

Продолжаем пример:

pa^.Show;

pb^.Show;

pc^.Show;

readkey;

cleardevice;

pa^.Move(100,10);

pb^.Move(100,10);

pc^.Move(100,10);

pa^.Show;

pb^.Show;

pc^.Show;

readkey;

Благодаря тому, что методы Move и Show являются виртуальными (обладают свойством полиморфизма), в примере для pA будут вызваны Move и Show класса TGrLine, а не TGrObject.

Для освобождения памяти служит процедура Dispose, расширенная для использования вместе с дестрактором.

Завершаем пример:

 ...

 Dispose(pa,done);

 Dispose(pb,done);

 Dispose(pс,done);

 closegraph;

end.

В любой серьезной программе предпочтение отдают динамическим, а не статическим объектам. Прежде всего это связано с мощными возможностями полиморфизма, которые в полной мере проявляются только при использовании динамических объектов. Хорошо спроектировав первоначальный класс – корень иерархии классов, можно построить программу или библиотеку процедур и функций, ориентированную на обработку динамических объектов данного класса. В дальнейшем, при добавлении нового класса, изменять код таких процедур и функций не потребуется, так как они одинаково хорошо будут работать с любым классом, порожденным от исходного. Динамические объекты настолько прочно вошли в практику программирования, что из языка Object Pascal (в среде Delphi), были полностью удалены статические объекты, в результате чего синтаксис обращения к динамическим объектам был значительно упрощен.

В разрабатываемом графическом редакторе повсеместно применяются динамические объекты, причем в подавляющем большинстве случаев ведется обработка переменных типа pGrObject. Таким образом, почти не требуется вносить изменения в программу при добавлении нового графического примитива (исключение составляют точки кода, в которых происходит создание примитивов и некоторые специфичные действия над их полями).


 

2.2.2.3              Модуль OBJECTS.

Для работы с объектами в поставку Турбо-Паскаля входит модуль Objects.

Иерархия объектов, сосредоточенных в модуле Objects (в сокращенном виде)

 

Класс TObject является стартовым объектом иерархии. Все классы, рассчитанные на использование коллекций (TCollection и порожденные от него), ресурсов (TResourceFile и порожденные от него) и потоков (TStream и порожденные от него) должны быть порождены от TObject.

TObject = object

    constructor Init;

    procedure Free;

    destructor Done; virtual;

end;

Инициализация объекта типа TObject производится с помощью конструктора Init (при этом производится обнуление всех полей объекта), завершение – дестрактором Done. Процедура Free чаще всего используется для уничтожения динамического объекта:

procedure TObject.Free;

 begin Dispose(PObject(@Self), Done); end;

Из тела метода Free очевидно, что ее вызов для статического объекта выдаст ошибку времени выполнения.

 

 

2.2.2.3.1          Потоки

Потоки (класс TStream и все его потомки) – это универсальное средство для хранения любых данных (в том числе и объектов) на диске, в памяти или на любом устройстве, которое поддерживает ввод-вывод данных.

Класс TStream – абстрактный класс, описывающий основные поля и методы для работы с потоком.

Класс TEmsStream – поток в EMS-памяти.

Класс TMemoryStream – поток в обычной (conventional) памяти.

Класс TDosStream – поток в файле на диске.

Класс TBufStream – буферизованный поток в файле на диске.

Работа с потоком состоит из трех этапов:

1.     Создание (открытие) специализированного потока.

2.     Сохранение или чтение данных

3.     Уничтожение (закрытие) потока

Методы работы с потоком очень схожи с файловым вводом-выводом, однако имеются и серьезные отличия:

1.     Унификация чтения-записи данных. Запись и чтение производится едиными методами для потока любого типа. Т.о. одна и та же процедура без модификаций может обращаться к потоку на диске, в оперативной памяти, в портах ввода-вывода и т.д.

2.     Файловые потоки отличаются большей гибкостью, чем стандартный файловый ввод-вывод. Увеличилось количество режимов открытия файлов, специализированных методов для записи-чтения данных отдельных типов.

 

TStream

Поток типа TStream является абстрактным предком для всех остальных классов потоков. Программист никогда не создает экземпляров класса TStream, используются только его потомки, где в обязательном порядке перекрываются методы чтения, записи, обработки позиции и размера потока, обработки ошибок.

Поля класса TStream:

Status: Integer;

Ошибка последнего обращения к потоку

В модуле Objects определены следующие константы для данного поля:

 stOk = 0; { Нет ошибки }

 stError = -1; { Ошибка доступа }

 stInitError = -2; { Невозможно инициализировать поток}

 stReadError = -3; { Чтение данных за концом потока}

 stWriteError = -4; { Невозможно расширить поток}

 stGetError = -5;   { Чтение из потока объекта незарегистрированного типа }

 stPutError = -6;   { Запись в поток объекта незарегистрированного типа }

ErrorInfo: Integer;

Дополнительная информация об ошибке, если Status<>stOk.

Методы класса TStream:

constructor Init;

Инициализирует поток.

procedure CopyFrom(var S: TStream; Count: Longint);

Копирует Count байт из потока S в текущий поток. Копирование производится с текущей и в текущую позицию.

procedure Error(Code, Info: Integer); virtual;

Вызывается при возникновении ошибки.

function Get: PObject;

Читает зарегистрированный ранее объект из потока

procedure Put(P: PObject);

Сохраняет зарегистрированный раннее объект в потоке

function GetPos: Longint; virtual;

Возвращает текущую позицию указателя в потоке

function GetSize: Longint; virtual;

Возвращает размер потока

procedure Read(var Buf; Count: Word); virtual;

Читает в переменную BUF Count байтов из потока

procedure Write(var Buf; Count: Word); virtual;

Пишет в поток Count байт из переменную BUF

function ReadStr: PString;

Читает из потока строку, сохраняя ее в куче и возвращает указатель на нее.

procedure WriteStr(P: PString);

Сохраняет в потоке строку.

procedure Reset;

Сброс ошибки. При возникновении любой ошибки ввода-вывода блокируются дальнейшие чтение и запись. Отменить блокировку можно методом Reset.

procedure Seek(Pos: Longint); virtual;

Устанавливает текущую позицию в потоке

function StrRead: PChar;

Читает из потока строку типа Pchar

procedure StrWrite(P: Pchar);

Пишет в поток строку типа PChar

procedure Truncate; virtual;

Усекает поток с текущей позиции до конца

 

TDosStream и TBufStream

Файловые потоки позволяют хранить данные на жестком диске. Одним из основных преимуществ файловых потоков перед файлами является большой набор режимов открытия файла. Если, например, нетипированный файл можно открыть только для чтения и записи одновременно, то потоковый файл разрешается открывать как для чтения и записи, так и только для чтения или только для записи.

Типичным примером проблемы, разрешаемой таким свойством файловых потоков, может служить следующая ситуация: Некоторая программа читает и обрабатывает содержимое файла на диске, который может быть рисунком, набором бинарных данных и т.п., причем данный файл требуется ей исключительно для чтения. Однако, из-за несовершенства стандартных средств файлового ввода Турбо-Пасаля, открытие такого файла возможно только для чтения и записи одновременно. Поместив программу на диск, например, локальной сети, который для многих пользователей может быть доступен только для чтения, мы тем самым запрещаем этим пользователям доступ к программе, так как попытка программы открыть типированный или нетипированный файл на таком диске завершится ошибкой времени выполнения.

 

TDosStream

Dos-поток используется для хранения данных в файле на жестком диске.

Поля и методы класса TDosStream

Handle: Word;

Уникальный ИД обрабатываемого файла.

constructor Init(FileName: FNameStr; Mode: Word);

Создает поток, ассоциируя его с файлом на диске.

FileName - имя файла

Mode - режим открытия файла

stCreate = $3C00; { создать файл }

stOpenRead = $3D00; { доступ только для чтения }

stOpenWrite = $3D01; { доступ только для записи }

stOpen = $3D02; { доступ для чтения и записи }

 

TBufStream

Буферизованные дисковые потоки используются для ускорения обращения к файлу.

Методы класса TBufStream

constructor Init(FileName: FNameStr; Mode, Size: Word);

Создает буферизованный поток на диске

Size – размер буфера

 

TMemoryStream и TEmsStream

Потоки в памяти служат для быстрого доступа к информации, ее временного хранения и копирования.

TМemoryStream

Методы класса TМemoryStream

constructor Init(ALimit: Longint; ABlockSize: Word);

Создает поток в обычной памяти размером до 64 КБ.

Alimit - максимальное количество блоков памяти

AblockSize - размер одного блока.

Количество блоков памяти не может превышать значения 16384.

TEmsStream

Методы класса TМemoryStream

constructor Init(MinSize, MaxSize: Longint);

Создает поток в EMS-памяти размером по меньшей мере в MinSize байт, расширяемый до MaxSize байт.

 

2.2.2.3.1.1     Хранение данных в потоках

Хранение обычных данных в потоке схоже с хранением данных в нетипированном файле. Для записи служат методы Write, WriteStr и StrWrite, для чтения – методы Read, ReadStr и StrRead, причем в отличии от файла, в котором размер минимального блока можно установить произвольно, чтение и запись в потоке производится побайтно. Установка потоковой позиции (аналогично файловой позиции) осуществляется методом Seek, а чтение – методом GetPos.

Задача 5. Создать 2 программы. Первая заполняет массив N случайными числами, выводит его на экран и сохраняет в потоке, вторая – считывает содержимое потока и выводит его на экран.

{первая программа}

uses objects;

 var     a:array [1..1000]of integer;

s:tBufstream;

i,n:integer;

begin

 readln(n);

 for i:=1 to n do begin a[i]:=random(10); write(a[i]:3); end;

 writeln;

 s.Init('noname',stCreate,1024);

 s.write(a,sizeof(integer)*n);

 s.done;

end.

 

{вторая программа}

uses objects;

 var     s:tBufstream;

i:integer;

begin

 s.Init('noname',stOpenRead,1024);

 while s.GetPos<s.GetSize do begin s.read(i,sizeof(integer)); write(i:3); end;

 writeln;

 s.done;

end.

 

Задания для раздела "Хранение данных в потоках" (60-63)

Задание 60. Используя два объекта класса TBufStream, создать программу копирования файлов. Имя исходного и результирующего файлов вводится с клавиатуры.

Задание 61. Создать процедуру, сортирующую переданный ей массив и сохраняющую результат в переданном потоке. Передаваемый массив должен являться параметром-переменной, передаваемый поток – либо параметром переменной класса TStream, либо параметром значением – указателем типа PStream. Проверить результат ее действия.

Задание 62. Создать процедуру, считывающую из переданного ей потока данные в переданный ей массив. Параметры процедуры аналогичны предыдущей задаче. Проверить результат ее действия.

Задание 63. Создать класс потока TMyStream, хранящий данные в массиве, который является полем этого класса (статическим или динамическим). Проверить полученный результат.

 

2.2.2.3.1.2     Хранение объектов в потоках.

Для хранения объектов, порожденных от TObject в потоке, требуется зарегистрировать каждый класс, экземпляры которого подлежат чтению-записи. Для это требуется:

·        наличие в классе методов чтения полей данных из потока и сохранения полей данных в потоке. Вид методов должен быть следующим:

constructor load(var s:tstream); {чтение из потока}

procedure store(var s:tstream); {запись в поток}

Внутри констрактора Load требуется заполнить поля данных чтением их из потока S. Внутри процедуры Store требуется аналогичным образом сохранить поля данных в потоке. Соответствие между записью и чтением должно быть "один в один", то есть, из потока должно быть считано столько же байт, сколько ранее в нем было сохранено.

·        наличие специализированной переменной, заполняемой индивидуально для каждого класса, подлежащего сохранению в потоке. Тип переменной TStreamRec - определен в модуле objects и выглядит следующим образом:

TStreamRec = record

 ObjType: Word; {уникальный ИД для каждого класса}

 VmtLink: Word; {адрес входа в таблицу VMT для класса}

 Load: Pointer; {указатель на метод загрузки объекта}

 Store: Pointer; {Указатель на метод сохранения объекта }

 Next: Word; {адрес следующей записи типа TStreamRec}

end;

В обязательном порядке должны быть заполнены все поля переменной, кроме поля Next, которое заполняется автоматически при последующей регистрации.

·        каждый класс должен быть зарегистрирован вызовом процедуры RegisterType:

procedure RegisterType(var S: TStreamRec);

После регистрации класса его экземпляры можно сохранять в потоке методом TStream.Put, и считывать в дальнейшем методом TStream.Get.

 

Программный проект (8)

В фазе 7 программного проекта был описан классы TGrObject, TGrPoint, TAbstractLS и TGrLine, в каждом из которых, в свою очередь, были определены методы Store и Load для хранения полей класса в потоке. Напомним их реализацию, а также произведем регистрацию данных классов.

{ модуль GrPrim.pas }

implementation

uses inter;

{ ------------ TGrObject ------------- }

constructor TGRObject.load(var s:tstream);

      begin  s.Read(disabled,sizeof(disabled));  end;

 

    procedure TGRObject.store(var s:tstream);

     begin s.Write(disabled,sizeof(disabled));  end;

{ ------------ TAbstractLS ------------- }

constructor TAbstractLS.load(var s:tstream);

     begin

       inherited load(s); s.read(style,sizeof (style));

     end;

 

    procedure TAbstractLS.store(var s:tstream);

     begin

       inherited store(s); s.write(style,sizeof (style));

     end;

{ ------------ TGrPoint ------------- }

constructor TGrPoint.load(var s:tstream);

      begin

       inherited load(s); s.read(point,sizeof (point)); s.read(color,2);

      end;

 

    procedure TGrPoint.store(var s:tstream);

      begin

       inherited store(s); s.write(point,sizeof (point));

       s.write(color,sizeof (color));

      end;

{ ------------ TGrLine ------------- }

constructor TGrLine.load(var s:tstream);

      begin

       inherited load(s); s.read(CoordLine, sizeof (CoordLine));

      end;

 

      procedure TGrLine.store(var s:tstream);

      begin

       inherited store(s); s.write(CoordLine,sizeof (CoordLine));

      end;

{ типированные константы для регистрации классов в системе потокового ввода-вывода  }

const

rGrPoint:tstreamrec=

 (objtype:2097;

 vmtlink:ofs(typeof (TGrPoint)^);

 load:@TGrPoint.load;

 store:@TGrPoint.store);

 

rGrLine:tstreamrec=

 (objtype:2098;

 vmtlink:ofs(typeof (TGrLine)^);

 load:@TGrLine.load;

 store:@TGrLine.store);

begin

 {секция инициализации модуля}

 {регистрация классов в системе потокового ввода-вывода}

 registertype(rGrpoint);

 registertype(rGrline);

end.

 

Проанализируем некоторые интересные моменты в регистрации классов.

Во первых, может возникнуть вопрос, почему из четырех классов регистрируется только два, несмотря на то, что методы Load и Store определены в каждом из четырех? Дело в том, что храниться в потоке будут экземпляры классов TGrPoint и TGrLine, а методы Load и Store оставшихся двух классов никогда не будут вызваны напрямую.

Во вторых, обращает на себя внимание формирование значений типированных констант для регистрации классов:

rGrPoint:tstreamrec=

 (objtype:2097;

 vmtlink:ofs(typeof (TGrPoint)^);

 load:@TGrPoint.load;

 store:@TGrPoint.store);

Значение поля objtype должно быть уникальным во всей потоковой системе программы. Так как значения от 0 до 999 зарезервированы Турбо-Паскалем для своих нужд, программист в потоковых переменных должен использовать значения >= 1000.

Поле vmtlink представляет собой смещение (offset) области памяти, в которой находится таблица виртуальных методов для данного класса (сегмент, в котором находится таблица, известен заранее – это сегмент данных, где хранятся все глобальные переменные). Рассмотрим способ получения значения поля vmtlink:

ofs(typeof (TGrPoint)^)

Функция ofs возвращает смещение области памяти переменной, которая ей передана в качестве аргумента. Функция typeof возвращает указатель на таблицу VMT переданного ей класса.

 

Задания для программного проекта (64-68)

Задание 64. Определить методы Load и Store для классов примитивов TGrRect, TGrCircle, TGrEllipse, TGrPoly, TGrBar, TGrFillEllipse (см. задания 53-59). Зарегистрировать каждый класс.

Задание 65. Определить методы Load и Store для классов примитивов TGrFillPoly, TGrArcCircle, TGrArcEllipse, TGrText, TGrPicture, TGrFill (см. задания 53-59). Зарегистрировать каждый класс.

Задание 66. Определить массив, состоящий из графических примитивов. Случайным образом заполнить его точками и линиями. Визуализировать содержимое массива. Сохранить в потоке. Очистить массив. Загрузить примитивы из потока в массив. Снова визуализировать массив, предварительно очистив экран.

Задание 67. Выполнить задание 66 для классов примитивов, определенных в задании 64.

Задание 68. Выполнить задание 66 для классов примитивов, определенных в задании 65.

 

2.2.2.3.2          Коллекции

Коллекции (TCollection и все от него порожденные) являются структурой данных, в которой могут храниться другие данные.

Коллекции используются как аналог массивов для объектов, порожденных от TObject, имеют динамический размер и могут увеличивать свой размер в процессе заполнения. Нумерация элементов коллекции всегда начинается с 0.

Основные поля класса TCollection:

Count:integer - количество элементов коллекции

Основные методы:

constructor Init(ALimit, ADelta: Integer);

Инициализирует коллекцию

ALimit - максимальное кол-во элементов в коллекции

ADelta - количество эл-тов для приращения при переполнении коллекции

destructor Done;virtual;

Завершает работу с коллекцией, уничтожая ее содержимое с помощью вызова метода FreeAll

procedure Insert(Item: Pointer); virtual;

Вставляет элемент в конец коллекции.

Item - указатель на объект.

procedure AtInsert(Index: Integer; Item: Pointer);

Вставляет элемент в коллекцию в позицию Index и сдвигает все последующие элементы на одну позицию вправо.

procedure AtPut(Index: Integer; Item: Pointer);

Вставляет элемент в коллекцию в позицию Index, заменяя стоящий в этой позиции.

function At(Index: Integer): Pointer;

По номеру элемента возвращает указатель на него

function IndexOf(Item: Pointer): Integer; virtual;

По указателю на элемент возвращает его номер в коллекции или -1, если такого элемента в коллекции нет.

procedure Delete(Item: Pointer);

Удаляет элемент из коллекции и сдвигает все последующие элементы на одну позицию влево.

procedure AtDelete(Index: Integer);

Удаляет элемент по его номеру из коллекции и сдвигает все последующие элементы на одну позицию влево.

procedure DeleteAll;

Удаляет все элементы из коллекции.

Удаление объекта из коллекции не уничтожает его. Для уничтожения удаляемого объекта требуется либо вызвать после удаления Dispose, либо воспользоваться следующими методами.

procedure Free(Item: Pointer);

Удаляет элемент из коллекции и уничтожает его (вызывая FreeItem).

procedure FreeAll;

Удаляет все элементы из коллекции и уничтожает их (вызывая FreeItem).

procedure FreeItem(Item: Pointer);virtual;

Уничтожает элемент. Никогда не вызывается напрямую.

function GetItem(var S: TStream): Pointer; virtual;

Загружает элемент коллекции из потока и возвращает указатель на него. Перекрывается в коллекциях, элементы которых не являются объектами или не порождены от TObject.

procedure PutItem(var S: TStream; Item: Pointer); virtual;

Сохраняет элемент коллекции Item в потоке. Перекрывается в коллекциях, элементы которых не являются объектами или не порождены от TObject.

constructor Load(var S: TStream);

Загружает содержимое коллекции из потока

procedure Store(var S: TStream);

Сохраняет содержимое коллекции в потоке

 

Методы коллекции, которые манипулируют нетипированными указателями, на самом деле подразумевают, что данные указатели являются динамическими объектами, порожденными от класса TObject. Фактически, в коллекции можно хранить не только объекты, но и любые структуры данных. Для этого надо четко понимать, в каких случаях элементы коллекции рассматриваются как объекты, а в каких – как обычные указатели: при уничтожении, сохранении в потоке и чтении из потока элементы коллекции обрабатываются как динамические объекты, порожденные от TObject.

Таким образом, класс TCollection можно использовать для хранения любых данных, если никогда прямо или косвенно не вызвать методы FreeItem, PutItem и GetItem. Из этого правила очевидно следует два приема:

1)     Не сохранять содержимое коллекции в потоке и не производить оттуда чтения.

2)     Перед уничтожением коллекции удалять из нее все элементы.

 

В модуле Objects определена переменная RCollection типа TStreamRec. Однако регистрацию коллекции в потоке программист обязан произвести самостоятельно, в начале программы или в одной из секций инициализации модулей программы вызовом RegisterType(RCollection).

 

Задача 6. Создать коллекцию из 20 случайных элементов - точек или линий, вывести ее на экран, а затем удалить.

 var     PCl:PCollection;

po:pGrObject;

i,k:integer;

 begin

 ...

 new(pcl, init(20,0));

 for i:=1 to 20 do begin

 k:=random(2);

 if k=0 then

 po:=new(PPoint,Init(Random(640),random(350),random(16))

 else po:=new( PLine, Init ( Random(640) , random(350), Random(640),random(350), 0,0,1,random(16));

 pcl^.Insert(po);

 end;

 for i:=0 to pcl^.count-1 do begin

 pGrObject(pcl^.at(i))^.Show;

 end;

 pcl^.FreeAll;

 ....

 

Программный проект (9)

Для управления набором графических примитивов мы создадим специализированную графическую коллекцию для хранения примитивов, методы которой позволят нам показывать примитивы, сохранять всю коллекцию в текстовом файле и определять примитив, на который указывает курсор мыши.

{ модуль GrPrim.pas }

{ класс "Коллекция графических примитивов"}

   TGrCollection=object(TCollection)

    procedure SaveASPas(filename,procname:string);

{ сохраняет содержимое коллекции в виде процедуры в подключаемом ($I) к паскалевской программе файле }

    procedure Show; { показывает коллекцию }

    function ObjectOnPoint(TP:TPoint):PGrObject;

{ возвращает последний (ближний в Z-упорядочивании) объект, включающий в себя переданную точку. Возвращает nil, если такого объекта нет }

   end;

implementation

uses inter;

{ ------------ TGrCollection ------------- }

     procedure tgrCollection.saveaspas(filename,procname:string);

      var f:text; i:integer;

      begin

       assign(f,filename); rewrite (f);

       writeln(f,'Procedure '+procName+'(cx,cy:integer)'+';'); writeln(f,'Begin');

       for i:=0 to count-1 do pgrobject(at(i))^.saveastext(f);

       writeln(f,'End;'); close(f) ;

      end;

 

      procedure tgrCollection.show;

      var i:integer;

      begin

       for i:=0 to count-1 do with pgrobject(at(i))^ do if not disabled then show;

      end;

 

      function tgrCollection.ObjectOnPoint(TP:TPoint):PGrObject;

        var i:integer;

       begin

        ObjectOnPoint:=nil;

        for i:=count-1 downto 0 do

         if pgrobject(at(i))^.PointIn(tp.x,tp.y) then begin

          ObjectOnPoint:=at(i); exit;

         end;

       end;

 

Задания для программного проекта (69-72)

Задание 69. Создать и заполнить произвольным набором примитивов две графических коллекции. Визуализировать по нажатию клавиши поочередно первую, затем вторую коллекции, затем снова первую и т.д. до тех пор, пока не будет нажата клавиша ESC.

Задание 70. Создать и заполнить произвольным набором примитивов графическую коллекцию. Сохранить ее в потоке и паскалевском файле. Уничтожить коллекцию. Загрузить коллекцию из потока и визуализировать ее, предварительно очистив экран.

Задание 71. Создать и заполнить произвольным набором примитивов графическую коллекцию. Используя программную заготовку из задания 51, удалять из коллекции те примитивы, на которых произошел щелчок левой кнопкой мыши. Перерисовывать коллекцию после каждого успешного удаления. Выход из программы по щелчку правой кнопкой.

Задание 72. Создать и заполнить произвольным набором примитивов, состоящем из точек, линий и прямоугольников, три графических коллекции. Последовательно визуализировать все три коллекции. Переместить все точки в первую коллекцию, все линии – во вторую, все прямоугольники – в третью. Снова последовательно визуализировать все три коллекции.

 

2.2.2.3.3          Ресурсы

Ресурсы (TResourceFile)- это потоки произвольного доступа, где содержатся объекты, каждый из которых доступен по уникальному ключу - строке, идентифицирующей объект. Существует возможность узнать количество элементов в ресурсе, получить ключ по номеру элемента в ресурсе, загрузить элемент по его ключу, удалить элемент и т.д. Ключи в ресурсе всегда хранятся в алфавитном порядке.

Одним из специфичных свойств ресурсов является возможность дозаписи их к концу EXE-файла с последующим чтением оттуда. Т.о. можно создавать и распространять программы, которые будут состоять из одного EXE-файла и произвольно самомодифицироваться в области данных.

Поля и методы класса TResourceFile

Stream: PStream;

Указатель на поток, ассоциированный с данным ресурсом.

Modified: Boolean;

True, если ресурс был изменен (внесены или удалены данные)

constructor Init(AStream: PStream);

Инициализирует ресурс, передавая ему ранее созданный поток. Если в текущей позиции переданного потока находится ИД EXE-файла, то конструктор передвигает позицию на конец файла, или на начало ресурса, который уже находится в EXE-файле.

destructor Done; virtual;

Уничтожает ресурс, закрывая при этом ассоциированный с ним поток.

function Count: Integer;

Возвращает количество объектов в ресурсе, их нумерация начинается от 0.

procedure Put(Item: PObject; Key: String);

Добавляет объект в ресурс, ассоциируя его с ключевой строкой. Если объект с такой ключевой строкой уже имеется в ресурсе, то он заменяется новым объектом.

function Get(Key: String): PObject;

Возвращает указатель на объект, считанный по ключевой строке из ресурса

function KeyAt(I: Integer): String;

Возвращает строку, ассоциированную с I-м объектом в ресурсе.

procedure Delete(Key: String);

Удаляет объект из ресурса по ключевой строке. Объект не удаляется физически, исчезает только ссылка на него. Для физического удаления следует в дальнейшем воспользоваться функцией SwitchTo.

function SwitchTo(AStream: PStream; Pack: Boolean): PStream;

Переключает (копирует) ресурс с текущего потока на поток Astream. Старый поток возвращается как результат функции. Если Pack=true, то данные, переносимые из старого потока в новый пакуются, т.е. освобождаются от удаленных объектов.

 

Задача 7. Создать программу, управляющую ресурсом, состоящим из графических коллекций и хранящимся в самом файле программы. Предусмотреть добавление коллекций в ресурс и их показ.

 

uses grprim,crt,graph,objects;

function CreateRandomGrC:PGrCollection;

{ создает и возвращает графическую коллекцию, заполняя ее 15 произвольными линиями 15 цветами }

  var P:PGrCollection; i:integer;

 begin

  randomize;

  p:=new(PGrCollection,init(100,100));

  for i:=1 to 15 do

   p^.insert(new(pgrLine,

    init(random(640),random(480),random(640),random(480),0,0,1,i)));

  CreateRandomGrC:=p;

 end;

 

procedure ShowGrC(p:PGrCollection);

{ инициализирует графический режим, показывает коллекцию, ожидает нажатия клавиши Enter и завершает работу с графикой }

 var grDriver: Integer; grMode: Integer; ErrCode: Integer;

 begin

  grDriver:=Detect; InitGraph(grDriver, grMode,'');

  p^.show; Readln; CloseGraph;

 end;

 

var RF:TResourceFile; Pgrc:PGrCollection;

    s:string; i,num:integer;

begin

 RF.Init(New(PBufStream, Init(paramstr(0), stOpen, 1024)));

{ инициализируем ресурс, передавая ему в качестве параметра создаваемый поток в файле, который является самой программой }

 repeat

   clrscr;

   writeln('В ресурсе имеются следующие рисунки:');

   if rf.count=0 then writeln(' ------- пусто ------- ')

   else for i:=0 to rf.count-1 do writeln('     ',i,'. ',rf.KeyAt(I));

   writeln('Выберите один из них (номер),');

   writeln('введите -1 для создания нового рисунка,');

   writeln('или -2 для выхода из программы');

   readln(num);

   if num=-1 then begin

     write('Введите новый ключ: ');readln(s);

     pgrc:=CreateRandomGrC;

     rf.put(pgrc,s);

     ShowGrC(pgrc);

     dispose(pgrc,done);

   end

   else

   if num<>-2 then begin

    if (num>=0)and(num<rf.count) then begin

     pgrc:=pgrcollection(rf.Get(rf.keyAt(num)));

     ShowGrC(pgrc);

     dispose(pgrc,done);

    end;

   end;

 until num=-2;

 rf.done;

end.

Проанализируйте и проверьте решение.

Задания для раздела "Ресурсы" (73-75)

Задание 73. В примере, показанном выше, добавьте возможность удаления элементов из ресурса.

Задание 74*. Создать программу сохраняющую набор графических коллекций в ресурсе отдельным файлом. Создать вторую программу, присоединяющую ресурс в отдельном файле, имя которого вводится с клавиатуры, к самой себе и циклически визуализирующую элементы ресурса с паузой в 1 секунду. Если имя ресурса неверно или не введено, считается, что ресурс уже присоединен к программе. Если введено правильное имя ресурса, но ресурс уже присоединен к программе, то старый ресурс заменяется новым. Выход из программы – по нажатию на любую клавишу.

Задание 75. Используя файл ресурса, созданный предыдущей программой, последовательно показать только те коллекции, имена которых находятся в текстовом файле, переданном программе в командной строке.


2.3 Отладка программ.

Отладкой называется процесс поиска и исправления ошибок в программах. Процесс отладки – обязательный атрибут современного программирования, и от знания приемов и методов, а также совершенства средств отладки зависит скорость создания программ и надежность конечного программного продукта.

 

2.3.1        Виды ошибок

Ошибки в программах подразделяются на 2 больших классах - синтаксические и семантические.

Синтаксические ошибки возникают обычно из-за нарушений правил языка программирования. Компилятор или интерпретатор программы сам определяет их местоположение. Таким образом не представляет труда локализировать и исправить такие ошибки.

Иногда исправление синтаксических ошибок вызывает затруднения, и чаще всего причина этого – плохое знание языка программирования. Например, в зарезервированном слове implementation легко сделать сразу несколько ошибок, также как и в слове constructor (особенно, если писать его так, как оно произносится). Иногда по вине программиста в зарезервированном слове появляются русские буквы, что компилятору переварить не под силу (хотя чисто визуально и русское и латинское "o" выглядит одинаково).

Большие трудности вызывают ошибки, связанные с незакрытыми блоками begin..end или скобками в выражениях. Для их исправления нужна большая внимательность и тщательный подсчет. По этой причине и предлагают выделять любые блоки едиными отступами в строках.

Семантические ошибки - это ошибки связанные с содержанием и смыслом программы. Причины ошибок могут быть самыми различными: может быть неверным алгоритм, при написании программы произошла опечатка, не нарушившая синтаксис (вместо 2 написали 3, вместо "+" напечатали "-") и т.д. Локализация таких ошибок и является главной задачей отладки.

По результатам своего действия семантические ошибки в контексте ТП делятся еще на два вида: ошибки времени выполнения (Runtime Errors - RTE) и логические ошибки.

Ошибки времени выполнения вызывают аварийный останов программы (переполнение стека из-за ошибок в рекурсии, переход за границы массива, запись на переполненный диск, переполнение числового значения и т.д.). Местоположение ошибки времени выполнения выявляется сразу по ее возникновении. ТП останавливает программу и переводит курсор на строку, где возникла ошибка.

Например, функция следующего вида:

function f(x:integer):real; begin if x=1 then f:=1 else f:=f(x-1)*x end; {x! при x Î N}

с первого взгляда не содержит логических ошибок. Однако серьезный анализ покажет сразу две семантические ошибки – ошибки времени выполнения. Первая ошибка, при относительно небольших значениях х, – это ошибка переполнения вещественного значения (Floating point overflow), вторая, при больших значениях х, – переполнение стека (Stack overflow error). Проверьте эту функцию с различными значениями параметра x.

ТП позволяет отключить слежение за такими ошибками, однако это не рекомендуется делать во время отладки программы. Отключение слежения уменьшает размер и увеличивает скорость работы программы, однако в случае возникновения ошибки может привести к непредсказуемым последствиям (чаще всего – зависание программы).

Слежение за ошибками времени выполнения реализуется с помощью элемента меню Options/Compiler/ или специальных директив компилятора:

Options/Compiler/ Range checking - слежение за ошибками выхода за границу. Директива {$R+} или {$R-}

Options/Compiler/ Stack checking - слежение за ошибками переполнения стека. Директива {$S+} или {$S-}

Options/Compiler/ I/O checking - слежение за ошибками ввода-вывода. Директива {$I+} или {$I-}

Options/Compiler/ Overflow checking - слежение за ошибками переполнения. Директива {$O+} или {$O-}

Результатом логических ошибок является неверная работа программы, т.е. программа делает то, что ей сказано, а не то, что хотелось бы. Это наиболее сложный для локализации вид ошибок, хотя сложность их исправления соответствует сложности исправления Runtime ошибок.

Рассмотрим предыдущий пример, немного видоизменив его:

function f(x:integer):real; begin if x=1 then f:=0 else f:=f(x-1)*x end; {x!}

В данном случае, добавилась логическая ошибка, которая обычно является результатом невнимательности или незнания формулы вычисления факториала. В результате функция будет возвращать 0 вне зависимости от значения x. Интересным побочным результатом такой ошибки является исчезновении ошибки переполнения вещественного значения.

По вероятности возникновения семантические ошибки делятся на явные и потенциальные. Явные ошибки всегда возникают при прохождении ошибочного участка кода (например – при делении на 0 или на переменную, значение которой в данной точке программы всегда равно нулю). Для возникновения потенциальных ошибок обычно требуется совпадение нескольких, иногда и маловероятных условий (например – при вычислении гипотенузы по теореме Пифагора можно в некоторых случаях получить ошибку переполнения числового значения). Потенциальные ошибки наиболее сложны в констатации, локализации и исправлении. Такие ошибки могут проявится через несколько месяцев, а то и лет, после ввода программы в эксплуатацию.

Снова рассмотрим функцию вычисления факториала:

function f(x:integer):real; begin if x=1 then f:=1 else f:=f(x-1)*x end;

Обе ошибки времени выполнения, присутствующие в решении, являются потенциальными, так как при небольших значениях x функция работает верно, а ошибки возникают при увеличении значения аргумента.

 

Задания для раздела "Виды ошибок"
(
76-78)

Задание 76. Найдите и исправьте ошибки в следующей программе:

var c:pgrcollection;

begin

 c.init(10,10);

 for i:=1 to random(1000) do begin

  c.insert(new(pgrpoint,init(random(100,100,15)));

 for i:=1 to c.count do with pgrpoint(c.at(i)).point do writeln(x:5,y:5);

end.

Задание 77. Найдите и исправьте ошибки в следующей программе:

var s;n:integer;

begin

 readln(n);

 for i:=1 to n do begin

  s:=s+n;

  writeln(s)

 end;

end;

Задание 78. Определите, какие из семантических ошибок в предыдущем задании являются явными, а какие – потенциальными.

 

2.3.2        Констатация и локализация ошибок

Процесс отладки состоит из двух частей: констатации и локализации ошибки.

 

1) Констатация ошибки.

До поиска ошибки требуется выяснить, существуют ли вообще ошибки в программе. Для решения этой задачи программу пропускают на контрольных примерах, причем стараются, чтобы выполнение программы прошло по всем элементам блок-схемы алгоритма. Как только выяснилось, что результат выполнения программы вступает в противоречие с эталонным, или происходит RTE, вступает в действие второй этап отладки.

Возвратимся снова к примеру с вычислением факториала.

function f(x:integer):real; begin if x=1 then f:=1 else f:=f(x-1)*x end; {x!}

Не смотря на явное наличие двух ошибок времени выполнения, констатировать их наличие можно не при любых значениях x. Это одна из основных проблем в констатации ошибок. Без проверки граничных условий резко уменьшается надежность программы и повышается вероятность возникновения трудноуловимых семантических ошибок. Особенно трудноуловимы ошибки, результат которых непредсказуем или проявляется в различных, не связанных с первого взгляда друг с другом ситуациях. При отключении проверки на возникновение ошибок времени выполнения резко повышается сложность как их констатации, так и локализации. В это случае RTE могут не иметь никаких последствий, а могут стать результатом "зависания" программы. Данная ситуация служит прекрасной иллюстрацией к поговорке "раз на раз не приходится".

2) Локализация ошибки.

RTE в большинстве случаев не требуют локализации, так как местоположение такой ошибки становится известным при ее возникновении. Исключение составляют случаи, когда RTE происходят в модулях без исходных текстов. Тогда возвращаемый адрес памяти не может быть сопоставлен со строкой программы и сложность локализации RTE становится сопоставимой со сложностью локализации логических ошибок.

Для поиска логических ошибок в программе существует несколько методов.

а) Метод промежуточных результатов, заключающийся в том, что в "подозрительном" месте программы устанавливают ловушку, которая выводит на экран или в файл содержимое тех переменных, неверное значение которых может стать причиной ошибки. После окончания (в случае вывода в файл) или во время работы (при выводе на экран), анализируются значения переменных. Если ошибка не найдена, то ловушку переносят в другое подозрительное место и все начинается сначала, до тех пор пока ошибка не будет найдена.

Данный метод является одним из наиболее старых, трудоемких и требующих определенных навыков. В то же время владение этим методом - один из признаков профессионализма программиста. С определенными изменениями и для совершенно других целей данный метод используется при хакинге и крекинге особо защищенных программ.

 б) Использование встроенного или внешнего отладчика, позволяющего трассировать программу, то есть выполнять ее шаг за шагом, устанавливать условные и безусловные точки прерывания, просматривать и изменять значения переменных, просматривать значения констант, перемещаться по процедурам, из которых была вызвана текущая.

Т.о. для локализации логической ошибки требуется обычно несколько раз запустить программу, анализируя полученные результаты и все более уточняя местоположение ошибочных действий.

С появлением отладчиков и увеличением быстродействия компьютеров резко повысилась скорость создания программ. Побочным же результатом явилось понижение качества и культуры программирования. Если ранее любой создаваемый алгоритм тщательно анализировался и оптимизировался "на бумаге", где и выявлялось большинство семантических ошибок, то теперь, после поверхностного анализа, а иногда и без оного, алгоритм переносится на машину, где и "прогоняется" на предмет ошибок. Именно поэтому во многих областях науки и техники с успехом применяются программы двадцатилетней давности, в то время как в новейших разработках каждый месяц выявляются все новые и новые ошибки, на исправление которых уходят гигантские человеко-машинные ресурсы.

2.3.3        Использование встроенного отладчика.

Встроенный отладчик в ТП включается помощью меню или опций компилятора Debug Information и Local symbols в Options/Compiler, а также кнопки Integrated в Options/Debugger.

Под точкой прерывания понимается строка программы, где произойдет остановка при ее выполнения. Безусловные точки прерывания устанавливаются с помощью Ctrl-F8 (Debug/Toggle breakpoint). Для задания условия прерывания требуется выбрать прерывание в элементе меню Debug/Breakpoints и записать условие прерывания в соответствии с общими правилами записи условий Турбо-Паскаля.

Там же можно установить счетчик, показывающий, при котором прохождении через точку прерывания произойдет останов программы.

Просмотр и изменение значения переменных можно сделать с помощью Ctrl-F4 (Debug/Evaluate/modify).

С помощью окна стека можно увидеть весь вложенный набор процедур с их параметрами. Можно также войти выбрать и войти в любую из этих процедур с помощью Enter.

Изменение значений переменных и выражений во время пошагового выполнения программы можно просмотреть с помощью окна выражений (Window/Watch), если эти переменные были туда внесены с помощью Ctrl-F7 (Debug/Watches/Add watch). В окно выражений можно войти и редактировать внесенную переменную, нажав на ней Enter, либо использовав элемент меню Debug/Watches/Edit watch.

Пошаговое выполнение программы производится с помощью F7 и F8 (Run/Trace into и Run/Step over). Также существует возможность выполнить программу до точки, где находится курсор, с помощью F4 (Run/Go to cursor).

 


2.4 Разработка больших программ.

2.4.1        Общие принципы разработки
программ.

Существует два наиболее общих принципа создания программ.

2.4.1.1              Метод организации «сверху-вниз».

·        Сначала строится основная программа (процедура, функция и т.д.), в котором словесно или ограниченно-синтаксически (с помощью последовательности действий, блок-схем или диаграмм Насси-Шнайдермана) описываются все требуемые действия. Процесс может повторяться циклически, все более и более уточняя и раскрывая набор действий.

·        Затем словесная и ограниченно-синтаксическая организация заменяется набором действий и вызовами других подпрограмм (процедур, функций и т.д.), описанных в других модулях или еще не определенных. Попутно определяется набор переменных и других структур, требуемых для выполнения программы.

·        Каждая неопределенная подпрограмма (заглушка) определяется тем же методом, что и основная до тех пора, пока все модули и основная программа (подпрограмма) не будут созданы.

 

2.4.1.2              Метод организации «снизу-вверх».

Данный метод используется в случае, когда конечный результат известен только в общих чертах, однако с достаточной точностью определено, какой набор подпрограмм и модулей может потребоваться для построения программы или искомой подпрограммы.

·        Определяются подпрограммы самого низкого уровня.

·        На их основе создаются подпрограммы и модули более высокого уровня. Процесс продолжается циклически до тех пор, пока не будет создана основная программа или подпрограмма.

2.4.1.3              Достоинства и недостатки обоих
методов:

Первый метод требует детальной разработки структуры программы с постоянными уточнениями на каждом этапе. Этот процесс удобен для определения, изменения и уточнения общей идеи проекта, однако результат работы программы может быть протестирован только в самом конце.

Достоинством второго метода является возможность проверки каждой подпрограммы нижнего и среднего уровня сразу после создания, т.е. тестирования и отладки отдельных частей программы. Однако в этом случае процесс уточнения общей идеи проекта заставляет видоизменять уже созданные подпрограммы и модули, разрывая отлаженные связи. Поэтому при реализации данного метода надо стремиться к максимальной автономности каждой подпрограммы, каждого модуля, их независимости от других подпрограмм и модулей.

Наиболее часто встречается комбинирование первого и второго методов в различных пропорциях. При разработке библиотек процедур и функций, объеденных некоторой концепцией, в большей степени применяют второй метод. При разработке больших программ на начальных этапах активно применяется первый метод, а на промежуточных и заключительных – второй.

Рассмотрим, как мог бы проходить процесс решения задачи номер 7. Напомним ее условие: Создать программу, управляющую ресурсом, состоящим из графических коллекций и хранящимся в самом файле программы. Предусмотреть добавление коллекций в ресурс и их показ.

Метод организации "сверху-вниз":

Этап 1. Разработка основной модели.

1.        Создаем ресурс

2.        Организуем цикл для вывода меню, ввода данных и их обработки.

3.        Закрываем ресурс.

Этап 2. Уточнение основной модели.

            Уточняем пункт 2 основной модели

2.1  Выводим меню выбора, созданное на основе строк ресурса с возможными вариантами: просмотра соответствующей коллекции, создания и добавления новой коллекции в ресурс, выхода из программы.

2.2  Обрабатываем варианты просмотра и добавления коллекции, завершаем цикл обработкой варианта выхода из программы.

2.3  Создаем процедуры просмотра и создания коллекции.

Этап 3. Реализация модели на языке программирования.

 

Второй метод организации ("снизу-вверх") применим здесь ограниченно, хотя его комбинация с первым методом может дать неплохие результаты. Дело в том, что вполне очевидны требования к наличию подпрограмм создания, просмотра, удаления (в расширенном условии задачи) и т.д. коллекции. С их реализации и можно начать разработку программу, с учетом, конечно, полной автономности этих подпрограмм, так как изначально отсутствуют любые глобальные структуры данных. Затем можно создать подпрограмму для обработки меню, что еще более упростит основную программу, и, наконец, создать основную программу, разработав предварительно упрощенную модель по методу "сверху-вниз".


 

2.4.2        Концепции разработки больших программных проектов (БПП).

 Для создания БПП следует определить основные концепции его разработки:

1.     Ясная цель и концептуальное единство.

2.     Рабочая сила.

3.     Материалы.

4.     Достаточное кол-во времени.

5.     Соответствующую технологию.

6.     Организация связи между разработчиками программного проекта.

 

Одним из самых важных элементов процесса создания ПП является постановка и определение задачи (1), предварительное проектирование и разбиение ее на несколько небольших частей, каждая из которых может быть решена и отлажена как единое целое (1) одним человеком либо коллективом программистов.

Затем следует определение базовой платформы (3), программного обеспечения (3,5), требуемого для решения задачи, временные рамки, за которые должен быть создан проект и в соответствии с этим - количество программистов, отвечающих за отдельные блоки программы (2).

И, наконец, создание системы связи между разработчиками проекта (6).

 

2.4.2.1              Руководство программным проектом и коллектив программистов.

 

И опыта разработки больших ПП следует, что определение задачей и целей проекта, связь с заказчиком, постановка временных рамок должны производиться одним человеком, так как только в этом случае может быть достигнуто общее концептуальное единство проекта. Этот человек называется руководителем проекта, имеет в своем подчинении администратора, отвечающего за смету проекта и системного архитектора, отвечающего за разбиение задачи на отдельные части, каждая из которых должна быть поручена для разработки коллективу программистов.

Любой коллектив (бригада) программистов для оптимального достижения своей цели должен быть организован по следующему принципу:

1) Главный программист.

Пишет и отлаживает программу, согласовывает входные и выходные спецификации со своим архитектором, готовит документацию. Наиболее квалифицированное звено программного проекта.

2) Помощник главного программиста (второй пилот). Работает совместно с главным программистом, но не настолько опытен. Может взять на себя выполнение любой части поставленной задачи. До тонкостей знает всю программу. Ищет альтернативные стратегии решения, представляет бригаду на обсуждениях и дискуссиях с архитекторами и другими бригадами.

3) Инструментальщик (системный программист). Разрабатывает ресурсозависимые приложения для решения задачи, получая условия от главного программиста. Работает обычно на языке низкого уровня. До тонкостей знает все нюансы операционной системы и компьютера, на которых будет работать готовая программа.

4) Контролер. Готовит тесты для программы и прогоняет их на каждом варианте решения задачи. Отвечает за констатацию ошибок.

5) Языковед. Человек, посвященный во все тонкости языка программирования высокого уровня, на котором пишется программа. Обязан оказывать помощь главному программисту в случае появления чисто языковых проблем.

6) Администратор. Отвечает за предоставление машинных мест и времени, за все материальные запросы бригады. При необходимости может обслуживать несколько бригад.

7) Редактор. Отвечает за правильное оформление всей документации по проекту.

8) Архивариус. Отвечает за хранение всех версий решения за дачи. Всегда может предоставить хирургу или помощнику один из старых вариантов решения для отладки, тестирования и т.д. В настоящее время функции архивариуса обычно инкапсулированы в инструментальную систему коллективной разработки программных проектов.

 

При использовании средств мультимедиа в состав бригады включаются художники и музыканты.

В случае небольшого объема задачи состав как руководства, так и бригад может быть изменен. Руководитель проекта может взять на себя функции системного архитектора, главный программист - функции инструментальщика и языковеда, второй пилот - функции редактора, архивариуса и контролера, администратор проекта - функции всех администраторов бригад.

В этом случае руководство проектом состоит из руководителя проекта и администратора, которые управляют набором бригад, каждая из которых состоит из двух человек - главного программиста и его помощника.

 

2.4.2.2              Концептуальное единство проекта.

Как было показано выше, любой программный проект для решения разбивается на отдельные модули. Отлаженная связь между этими модулями и подразумевается под концептуальным единством проекта.

Определяя подзадачи, системный архитектор активно использует метод разработки программ "сверху-вниз", руководствуясь при этом несколькими правилами:

1) Четко и однозначно определяет входные и выходные данные каждого модуля.

2) Определяет максимальные системные ресурсы, которые могут быть выделены под данную задачу.

3) Передает информацию о пунктах 1 и 2 для каждой группы программистов главному программисту каждой группы. Таким образом главный программист сможет учесть и использовать обращения к параллельно создаваемым модулям при решении своей задачи.

4) Предпочитает отлаженный и работоспособный модуль любому другому (более скоростному, менее ресурсоемкому, но не отлаженному).

 

2.4.2.3              Ошибки при реализации проекта.

К обычной проблеме нахождения и исправления ошибок в отдельно взятом модуле добавляются проблемы ошибок во всем программном проекте.

При объединении модулей могут возникнуть несоответствия, связанные с неверной моделью общей задачи, неправильным определением подзадач или несогласованностью ресурсовыделения для каждого модуля. Эти проблемы возникают по вине системного архитектора и часто не могут быть замечены программистами. Исправление таких ошибок чрезвычайно дорогостояще, так как приходится отвергать готовые решения задач и переформулировать их, в результате чего теряется время и ресурсы, потраченные на создание полностью работоспособных модулей.

Один из способов решить данную проблему - после общей формулировки подзадач провести совещание с главными программистами всех групп для поиска возможных несоответствий.

 

Задания для раздела "Концепции разработки больших программных проектов (БПП)." (79-80)

Задание 79. Определите состав руководства и бригады программистов для разработки векторного графического редактора. Определите задачи каждого члена бригады и руководства в разработке данного БПП.

Задание 80. Выполните функции системного архитектора при разработке векторного графического редактора.

 

2.4.3        Событийная модель программного проекта.

2.4.3.1              Понятие события при разработке больших программных проектов.

 

Основным способом реакции большого программного проекта на действия пользователя является получение и обработка событий.

Под событием понимается любой сигнал со внешнего устройства (клавиатура, мышь, коммуникационный порт, джойстик и т.д.) или от самой программы (сигнал о завершении работы программы, о требовании перерисовать некоторую область в результате ее изменения, о требовании сохранить результаты работы в файле и т.д.). В многозадачных системах события к программе могут приходить от других программ и операционной системы.

Для реакции программы на внешние и внутренние события разработан механизм их получения и обработки. Для хранения полученных, но еще необработанных событий используется так называемая очередь событий, представляющая собой область памяти, где хранятся необработанные события, организованная по принципу FIFO (first in, first out - первый вошел, первый вышел). Примером аппаратной реализации данного принципа служит очередь событий клавиатуры, в которую помещаются нажатия клавиш (о переполнении этой очереди свидетельствует характерный писк). Типичным программным примером является событийный блок объектно-ориентированной библиотеки Turbo Vision, которая полностью основана на положениях, описанных в следующих параграфах.

2.4.3.2              Реализация механизма получения и
обработки событий в однозадачной среде.

Простейшая программа, реализующая механизм получения и обработки событий, содержит три основных процедуры и несколько дополнительных:

1)         Процедура получения события, которая опрашивает состояние внешних устройств, кодирует полученные события и вызывает процедуру, помещающую их в конец очереди. Затем изымает событие из начала очереди и возвращает его в качестве результата. Таким образом, если очередь событий была пуста при обращении к процедуре, то внешнее событие передается прямо в программу, а очередь снова становится пустой. Наличие дополнительной процедуры перемещения события в очередь связано с возможностью ее использования в любом месте программы для создания внутренних событий.

2)         Процедура обработки события. Ее параметром является некоторое событие, внешнее или внутреннее, которое требуется обработать. В зависимости от типа события процедура производит обращение к тем или иным блокам программы, которые содержат реакцию на соответствующее событие и в зависимости от текущего состояния программы совершают те или иные действия.

Пример. При создании текстового редактора процедура обработки события передает нажатие клавиш процедуре обработки клавиш, которая уже проводит анализ нажатой клавиши и совершает требуемые действия для активного окна (ввод символа, перемещение курсора, удаление строки и т.д.). Если же активное окно отсутствует, что реально, например при работе с меню, то обработка клавиатурного события происходит совершенно другим образом (для меню - отрабатываются нажатия клавиш курсора, клавиша "Enter", "горячие" клавиши и т.д.). Аналогичным образом происходит обработка для событий других типов. Например, в случае получения внутреннего события, являющимся требованием о завершении работы, управление передается в процедуру, которая выполняет диалоговый запрос типа "Вы уверены?" или "Текст в окне 3 не сохранен, сохранить?" и в зависимости от ответа совершает те или иные действия.

3)         Основная процедура, совмещающая в себе получение и обработку событий. Может состоять всего из нескольких строк:

Procedure RUN; {Основная процедура}

  Var Event:Tevent;              {Tevent - предопределенный тип записи, в котором хранится информация о событии любого типа}

 Begin

  Repeat

   GetEvent(Event); {Получение события}

   HandleEvent(Event); {Обработка события}

 Until Quit;          {Выход из процедуры, если получено сообщение о завершении работы. Quit - переменная логического типа, первоначально имеющая значение false}

 End;

 

Данная процедура запускается из основной программы после проведения инициализации всех переменных, установки режима экрана и т.д. После ее выполнения производится завершение работы, закрытие всех открытых файлов, установка первоначального режима экрана и т.д.

Т.о. основная программа может состоять всего из 5 строк:

Begin

  Init;    {процедура инициализации}

  Run;   {процедура получения и обработки событий}

  Done; {процедура завершения работы}

End.

 

2.4.3.3              Реализация механизма получения и
обработки событий в многозадачной среде.

 

При разработки многозадачных операционных систем системные программисты изначально опиралась на концепцию получения и обработки событий, поэтому в многозадачной среде механизм получения и обработки событий обычно встроен в операционную систему. Таким образом программисту не нужно определять очередь и организовывать получение событий. Значение имеет только обработка получаемых и создание своих собственных событий.

События в многозадачной среде могут приходить из гораздо большего набора источников и количество их типов на несколько порядков выше, чем в однозадачной программе, разработанной одним или группой программистов.

Отличительной особенностью события в многозадачной среде является его конкретная направленность, то есть полученное событие всегда направляется в конкретное окно, к конкретному приложению и т.д. Таким образом, для прикладного программиста не существует просто клавиатурных событий, имеются события от клавиатуры для некоторого объекта. Аналогично не существует абстрактных мышиных событий, имеются события от манипулятора мышь для конкретной кнопки, элемента меню, окна и т.д.

В отличие от однозадачной среды, в многозадачной существует также несколько типов очередей событий. Среди них можно выделить одну глобальную и множество локальных очередей, связанных с конкретными приложениями. В глобальную поступают аппаратные события и события, предназначенные для передачи из одной программы в другую, в локальные - преобразованные события из глобальной (например - мышиное событие для какого-либо окна приложения поступает из глобальной очереди в локальную, меняя в процессе преобразования свои глобальные координаты на локальные - относительно левого верхнего угла соответствующего окна), и внутренние события программы.

При создании программы в многозадачной среде определяют главную процедуру обработки событий со специфичным названием (например в Windows - это MainWndProc), а также несколько дополнительных, связанных с различными параллельными процессами внутри одного приложения. Операционная система вызывает эти процедуры, передавая им события для обработки. Таким образом, задача программиста - реализовать реакцию приложения на различные типы событий.

Некоторые системы визуальной разработки программ (Delphi, C++ Builder, Visual Basic и др.) искусно скрывают механизмы передачи событий, позволяя программисту кодировать только соответствующие обработчики, в результате чего время разработки программы значительно сокращается.

 

2.4.3.4              Пример реализации получения и обработки событий в однозадачной среде.

1)     Разработаем структуру для хранения одиночного события

const

{ События }

  evMouseUp   = $0001; { Отжатие кнопки мыши }

  evMouseDown = $0002; { Нажатие кнопки мыши }

  evMouseMove = $0004; { Перемещение мыши }

  evKeyDown   = $0010; { Нажатие клавиши }

  evCommand   = $0100; { Внутренняя команда }

{ Маски событий }

  evNothing   = $0000;

  evMouse     = $0007;

  evKeyboard  = $0010;

{ Команды }

  cmQuit = 1;

{ Маски клавиш мыши }

  mbLeft=1;

  mbRight=2;

 

type

 { тип записи для хранения событий }

 TEvent=Record

  case TypeEvent:word of

   evNothing:();

   evKeyBoard:(Code:char; Ext: boolean;

               LShift,LAlt,LCtrl:boolean;

               RShift,RAlt,RCtrl:boolean);

   evMouse:(XY:TPoint; Buttons:byte);

   evCommand:( Command:integer; pInfo:pointer );

  end;

 

Рассмотрим подробно тип TEvent.

Поле TypeEvent определяет вид полученного события. Событие отсутствует, если значение TypeEvent=evNothing. Такая ситуация возможна, если при обращении к процедуре получения события очередь событий пуста, или полученное из очереди событие предается последовательно нескольким объектам (процедурам, функциям и т.д.), причем обработавший событие объект устанавливает TypeEvent в evNothing для предотвращения дальнейшего распространения сообщения.

При нажатии клавиши поле TypeEvent устанавливается в
evKeyDown, при этом заполняются поля Code (код нажатой клавиши), Ext (признак расширенного кода) и поля, соответствующие нажатым клавишам Ctrl, Alt, Shift.

При возникновении мышиного события поле typeEvent устанавливается в evMouseUp, evMouseDown или evMouseMove в зависимости от типа события. Поле XY устанавливается в координаты мыши, а поле Buttons – в состояние клавиш мыши.

При создании пользовательского события поле typeEvent устанавливается в evCommand, поля Command и pInfo – соответственно в значение пользовательского события и дополнительной информации, которая может быть указателем на некоторую структуру данных, объект или просто целочисленным значением, преобразованным в тип pointer.

 

2)    Очередь событий

const MaxEvent=100;

{максимальное количество событий в очереди}

var ChainEvent:array[1..MaxEvent] of TEvent;

{очередь событий в виде массива}

const CountEvent:integer=0; {текущее количество событий}

 

3)    Процедуры добавления события в очередь и получения его оттуда.

{процедура добавления события в очередь}

procedure PutEvent(Event:Tevent);

  var i:integer;

 begin

  if CountEvent=MaxEvent then Exit;

  for i:=CountEvent Downto 1 do begin

   ChainEvent[i+1]:=ChainEvent[i];

  end;

  ChainEvent[1]:=Event;

  inc(CountEvent);

 end;

 

{ переменные для хранения "старой" информации о состоянии мыши }

const

       MouseX:integer=0;

       MouseY:integer=0;

       MouseLeftDown:boolean=false;

       MouseRightDown:boolean=false;

{процедура получения события}

procedure GetEvent(Var Event:Tevent);

   var c:char; k1,k2:byte; E:Tevent; Left, Right:boolean;

 begin

  FillChar(E,sizeof(e),0);

{ проверяем на клавиатуру }

  k1:=mem[$40:$17]; k2:=mem[$40:$18];

  with E do begin

    LShift:=(k1 and 2)<>0; RShift:=(k1 and 1)<>0;

    if (k1 and 4)<>0 then if (k2 and 1)<>0 then LCtrl:=true else RCtrl:=true;

    if (k1 and 8)<>0 then if (k2 and 2)<>0 then LAlt:=true else RAlt:=true;

    if LShift or LAlt or LCtrl or RShift or RAlt or RCtrl or keypressed then begin

     TypeEvent:=evKeyDown; ext:=true;

     if keypressed then begin

      Ext:=false; Code:=readkey;

      if Code=#0 then begin Ext:=true; Code:=readkey; end;

     end;

     PutEvent(e);

    end;

  end;

{ проверяем на мышь }

  fillchar(e,sizeof(e),0);

  with E do begin

   GetMouseState(XY.x,XY.y,left,right);

   if (XY.x<>MouseX) or (XY.y<>MouseY) then begin

    TypeEvent:=evMouseMove; PutEvent(e);

   end;

   if Left<>MouseLeftDown then begin

    TypeEvent:=evMouseUp shl byte(left); buttons:=mbLeft; PutEvent(e);

   end;

   if Right<>MouseRightDown then begin

    TypeEvent:=evMouseUp shl byte(right); buttons:=mbRight;

    PutEvent(e);

   end;

   MouseX:=XY.X;MouseY:=XY.Y;

   MouseLeftDown:=left; MouseRightDown:=right;

  end;

{теперь возвращаем события из конца очереди событий}

  if Countevent=0 then exit;

  Event:=ChainEvent[CountEvent];

  Dec(CountEvent);

 end;

При получении события мыши используется процедура GetMouseState. Данная процедура возвращает состояние клавиш и местоположение мыши, и находится в модуле Mouse (см. фазу 0 программного проекта)

 

4)     Обработка события (каркас)

Процедура, показанная ниже, может использоваться для обработки всех событий программы. Она вызывается из процедуры Run (см. параграф 2.4.3.2)

procedure HandleEvent(Var Event:Tevent);

 begin

  case Event do begin

   if TypeEvent or evMouse <>0 then begin

     {некоторые действия, единые для любых событий мыши, перед обработкой этих событий }

    case TypeEvent of

     evMouseUp: begin

      {обработка отжатия кнопки мыши}

      TypeEvent:=0;

    end;

    evMouseDown: begin

      {обработка нажатия кнопки мыши}

      TypeEvent:=0;

    end;

    evMouseMove: begin

      {обработка перемещения мыши}

      TypeEvent:=0;

    end;

{некоторые действия, единые для любых событий мыши, после обработкой этих событий }

   end;

   Case typeEvent of

    evKeyDown: begin

     {обработка нажатия клавиш}

     TypeEvent:=0;

    end;

    evCommand: begin

     {обработка команд пользователя}

     TypeEvent:=0;

    end;

   end;

 end;

 

Программный проект (10)

Создадим модуль Events, в который поместим наиболее общие типы данных, переменные, константы, процедуры и функции, которые будут использованы при реализации программного проекта. Это касается:

1)      Всех констант и типов данных, необходимых для хранения событий (TEvent, константы событий, команд и масок) – в интерфейсную секцию.

2)      Переменных и констант очереди событий (MaxEvent, CountEvent, ChainEvent) – в секцию реализации.

3)      Процедуры добавления события в очередь (PutEvent) – в интерфейсную секцию

4)      Процедуры получения события из очереди (GetEvent) – в интерфейсную секцию, и всех глобальных типированных констант для этой процедуры (MouseX, MouseY, MouseLeftDown, MouseRightDown) – в секцию реализации.

Наш графический редактор основан на объектно-ориентированной модели, поэтому в дальнейшем мы создадим специализированный объект, в который будут включены остальные процедуры событийной модели.

Откомпилируйте полученный модуль, убедитесь в отсутствии синтаксических ошибок.


 

2.4.4        Объектно-событийная модель программы.

 

2.4.4.1              Объектная модель программы

Объектной моделью программы называется такая организация программного кода, когда (в идеальном случае) имеется только одна глобальная переменная-объект, в результате вызова методов которой выполняется программа. Все данные, в том числе и объекты, находятся внутри глобального. При этом практически не существует никаких глобальных процедур и функций, кроме тех, которые выполняют некоторые общие служебные действия, несвязанные с данными внутри объектов.

Наборы объектов внутри главного организованы в виде иерархического дерева, напоминающего иерархию классов. Каждый объект может содержать в себе набор таких же объектов или объектов, порожденных от данного класса. Объект, содержащий в себе другие объекты, является хозяином по отношении к ним, а они – подчиненными по отношению к хозяину. Одним из основных свойств хозяина является то, что он обязан уничтожить все подчиненные объекты при своем собственном уничтожении (внутри дестрактора).

Пример иерархии объектов:

Первичный класс – ОБЪЕКТ-ДЕРЕВО, порожденные – ОКНО, КНОПКА и ПРИЛОЖЕНИЕ.

В этом случае иерархия объектов в программе может выглядеть следующим образом:

В данном случае объект-приложение содержит в себе 6 других объектов, один из которых содержит два объекта.

 

2.4.4.2              Объединение объектной и событийной модели программ.

Один из наиболее эффективных путей использования объектной модели – это интегрирование ее с событийной моделью. Получающаяся в результате объектно-событийная модель обладает следующими свойствами:

1.     Каждый объект иерархии обладает своим собственным виртуальным обработчиком событий.

2.     Любой обработчик событий обязан не только сам обрабатывать события, но и предоставлять возможность подчиненным объектам делать то же самое.

3.     Каждый объект может быть модальным, то есть уметь монопольно (совместно со своими подчиненными) получать и обрабатывать все возникающие в системе события.

4.     Среди подчиненных объектов хозяина один может являться активным, то есть таким, к которому прежде всего приходят события

5.     Каждый из объектов иерархии может быть запрещенным, то есть таким, к которому не приходят события.

6.     Для визуальной среды каждый объект иерархии имеет средства визуализации.

Например:

Создадим 2 класса – Событийный класс (TEventObject) и Приложение (TApp). Для построения иерархии объектов событийный класс порожден от TCollection.

{***************-TEventObject-****************}

 { событийный класс }

 PEventObject=^TEventObject;

 TEventObject=object(TCollection)

  constructor Init(_Owner:PEventObject);

  destructor Done;virtual;

  procedure GetEvent(Var Event:Tevent);virtual;

  procedure PutEvent(Event:Tevent);virtual;

   { получает и посылает событие, используя процедуры модуля Events }

  procedure HandleEvent(Var Event:Tevent);virtual; {обработка событий}

  function HandleModal:integer;virtual; {модальная обработка событий}

   procedure SetOwner(_Owner:PEventObject);

   procedure SetActive;

   procedure DeActivate;

   procedure Disable;

   procedure Enable;

  public

   ModalResult:integer; {результат модальной обработки объекта}

   Owner,Active:PEventObject; {хозяин и активный объект иерархии}

   Disabled:boolean; {запрещенность объекта}

 end;

 

 TApp=object(TEventObject)

  constructor Init;

  procedure Run;     {эквивалент основной программы в событийной модели}

  destructor Done;virtual;

  procedure EndApp;             {завершает модальное состояние объекта-приложения, устанавливая ModalResult в 1}

  procedure HandleEvent(Var Event:Tevent);virtual;

   {обрабатывает несколько дополнительных команд }

 end;

 

{ ------- Реализация --------}

 

constructor TApp.Init;

 begin inherited init(nil); end;

 

procedure TApp.Run;

 begin HandleModal; end;

 

destructor TApp.Done;

 begin inherited done; end;

 

procedure TApp.EndApp;

 begin ModalResult:=1; end;

 

procedure TApp.HandleEvent(Var Event:Tevent);

 begin

  inherited HandleEvent(event);

  with Event do begin

   case typeEvent of

    evKeyDown:if code=#27 then EndApp;

    evCommand:if command=cmQuit then EndApp;

   end;

  end;

 end;

 

{-----------------------------------------------}

 

constructor TEventObject.Init(_Owner:PEventObject);

 begin inherited Init(10,10); SetOwner(_Owner); end;

 

destructor TEventObject.Done;

 begin inherited done; end;

 

procedure TEventObject.PutEvent(Event:Tevent);

 begin events.putevent(event) end;

 

procedure TEventObject.GetEvent(var Event:Tevent);

 begin events.Getevent(event) end;

 

function TEventObject.HandleModal:integer;

  var E:TEvent;

 begin

  ModalResult:=0;

  repeat

   GetEvent(e); HandleEvent(e);

  until ModalResult<>0;

  HandleModal:=ModalResult;

 end;

 

procedure TEventObject.HandleEvent(Var Event:Tevent);

  var i:integer;

      P:PEventObject;

 begin

  if active<>nil then active^.HandleEvent(event);

  for i:=count-1 downto 0 do begin

   if Event.TypeEvent=evNothing then exit;

   p:=PEventObject(at(i));

   if (p<>active) and (not p^.disabled) then p^.HandleEvent(event);

  end;

 end;

 

procedure TEventObject.SetOwner(_Owner:PEventObject);

 begin

  if owner<>nil then owner^.Delete(@self);

  owner:=_owner; if owner<>nil then owner^.Insert(@self);

 end;

 

procedure TEventObject.SetActive;

 begin if owner<>nil then owner^.Active:=@self; end;

 

procedure TEventObject.DeActivate;

 begin if owner<>nil then owner^.Active:=nil; end;

 

procedure TEventObject.Disable;

 begin disabled:=true; end;

 

procedure TEventObject.Enable;

 begin disabled:=false; end;

 

Для разработки программы в рамках объектно-событийной модели определяют набор необходимых классов, порожденных от событийного класса, а также класс-оболочку, порожденный от класса–приложения, перекрывая в случае необходимости требуемые виртуальные методы для организации интерфейса, реакций на события и т.д.

Программный проект (11)

Дополним классами TEventObject и TApp модуль Events и начнем создание интерактивной среды графического редактора.

Применяя к разрабатываемому графическому редактору объектно-событийную модель, можно определить классы

Визуальный класс (TView=object(TEventObject)),

Кнопка (TButton= object(TView)):

Зависимая кнопка (TCheckButton= object(TButton))

Прямоугольник выбора цвета (TColorBar= object(TView))

Холст (TCanvas= object(TView)),

Приложение (TApplication=object(TApp)),

со следующими характеристиками

1.      Визуальный класс:

Визуальный класс определяет абстрактный метод Show, используемый потомками для визуализации своего содержимого, а также обработку команды cmShow. Визуальный класс рассчитывает, что все подчиненные ему объекты также являются визуальными, поэтому (при обработке cmShow) для каждого из них вызывается метод Show. Визуальный класс имеет координаты в виде прямоугольника, может устанавливать область вывода в эти координаты.

2.      Кнопка:

Кнопки используются для передачи команд другим объектам приложения, формируя эти команды из событий мыши и нажатия клавиш. При инициализации экземпляра кнопки ей передаются координаты прямоугольника, строка, обозначающая надпись на кнопке, код клавиши, при нажатии которую происходит срабатывание кнопки, а также команда, которая должна возникать при срабатывании кнопки. Анализируя поступающие к ней события, кнопка в требуемый момент посылает свою команду в очередь событий.

3.         Зависимая кнопка:

Имеет следующие отличия от обычной кнопки:

При нажатии посылает команду в очередь событий, причем остается нажатой. Кнопки данного типа могут группироваться. В этом случае нажатие на одну из кнопок группы вызывает отжатие уже нажатой в данной группе. При инициализации ей дополнительно к стандартным для кнопки параметрам передается номер группы, в которую она входит.

4.         Прямоугольник выбора цвета

Прямоугольник выбора цвета используется для интерактивного выбора цвета в графическом редакторе. Представляет собой прямоугольник с набором из 16 цветов, один из которых всегда выбран (отмечен). Нажатие на другой цвет выбирает его.

5.      Холст:

Холст используется для хранения и визуализации коллекции графических объектов, обрабатывает события мыши и команды переключения режимов, направленные от кнопок или другого источника. При инициализации ему передаются координаты рабочего прямоугольника, в котором происходит визуализация коллекции графических объектов. Коллекция графических примитивов хранится отдельным полем внутри холста, не входя в иерархию объектов программы.

6.      Приложение:

Приложение реализует инициализацию и завершение графики (в методах INIT, DOWN), а также подготовку рабочего стола (в методе INIT), которая заключается во вставке туда одного холста, требуемого набора кнопок и прямоугольника для выбора цвета (вставка производится в сам объект Приложение). Дополнительно, приложение дублирует возможности Визуального класса.

 

Реализацию холста и приложения мы проведем в фазе 12 программного проекта, так как концепция режима, активно используемая в методах холста, будет подробно рассмотрена только в следующем параграфе, а в конструкторе приложения кнопкам назначаются команды, обрабатываемые холстом.

Создадим новый модуль GRED.PAS, где разместим собственно тело редактора, то есть все объекты, определенные в текущей фазе, и все сопутствующие данные, процедуры, функции и т.д.

Unit GRED;

Interface

uses Graph,objects,crt,mouse,grprim,events;

{ определим команду, используемую зависимой кнопкой при своем собственном нажатии для "отжатия" уже нажатой кнопки в группе }

const

 cmUnCheck=333;

 

type

 PView=^TView;

 PButton= ^TButton;

 PCheckButton= ^TCheckButton;

 PColorBar= ^TColorBar;

 

{ -- TView -- }

  { Визуально-событийный абстрактный класс }

 TView=object(TEventObject)

  Rect:TRect; { прямоугольник вывода }

  constructor Init(_Owner:PEventObject; x1,y1,x2,y2:integer);

  procedure Show;virtual; { абстрактный метод }

  procedure HandleEvent(Var Event:Tevent);virtual;

   { обрабатывает команду cmShow}

  procedure SetView; { устанавливает окно вывода }

  procedure SetStandartView;{ устанавливает окно вывода на весь экран}

  procedure ResetView;        { устанавливает окно вывода, существовавшее до вызова SetView и SetStandartView}

   private

    oldView:ViewPortType; {поле для хранения "старого" окна вывода}

 end;

 

{ --TButton-- }

  { Класс "Кнопка". При нажатии посылает команду в очередь событий }

 TButton= object(TView)

   Caption:string[20]; { надпись на кнопке }

   Code:Char; { код клавиши ... }

   Ext:boolean;           {... и признак расширенного кода, при нажатии на которую срабатывает кнопка }

   Command:integer; {команда. которая посылается в очередь событий}

   constructor Init(_Owner:PEventObject; x1,y1,x2,y2:integer;s:string; c:char; _ext:boolean;cm:integer);

   procedure Show;virtual;

   procedure HandleEvent(Var Event:Tevent);virtual;

   procedure ButtonDown;virtual;

    { Вызывается при нажатии на кнопку. В классе TButton анимирует нажатие }

 end;

 

{--TCheckButton--}

  { Класс "Зависимая кнопка". При нажатии посылает команду в очередь событий, причем остается нажатой. Кнопки данного типа могут группироваться. В этом случае нажатие на одну из кнопок группы вызывает отжатие уже нажатой в данной группе. Активно используется для переключения в режимы рисования различных примитивов }

 TCheckButton= object(TButton)

   Checked:boolean; { признак нажатия-отжатия }

   Group:integer; { номер группы }

   constructor Init(_Owner:PEventObject; x1,y1,x2,y2:integer;s:string; c:char; _ext:boolean;cm:integer; _group:integer);

   procedure Show;virtual;

   procedure ButtonDown;virtual;

    { визуализирует нажатую кнопку и посылает в очередь событий команду cmUnCheck }

   procedure HandleEvent(Var Event:Tevent);virtual;

    { обрабатывает команду cmUnCheck }

 end;

 

{--TColorBar--}

  { "Прямоугольник выбора цвета". Используется для интерактивного выбора цвета }

 TColorBar= object(TView)

  Color:word; { выбранный цвет }

  constructor Init(_Owner:PEventObject; x1,y1,x2,y2:integer);

  procedure Show;virtual;

  procedure ShowCell(R:TRect; _Color:word);

   { показывает одну ячейку в переданном прямоугольнике и переданным цветом}

  procedure SetBarColor(_Color:word); { рисует маркер на выбранном цвете }

  function  GetCell(x,y:integer):word;

   { по координатам возвращает номер цвета }

  procedure GetCellRect(_Color:word; var R:TRect);

   { по цвету возвращает координаты цветового прямоугольника }

  procedure HandleEvent(Var Event:Tevent);virtual;

 end;

implementation

uses inter;

 

{ ------------ TView ------------- }

 procedure TView.Show;

{ абстрактный метод для визуализации объекта, перекрывается во всех потомках }

 begin abstract; end;

 

 procedure TView.HandleEvent;

   var i:integer;

  begin

    if (Event.TypeEvent=evCommand) and (Event.Command=cmShow) then begin

{Обработка команды cmShow}

     Show; For i:=0 to count-1 do PView(at(i))^.Show;

{показ самого объекта, затем – показ всех подчиненных}

     Event.TypeEvent:=0;exit;

    end;

    inherited HandleEvent(Event);

  end;

 

 constructor TView.Init(_Owner:PEventObject; x1,y1,x2,y2:integer);

  begin

   inherited init(_Owner); Rect.assign(x1,y1,x2,y2);

  end;

 

 procedure TView.SetView;

  begin

    GetViewSettings(oldView); with rect do SetViewPort(a.x,a.y,b.x,b.y,true);

  end;

 

 procedure TView.SetStandartView;

  begin

    GetViewSettings(oldView); SetViewPort(0,0,GetMaxX,GetMaxY,true);

  end;

 

 procedure TView.ResetView;

  begin with oldView do SetViewPort(x1,y1,x2,y2,true); end;

 

{ ------------ TButton ------------- }

constructor TButton.Init(_Owner:PEventObject;x1,y1,x2,y2:integer; s:string;c:char;_ext:boolean;cm:integer);

  begin

   inherited init(_owner,x1,y1,x2,y2);

   Caption:=s; Code:=C; Ext:=_ext; Command:=cm;

  end;

 

 procedure TButton.ButtonDown;

  begin

      with rect do begin

       hidemouse; SetStandartview; setcolor(15); setWriteMode(XorPut);

       SetLineStyle(SolidLn,0,1); rectangle(a.x,a.y,b.x,b.y);

       delay(100);

       rectangle(a.x,a.y,b.x,b.y); setWriteMode(NormalPut);

       ReSetview; showmouse;

      end;

  end;

 

procedure TButton.Show;

   var dx,dy,x,y:integer;

  begin

   hidemouse; SetStandartView;

   with rect do begin

    Showbar(a.x,a.y,b.x,b.y,7,true);

    if not disabled then SetColor(0) else setcolor(8);

    SetTextstyle(2,0,0); dx:=TextWidth(caption); dy:=TextHeight(caption);

    x:=a.x+(b.x-a.x-dx)div 2; y:=a.y+(b.y-a.y-dy)div 2;

    OutTextXY(x,y,Caption);

   end;

   ResetView; showmouse;

  end;

 

 procedure TButton.HandleEvent(Var Event:Tevent);

   var E:TEvent;

  begin

   if ((event.typeEvent=evKeyBoard)

      and(event.code=code)

      and(event.ext=ext))

            or

      ((event.typeEvent=evMouseDown)

      and rect.contains(event.xy))

   then begin

      e.TypeEvent:=evCommand; e.Command:=Command;

      PutEvent(e);

      Event.TypeEvent:=0;

      ButtonDown;

   end;

   inherited HandleEvent(Event);

  end;

 

{ ------------ TCheckButton ------------- }

constructor TCheckButton.Init(_Owner:PEventObject; x1,y1,x2,y2:integer; s:string; c:char;_ext:boolean;cm:integer; _group:integer);

  begin

   inherited init(_owner,x1,y1,x2,y2,s,c,_ext,cm); checked:=false; group:=_group;

  end;

 

 procedure TCheckButton.ButtonDown;

   var E:TEvent;

  begin

   checked:=true; show;

   e.TypeEvent:=evCommand; e.Command:=cmUnCheck;

   e.pinfo:=@self;

   PutEvent(e);

  end;

 

 procedure TCheckButton.Show;

   var dx,dy,x,y:integer;

  begin

   hidemouse; SetStandartView;

   with rect do begin

    showbar(a.x,a.y,b.x,b.y,7,not checked);

    if not disabled then SetColor(0) else setcolor(8);

    SetTextstyle(2,0,0); dx:=TextWidth(caption); dy:=TextHeight(caption);

    x:=a.x+(b.x-a.x-dx)div 2; y:=a.y+(b.y-a.y-dy)div 2;

    OutTextXY(x,y,Caption);

   end;

   ResetView; showmouse;

  end;

 

 procedure TCheckButton.HandleEvent(Var Event:Tevent);

  begin

   if (event.typeEvent=evCommand)

      and(event.Command=cmUnCheck)

      and(event.pinfo<>@self)

      and(pCheckButton(event.pinfo)^.group=group)

      and(checked) then begin

       checked:=false; show;

      end;

   inherited HandleEvent(Event);

  end;

 

{ ------------ TColorBar ------------- }

  constructor TColorBar.Init(_Owner:PEventObject; x1,y1,x2,y2:integer);

   begin inherited init(_Owner,x1,y1,x2,y2); SetBarColor(15);  end;

 

  function TColorBar.GetCell(x,y:integer):word;

    var dx,dy:integer;

   begin

    with rect do begin

     x:=x-a.x; y:=y-a.y; dx:=(b.x-a.x)div 8; dy:=(b.y-a.y)div 2;

     GetCell:=(x div dx)+8*(y div dy);

    end;

   end;

 

  procedure TColorBar.GetCellRect(_Color:word; var R:TRect);

    var dx,dy,x,y,i:integer;

   begin

    with rect do begin

     dx:=(b.x-a.x)div 8; dy:=(b.y-a.y)div 2;

     x:=a.x+dx*(_color mod 8); y:=a.y+dy*(_color div 8);

    end;

    R.assign(x,y,x+dx-1,y+dy-1);

   end;

 

   procedure TColorBar.ShowCell(R:TRect; _color:word);

     begin with r do Showbar(a.x,a.y,b.x,b.y,_color,false); end;

 

  procedure TColorBar.Show;

    var i:integer;R:TRect;

   begin

    hidemouse; SetStandartView;

    with rect do for i:=0 to 15 do begin GetCellRect(i,R); ShowCell(R,i) end;

    SetBarColor(Color); ResetView; showmouse;

   end;

 

  procedure TColorBar.SetBarColor(_Color:word);

    var R:trect; oldColor:word;

   begin

    hidemouse; SetStandartView;

     GetCellRect(Color,R); oldColor:=Color; Color:=_Color;

     ShowCell(R,OldColor); GetCellRect(Color,R);

     if Color in [3,7,10..15] then SetColor(0) else Setcolor(15);

     with r do Circle(a.x+(b.x-a.x)div 2,a.y+(b.y-a.y)div 2,2);

    ResetView; showmouse;

   end;

 

  procedure TColorBar.HandleEvent(Var Event:Tevent);

   begin

    inherited HandleEvent(event);

    if (event.typeEvent=evMouseDown) and Rect.contains(event.xy) then begin

       SetBarColor(GetCell(event.xy.x,event.xy.y)); event.typeevent:=0;

    end;

   end;

 

Рассмотрим некоторые моменты в реализации методов классов.

1.      Стандартный способ прорисовки любого класса, порожденного от TView состоит в следующих действиях:

hidemouse; SetStandartview;

{ прорисовка объекта в абсолютных координатах }

ResetView; showmouse;

или:

hidemouse; SetView;

{ прорисовка объекта в относительных координатах }

ResetView; showmouse;

2.      При нажатии на зависимую кнопку в очередь событий посылается не одно, а два события: одно – в унаследованным от кнопки методе HandleEvent, второе – в перекрытом методе ButtonDown. В последнем случае посылается команда cmUnCheck, которую обрабатывают все зависимые кнопки в группе, кроме пославшей команду.

3.      В большом программном проекте любой класс должен поддерживать универсальность отображения, реакции на события и т.д. Поэтому вывод текста в TButton и TCheckButton и цветов в TColorBar универсален. И текст и цвета всегда будут отображаться правильно вне зависимости от размеров объектов.

 

2.4.4.3              Режим работы объекта в объектно-событийной модели

При разработке БПП большое значение имеет понятие режима.

Под режимом (MODE) будем понимать состояние объекта (программы), в котором он обладает уникальной реакцией на совокупность событий.

Рассмотрим несколько примеров из графического редактора:

Объект зависимая кнопка может находится в двух состояниях – нажатом и отжатом. В зависимости от текущего состояния (режима) изменяется реакция на события (если кнопка нажата, то щелчок мыши на ней не вызовет ответной реакции)

Объект холст может находится в нескольких состояниях – удаления, рисования, перемещения примитива и т.д. Реакция холста на события мыши и клавиатуры, таким образом, напрямую зависит от текущего режима.

Программирование режимов объекта (программы) обладает некоторыми особенностями:

1.      Всегда существует специальное средство для определения текущего режима – переменная(-ые), поле(-я) объекта, функция, метод объект, сам(и) объект(ы) и т.д., которое мы будем называть идентификатором режима (ИДР)

2.      Установка режима сопровождается некоторым программным кодом

Мало установить значение поля, отвечающие за "нажатость" кнопки, в true, требуется еще и перерисовать кнопку в "нажатом" виде.

3.      Выход из режима (вне зависимости от нового значения ИДР) также сопровождается программным кодом

В графическом редакторе переход из режима рисования фигуры в любой другой режим (то есть выход из режима рисования), должен сопровождаться действиями, которые либо отменяют рисование, либо помещают рисуемую в настоящий момент фигуру в графическую коллекцию (в зависимости от соглашения), а также перерисовывают холст. Эти действия сопровождают и неразрывно связаны именно с выходом из режима рисования фигур, а не с переходом в какой-либо другой режим.

4.      Для удобства программирования ИДР может быть составным. В результате программа или объект может иметь несколько режимов на одном (нулевом) уровне, причем любой из них может иметь один или несколько подрежимов (SUBMODE) первого уровня, определяемых своими собственными ИДР, каждый из которых может иметь подрежимы второго уровня и т.д.

В графическом редакторе режим рисования имеет два подрежима – статический режим (до начала рисования) и динамический (в процессе рисования), причем второй имеет еще один подрежим – для обозначения рисуемого примитива.

 

Программный проект (12)

Дополним модуль GRED.PAS реализацией холста (TCanvas) и приложения (TApp).

Определим константы режимов холста графического редактора:

Unit GRED;

Interface

{РЕЖИМЫ ДЛЯ TCanvas}

 modeStandart=0;

 modeRis=1; {рисование}

  {ПОДРЕЖИМ1}

  {подрежим 1 соответствует подготовке и самому процессу рисования}

  smodeRisBegin=0; {подготовка рисования}

  smodeRis=1; {процесс рисования}

   {ПОДРЕЖИМ2}

   {подрежим 2 соответствует типу рисуемого примитива}

 modeDel=2; {удаление примитива}

  {подрежимов нет}

 modeMove=3; {перемещение примитива}

  {ПОДРЕЖИМ1}

  {подрежим 1 соответствует подготовке и самому процессу перемещения}

  smodeMoveBegin=0; {подготовка перемещения}

  smodeMove=1; {процесс перемещения}

 

{ Определим команды, которые будут обрабатывать холст. Для простоты все команды, кроме команды выхода, обрабатываются холстом}

{ одиночные команды }

 

 cmRead=2;

 cmSave=3;

 cmClear=4;

 cmShow=5;

 cmSaveToPas=9;

 

  {команды переключения в режимы}

 cmToLine=10001;

 cmToCircle=10002;

 cmToRect=10003;

 cmToEllipse=10004;

 cmToDel=10011;

 cmToMove=10012;

 

type

{--TCanvas--}

{ "Холст". Используется для интерактивного построения и прорисовки набора графических примитивов }

 PCanvas= ^TCanvas;

 TCanvas= object(TView)

  PG:PGrCollection; { коллекция графических примитивов }

  Mode:integer; { текущий режим }

  SubMode,SubMode2:integer; { подрежимы }

  CurrObj:PGrObject; { текущий рисуемый (перемещаемый и т.д.) примитив }

  constructor Init(_Owner:PEventObject;x1,y1,x2,y2:integer);

  destructor Done;virtual;

  procedure Show;virtual;

  procedure Clear; { очищает холст, уничтожая коллекцию примитивов }

  procedure HandleDown(xy:tpoint; buttons:byte);

  procedure HandleMove(xy:tpoint; buttons:byte);

  procedure HandleUp(xy:tpoint; buttons:byte);

   { процедуры HandleDown, HandleMove и HandleUp вызываются из HandleEvent при соответствующих событиях мыши }

  procedure CancelDynamicMode;

   { отменяет динамический подрежим (smodeRis,smodeMove), если он установлен, переводя его в соответствующий подготовительный подрежим (smodeRisBegin,smodeMoveBegin). Используется обычно перед установкой другого режима или подрежима (например, до завершения рисования линии пришла команда о начале рисования окружности) }

  procedure BeginRis(xy:tpoint);

  procedure EndRis(xy:tpoint);

  procedure MoveRis(xy:tpoint);

   { процедуры BeginRis, EndRis и MoveRis вызываются из HandleDown и HandleMove в начале, завершении и протяжении рисования примитива. Используются в целях установки специфичных для каждого графического примитива параметров }

  procedure HandleEvent(Var Event:Tevent);virtual;

   { в дополнении к обработке команд производит перевод координат мыши из абсолютных в относительные }

  procedure LoadFromFile;

  procedure SaveToFile;

  procedure SaveToPas;

   { процедуры чтения и сохранения коллекции примитивов в файле, а также сохранения в паскалевском формате}

  private

    OldPoint:TPoint;

 end;

 

{--TApplication--}

 { "Приложение" }

 TApplication=object(TApp)

  constructor Init;

   { создает и располагает на экране набор визуальных объектов }

  procedure ShowAll;

   { так как TApplication порожден не от TView, требуется метод ShowAll для показа содержимого приложения }

  destructor Done;virtual;

 end;

 

{--Переменные--}

 var ColorBar:PColorBar;

     { ссылка на прямоугольник выбор цвета. Используется для доступа к текущему установленному цвету примитивов }

 

implementation

uses inter;

 

  { ------------ TCanvas ------------- }

  procedure TCanvas.CancelDynamicMode;

   begin

    if (mode=moderis)and(submode=smoderis) then begin

     submode:=smodeRisBegin; pg^.Delete(currobj);

     hidemouse; SetView; show; ReSetView; showmouse;

     CurrObj:=nil;

    end

    else

    if (mode=modeMove)and(submode=smodeMove) then begin

      submode:=smodeRisBegin;

      hidemouse; SetView; currObj^.enable; show; ReSetView; showmouse;

      CurrObj:=nil;

     end;

   end;

 

  procedure TCanvas.BeginRis(xy:tpoint);

   begin

    case submode2 of

     typeline: CurrObj:=new(pGrline, init(xy.x,xy.y,xy.x,xy.y,0,1,1,ColorBar^.Color));

     typeRect: CurrObj:=new(pGrRect, init(xy.x,xy.y,xy.x,xy.y,0,1,1,ColorBar^.Color));

     typeCircle: CurrObj:=new(pGrCircle,init(xy.x,xy.y,0,0,1,1,ColorBar^.Color));

     typeEllipse: ;

    end;

   end;

 

  procedure TCanvas.EndRis(xy:tpoint);

   begin

    case submode2 of

     typeline,typerect: with pgrline(CurrObj)^.CoordLine do begin

       b.x:=xy.x; b.y:=xy.y;

       if submode2=typerect then begin

        normalizedRect(pgrRect(CurrObj)^.CoordLine);

       end;

      end;

     typeCircle: with pgrCircle(CurrObj)^ do begin

       with coordCircle do radius:=round(sqrt(sqr(int(x-xy.x))+sqr(int(xy.y-y))));

      end;

     typeEllipse: ;

    end;

   end;

 

  procedure TCanvas.MoveRis(xy:tpoint);

   begin

        case submode2 of

         typeline,typeRect: with pgrline(CurrObj)^.CoordLine do begin

            b.x:=xy.x; b.y:=xy.y;

           end;

         typeCircle:with pgrCircle(CurrObj)^ do begin

          with coordCircle do radius:=round(sqrt(sqr(int(x-xy.x))+sqr(int(y-xy.y))));

          end;

         typeEllipse: ;

        end;

   end;

 

  procedure TCanvas.HandleDown(xy:tpoint; buttons:byte);

   begin

    case mode of

     modeRis: begin

       case submode of

        smodeRisBegin: begin

          BeginRis(xy); pg^.Insert(CurrObj);

          hidemouse; SetView; CurrObj^.show; ResetView; showmouse;

          submode:=smodeRis;

         end;

        smodeRis: begin

         if buttons=mbRight then begin

          CancelDynamicMode; exit;

         end;

         EndRis(xy); submode:=smodeRisBegin;

         hidemouse; SetView; show; ReSetView; showmouse;

         CurrObj:=nil;

        end;

       end;

      end;

     modeDel:begin

       CurrObj:=pg^.ObjectOnPoint(XY);

       if CurrObj<>nil then begin pg^.Delete(CurrObj); show; end;

      end;

     modeMove:begin

       case submode of

        smodeMoveBegin: begin

          CurrObj:=pg^.ObjectOnPoint(XY);

          if CurrObj<>nil then begin

           hidemouse; SetView; submode:=sModeMove; OldPoint:=XY;

           CurrObj^.disable; Show; CurrObj^.XorShow; ResetView; showmouse;

          end;

         end;

        smodeMove: begin

         if buttons=mbRight then begin

          CancelDynamicMode; exit;

         end;

         submode:=smodeRisBegin;

         hidemouse; SetView; currObj^.enable; show; ReSetView; showmouse;

         CurrObj:=nil;

        end;

       end;

      end;

    end;

   end;

 

  procedure TCanvas.HandleMove(xy:tpoint; buttons:byte);

    var dx,dy:integer;

   begin

    case mode of

     modeRis: begin

       if submode=smodeRis then begin

        hidemouse; SetView; CurrObj^.XorShow;

        MoveRis(xy);

        CurrObj^.XorShow; ResetView; showmouse;

       end;

      end;

     modeMove:begin

       if submode=smodeMove then begin

        hidemouse; SetView; CurrObj^.XorShow;

        dx:=xy.x-oldpoint.x; dy:=xy.y-oldpoint.y;

        CurrObj^.Move(dx,dy); OldPoint:=xy;

        CurrObj^.XorShow; ResetView; showmouse;

       end;

      end;

    end;

   end;

 

  procedure TCanvas.HandleUp(xy:tpoint; buttons:byte);

   begin

    { зарезервировано }

   end;

 

 constructor TCanvas.Init(_Owner:PEventObject; x1,y1,x2,y2:integer);

  begin

   inherited init(_owner,x1,y1,x2,y2); mode:=modeStandart;

   pg:=new(pGrCollection,Init(100,10));

  end;

 

 destructor TCanvas.Done;

  begin dispose(pg,done); inherited done; end;

 

 procedure TCanvas.Clear;

  begin pg^.freeall; show; end;

 

 procedure TCanvas.Show;

    var V:ViewPortType;

  begin

   hidemouse; setView; setfillstyle(solidfill,0);

   with rect do bar(0,0,b.x-a.x,b.y-a.y);

   pg^.show; ResetView; showmouse;

  end;

 

 procedure TCanvas.HandleEvent(Var Event:Tevent);

   var s:string;

  begin

   inherited HandleEvent(event);

   if event.typeEvent and evMouse <> 0 then begin

    if Rect.contains(event.xy) then begin

     dec(event.xy.x,rect.a.x); dec(event.xy.y,rect.a.y);

     with event do begin

      case event.TypeEvent of

       evMouseDown: HandleDown(xy, buttons);{обработка нажатий}

       evMouseMove: HandleMove(xy, buttons);{перемещений}

       evMouseUp: HandleUp(xy, buttons); {отжатий}

      end;

      Event.TypeEvent:=0;

     end;

    exit;

   end;

   end;

   if event.TypeEvent=evCommand then begin

    case Event.Command of

     cmToLine:begin

       CancelDynamicMode;

       Mode:=ModeRis;

       SubMode:=sModeRisBegin; SubMode2:=typeLine;

      end;

     cmToRect:begin

       CancelDynamicMode;

       Mode:=ModeRis;

       SubMode:=sModeRisBegin; SubMode2:=typeRect;

      end;

     cmToCircle:begin

       CancelDynamicMode;

       Mode:=ModeRis;

       SubMode:=sModeRisBegin; SubMode2:=typeCircle;

      end;

     cmToDel:begin

       CancelDynamicMode;

       Mode:=ModeDel;

      end;

     cmToMove:begin

       CancelDynamicMode;

       Mode:=ModeMove;

      end;

     cmRead:begin

       CancelDynamicMode;

       LoadFromFile;

      end;

     cmSave:begin

       CancelDynamicMode;

       SaveToFile;

      end;

     cmSaveToPas:begin

       CancelDynamicMode;

       SaveToPas;

      end;

     cmClear:begin

       CancelDynamicMode;

       Clear;

      end

     else exit;

    end;

   end;

  end;

 

 procedure TCanvas.LoadFromFile;

   var s:string; stm:tbufstream;

  begin

      s:='noname.stm';

      if inputstring(12,'Загрузка файла',s) then begin

        if not exists(s) then begin messagebox('Такого файла нет!'); exit;

        end;

        dispose(pg,done);

        stm.init(s,stopenread,1024); pg:=pgrcollection(stm.Get); stm.done;

        show;

      end;

  end;

 

 procedure TCanvas.SaveToFile;

   var s:string; stm:tbufstream;

  begin

      s:='noname.stm';

      if inputstring(12,'Сохранение файла',s) then begin

        stm.init(s,stCreate,1024); stm.Put(pg); stm.done;

      end;

  end;

 

 procedure TCanvas.SaveToPas;

   var s,n:string;

  begin

      s:='include.inc'; n:='Paint';

      if inputstring(12,'Сохранение в тексте',s) and

         inputstring(24,'Введите имя процедуры',n)

         then pg^.saveAsPas(s,n);

  end;

 

 { ------------ TApplication ------------- }

 constructor TApplication.Init;

   var obj:pView; dr,dm,gError:Integer; x,y,dx,dy:integer;

  begin

   inherited Init; dr := vga; dm := vgaHi; InitGraph(dr,dm,' ');

   gError := GraphResult;

   if gError <> grOk then begin

    Writeln('Graphics error:', GraphErrorMsg(gError)); halt(1);

   end;

   CHKAndReset;

   obj:=new(PCanvas,Init(@Self,20,20,getmaxx-20,400));

   x:=20;y:=410;

   dx:=65; dy:=17;

   obj:=new(PCheckButton,Init(@Self,x,y,x+dx,y+dy,'Линия',#0,false,cmtoline,0));

   inc(x,0); inc(y,dy+2);

   obj:=new(PCheckButton,Init(@Self,x,y,x+dx,y+dy,'Прям.',#0,false,cmtorect,0));

    inc(x,dx+2); inc(y,-dy-2);

   obj:=new(PCheckButton,Init(@Self,x,y,x+dx,y+dy,'Окр.',#0,false,cmtocircle,0));

   inc(x,0); inc(y,dy+2);

   obj:=new(PCheckButton, Init(@Self,x,y,x+dx,y+dy,'Эллипс',#0,false,cmtoellipse,0));

   obj^.disable;

   inc(x,dx+2); inc(y,-dy-2);

   obj:=new(PCheckButton, Init(@Self,x,y,x+dx,y+dy,'Ломаная',#0,false,cmtocircle,0));

   obj^.disable;

   inc(x,0); inc(y,dy+2);

   obj:=new(PCheckButton, Init(@Self,x,y,x+dx,y+dy,'З.Прям.',#0,false,cmtoellipse,0));

   obj^.disable;

   inc(x,dx+2); inc(y,-dy-2);

   obj:=new(PCheckButton, Init(@Self,x,y,x+dx,y+dy,'З.Окр.',#0,false,cmtocircle,0));

   obj^.disable;

   inc(x,0); inc(y,dy+2);

   obj:=new(PCheckButton, Init(@Self,x,y,x+dx,y+dy,'З.Элл.',#0,false,cmtoellipse,0));

   obj^.disable;

   inc(x,dx+2); inc(y,-dy-2);

   obj:=new(PCheckButton, Init(@Self,x,y,x+dx,y+dy,'З.Мног.',#0,false,cmtocircle,0));

   obj^.disable;

   inc(x,0); inc(y,dy+2);

   obj:=new(PCheckButton, Init(@Self,x,y,x+dx,y+dy,'Заливка',#0,false,cmtoellipse,0));

   obj^.disable;

   inc(x,dx+10); inc(y,-dy-2);

   obj:=new(PCheckButton, Init(@Self,x,y,x+dx,y+dy,'Удал.',#66,true,cmToDel,0));

   inc(x,0); inc(y,dy+2);

   obj:=new(PCheckButton, Init(@Self,x,y,x+dx,y+dy,'Перем.',#0,false,cmToMove,0));

   x:=20; y:=455;

   dx:=70; dy:=20;

   obj:=new(PButton,Init(@Self,x,y,x+dx,y+dy,'Выход',#0,false,cmQuit));

   inc(x,dx+5);

   obj:=new(PButton,Init(@Self,x,y,x+dx,y+dy,'Загр.',#61,true,cmread));

   inc(x,dx+5);

   obj:=new(PButton,Init(@Self,x,y,x+dx,y+dy,'Сохр.',#60 ,true,cmsave));

   inc(x,dx+5);  dx:=100;

   obj:=new(PButton, Init(@Self,x,y,x+dx,y+dy,
'
Сохр. в PAS',#63,true,cmSavetoPas));

    x:=364;

    obj:=new(PButton,Init(@Self,x,y,x+dx,y+dy,'Очистка',#59,true,cmclear));

    ColorBar:=new(pColorBar,init(@Self,500,420,620,460));

    ShowAll;

   end;

 

 destructor TApplication.Done;

   begin CloseGraph; inherited Done; end;

 

 procedure TApplication.ShowAll;

   var e,e1:TEvent;i:integer;

  begin

   hidemouse; setfillstyle(solidfill,7); bar(0,0 ,getmaxx,getmaxy);

   with e1 do begin TypeEvent:=evCommand; Command:=cmShow end;

   for i:=0 to Count-1 do begin e:=e1; PView(at(i))^.HandleEvent(e); end;

   showmouse;

  end;

end.

 

Прокомментируем некоторые методы:

1.      Метод ShowAll приложения дублирует возможности метода Show класса TView.

2.      Иерархия объектов приложения строится внутри констрактора. Любые изменения в интерфейсе желательно сосредоточить именно там.

3.      Для простоты управления иерархия объектов в графическом редакторе одноуровневая, то есть все объекты принадлежат объекту "приложение" напрямую.

4.      Принцип интерактивного создания примитива состоит в следующем: При щелчке мыши на холсте в режиме рисования происходит переход в режим динамического рисования, создается соответствующий примитив, временно запрещается и вставляется в коллекцию. Указатель на этот примитив дублируется в специальной переменной, с которой и ведется дальнейшая работа. При перемещении мыши изменяется одна из групп координат примитива, причем прорисовка ведется методом XorShow. При повторном нажатии снова изменяются координаты примитива, он разрешается для показа, обнуляется временная переменная для хранения указателя на примитив, холст перерисовывается, происходит переход в режим рисования.

5.      Принцип интерактивного перемещения примитива состоит в следующем: при щелчке мыши в режиме перемещения определяется примитив, на котором произошло нажатие и если такой существует, то происходит переход в режим динамического перемещения, примитив временно запрещается, указатель на него копируется в специальную переменную для дальнейшей работы. При изменении координат курсора мыши в режиме динамического перемещения, изменяются и координаты примитива методом TGrObject.Move. Перерисовка примитива производится методом TGrObject.XorShow. При повторном щелчке кнопкой мыши происходит переход из режима динамического перемещения в режим перемещения, а переменная для хранения указателя на примитив обнуляется. Методы для прорисовки и перемещения примитива являются виртуальными, поэтому добавление нового вида примитива не требует изменения кода для перемещения примитивов.

6.      Принцип интерактивного удаления примитива состоит в следующем: при щелчке мыши в режиме удаления определяется примитив, на котором произошло нажатие и если такой существует, то происходит его удаление из коллекции, после чего холст перерисовывается.

7.      Для интерактивного создания нового вида примитивов следует:

a.      Определить команду-событие для перехода в режим рисования соответствующего примитива (например - cmToPoly)

b.      Создать кнопку или другое средство для возникновения данного события.

c.      Реализовать переход в режим рисования примитива в разделе обработки команд метода TCanvas.HandleEvent.

d.      Добавить соответствующие действия (по аналогии с уже существующими) в методы TCanvas.BeginRis, TCanvas.EndRis, TCanvas.MoveRis. При необходимости добавить действия в TCanvas.CancelDynamicMode.

 

Теперь, по завершении работы с модулем gred.pas, следует создать основную программу, которая будет выглядеть очень просто:

uses Gred;

 var A:TApplication;

 begin

  a.init;

  a.run;

  a.done;

 end.

Графический редактор готов к использованию.

 

Задания для программного проекта (81-91)

Задание 81. Определите класс TSpeedButton – кнопка с растровым рисунком вместо текста. Проверьте его работоспособность.

Задание 82. Определите класс TVSpeedButton – кнопка с векторным рисунком вместо текста. Проверьте его работоспособность.

Задание 83. Определите класс для интерактивного выбора стиля линий (по аналогии с TColorBar). Проверьте его работоспособность.

Задание 84. Определите класс для интерактивного выбора толщины линий (по аналогии с TColorBar). Проверьте его работоспособность.

Задание 85. Определите класс для интерактивного выбора стиля заливки (по аналогии с TColorBar). Проверьте его работоспособность.

Задание 86. Реализуйте интерактивную прорисовку классов TGrEllipse, TGrBar, TGrFillEllipse.

Задание 87. Реализуйте интерактивную прорисовку классов TGrArcCircle, TGrArcEllipse.

Задание 88. Реализуйте интерактивную прорисовку класса TGrText.

Задание 89. Реализуйте интерактивную прорисовку класса TGrPicture.

Задание 90. Реализуйте интерактивную прорисовку классов TGrPoly, TGrFillPoly.

Задание 91. Реализуйте интерактивную прорисовку класса TGrFill


 

2.4.1        Особенности отладки больших программных проектов (БПП), основанных на объектно-событийной модели.

 

Виды ошибок идентичны как для небольших программ, так и для БПП. Однако размеры БПП и специфическая структура программы значительно усложняют процесс отладки.

 

2.4.1.1               Синтаксические ошибки в БПП.

Частота возникновения синтаксических ошибок не зависит от структуры и размеров программы. Однако особенности процесса ввода БПП в компьютер позволяют сделать несколько выводов. В отличие от малых программ, БПП компилируются и тестируются гораздо большими по объему блоками. При этом компилятор может найти целый набор синтаксических ошибок, которые являются либо идентичными (например - недопустимые символы в имени некоторой переменной, отсутствие скобок в условных операторах при наличии логических операций и т.д.), либо одна из ошибок служит причиной генерации других (например - отсутствие end в составном операторе вызовет лавину ошибок в оставшейся части программы, особенно, если данная ошибка произошла не составном операторе программы, а в одном из составных операторов вышеопределенных процедур и функций).

Для ускорения отладки в первом случае можно произвести исправление всех идентичных ошибок, не дожидаясь перекомпиляции. Второй случай возникает только на компиляторах, отслеживающих все ошибки программы до аварийной остановки (Turbo C, C++, C++ Builder, Delphi). Если программист предпочитает корректировать все найденные ошибки до повторной перекомпиляции программы, то он сталкиваться с ситуацией, когда исправлять приходится несуществующие ошибки. Таким образом, не следует искать причину синтаксической ошибки, если она не видна сразу и ошибка не является первой в списке синтаксических ошибок. Устранение первой ошибки и перекомпиляция программы часто автоматически устраняет и последующие.

 

2.4.1.2              Семантические ошибки в БПП.

2.4.1.2.1          Предупреждение семантических ошибок в БПП.

Констатация и локализация семантических ошибок в БПП реализуется более сложными средствами, чем в небольших программах.

Поэтому были выработаны специальные методы, которые в значительной степени позволяют уменьшить количество семантических ошибок в программе. Суть данных методов заключаются в определенном стиле программирования, в основу которого положена независимость и надежность каждого логически завершенного участка программного кода:

1) Независимость программного кода от таких критичных параметров, как объем доступной памяти, свободное место на жестком диске, набор коммуникационных устройств, тип видеоадаптера и т.д., либо правильная обработка всех критических ситуаций (например - перед NEW проверить MEMAVAIL).

2) Самодостаточность программного кода, что подразумевает общение с вызвавшим кодом с помощью только входных и выходных параметров (заранее четко и однозначно определенных), а также независимость программного кода от места его вызова.

3) Уникальность любого набора данных и методов определения состояния программы. Например - если некоторое поле объекта отвечает за состояние объекта (видим-невидим, запрещен-разрешен, активен-неактивен) и т.д., то никакие другие поля (или методы, ссылающиеся на эти поля) не должны отвечать за то же самое состояние.

4) Минимальное использование глобальных переменных. В идеальном случае программа имеет только одну глобальную переменную - корень иерархии экземпляров объектов.

 

2.4.1.2.2          Констатация семантических ошибок в БПП.

В программах с последовательной структурой констатация ошибки не является обычно проблемой, так как имеется ограниченное количество путей прохождения от начала до конца алгоритма и практически всегда можно определить, откуда выполнение программы пришло в некоторую точку и куда затем последует. Для констатации ошибки в такой программе требуется небольшое количество контрольных примеров, на которых выполнение программы захватит все ветви алгоритма.

В БПП с событийной структурой логически завершенные участки кода могут выполнятся в произвольном порядке произвольное количество раз. Поэтому существует бесконечное количество вариантов выполнения такой программы. Метод контрольных примеров может лишь частично помочь в констатации ошибки. С его помощью можно добиться прохождения по всем элементам блок-схемы алгоритма, но невозможно перебрать все варианты порядка выполнения частей программы. (на сегодняшний день не существует методов полной констатации ошибок в БПП. Типичный пример - постоянные дополнения к Windows, Office и другим событийно-ориентировнным БПП)

 

2.4.1.2.3          Локализация семантических ошибок в БПП.

Локализация ошибок в БПП - более сложная задача, чем в небольших программах.

Причины:

1) Большой объем программного кода

Большой объем программного кода усложняет локализацию ошибки по следующим причинам - невозможность использования встроенного или внешнего отладчика из-за нехватки оперативной памяти, большое количество модулей, использование неотлаженных модулей других групп программистов проекта, активное использование не протестированных модулей сторонних фирм.

2) Разрывность программного кода из-за событийной организации БПП.

Разрывность программного кода в общем случае не позволяет четко и однозначно определить, откуда произойдет вызов метода обработки некоторого события и какой метод будет вызван следующим. Таким образом, участки программы, в которых программист не учел возможности вызова из любой другой части программы, могут в некоторых случаях работать неверно, то есть содержать потенциальные ошибки. (например - совместное использование глобальных переменных, несогласованность в использовании динамической памяти и т.д. ).

3) Влияние процесса трассировки программы на результаты выполнения того или иного участка кода.

Это особенно заметно при ошибках в реализации событийной модели или в различных обработчиках событий. Если требуется протестировать последовательность, состоящую из нескольких внешних событий, таких как события от клавиатуры или мыши, то использование трассировки внесет неизбежные искажения в саму последовательность.

4) Высокодинамичная структура данных в БПП.

Ошибки, связанные с динамической организацией иерархии экземпляров объектов чрезвычайно трудно локализовать. Отсутствие общих методов визуального представления такой иерархии не позволяет проанализировать динамическую структуру данных программы в любой момент времени.

 

2.4.1.2.4          Методы локализации семантических ошибок в БПП.

Общий подход к локализации ошибок в БПП состоит в использовании условных и безусловных точек прерывания, трассировки отдельных участков кода после срабатывания точки прерывания, просмотра и изменения значения переменных, а также в организации отладочных файлов или окон на экране, визуализирующих динамическую структуру данных программы или любой ее части. Таким образом, основной метод отладки небольших программ - трассировка, используется очень ограниченно.

Большинство из этих средств предоставляются встроенным или внешним отладчиком, однако исследование динамической структуры данных можно организовать только программно. Таким образом сам программный код будет включать в себя множество средств для отладки.

Проблема быстрого перехода от отладочной версии к рабочей во многих языках решается использованием условной компиляции.

{$DEFINE Условный_символ},{$UNDEF Условный_символ }

{$IFDEF Условный_символ},

{$IFNDEF Условный_символ },

{$ELSE},{$ENDIF}

Использование условных символов позволяет заключать отладочные участки программы в блоки, которые либо будут, либо не будут компилироваться в зависимости от того, определен или нет условный символ. Ряд условных символов автоматически определяется (или отменяется) компилятором ТП:

VER70 – версия ТП – 7.0

MSDOS – операционная система – DOS

WINDOWS - операционная система – Windows

CPU86 – микропроцессор – серии Intel xxx86

CPU87 – сопроцессор – серии Intel xxx87

DPMI –программа предназначена для защищенного режима

Директива DEFINE позволяет определить условный символ, директива UNDEF – отменить его. Директивы IFDEF и IFNDEF открывают блок условной компиляции, ENDIF – закрывает. Директива ELSE применяется аналогично соответствующему оператору ТП.

Задача 8. В некоторой программе активно используются вещественные значения различных типов. Однако отладка программы проходит на нескольких компьютерах, одни из которых оборудованы математическим сопроцессором, а другие – нет. Определить блок условной компиляции, позволяющий компилировать и тестировать программу на любом из этих компьютеров.

Решение состоит в переопределении вещественных типов на компьютерах, не оборудованных математическим сопроцессором. Наличие сопроцессора определятся с помощью условного символа CPU87:

{$IFDEF CPU87}

  {$N+}

  type  Real = Double;

{$ELSE}

  {$N-}

  type

   Single = Real;

   Double = Real;

   Extended = Real;

   Comp = Real;

{$ENDIF}

Рассмотрим пример, в котором динамическая структура иерархии объектов или любая ее ветка сохраняется в файле для последующего анализа. Вызов созданных отладочных процедур может производится по нажатию на неиспользуемые в программе клавиши.

Определим условный символ

{$Define DEBUG}

Определим в классе TEventObject несколько отладочных процедур.

{$IFDEF DEBUG}

Function GetDebugInfo:string;virtual;

Procedure Debug(var F:Text; FirstString:string);

Procedure BeginDebug(FN:string);

{$ENDIF}

Функция GetDebugInfo должна перекрываться в каждом, порожденном от TEventObject объекте и возвращать в строке специфичную для каждого объекта информацию.

 

{$IFDEF DEBUG}

Procedure TeventObject.GetDebugInfo:string;

 Begin GetDebugInfo:='TeventObject'; End;

Процедура Debug выводит в файл отладочную информацию для данного объекта и всех подчиненных;

 Procedure TeventObject.Debug(var F:Text;

FirstString:string{отступ});

  Var s:string; i:integer

 Begin

S:=firstString;

If (owner=nil) or

((owner<>nil) and (owner^.Active=@self))

then s:=s+'*';

if disabled then s:=s+'-';

Writeln(f,s+GetdebugInfo);

FirstString:=firstString+' ';

With components^ do begin

For i:=0 to Count-1 do PeventObject(at(i))^.Debug(f,FirstString);

End;

 End;

Процедура BeginDebug открывает файл, записывает в него отладочную информацию и закрывает файл.

Procedure TeventObject.BeginDebug(FN:string);

  Var F:Text;

 Begin

  Assign(f,FN);

  Rewrite(f);

  Debug(f,'')

  Close(f);

 End;

{$ENDIF}

Таким образом, после констатации ошибки, причиной которой может быть неверно сформированная структура динамических данных или для проверки работоспособности процедур, отвечающих за формирование такой структуры, может быть вызвана процедура BeginDebug для любого объекта иерархии.

В окончательной версии программы для удаления отладочного кода можно отменить условный символ DEBUG, заменив директиву Define на Undef.

 

Задания для раздела "Методы локализации семантических ошибок в БПП." (92-93)

Задание 92*. Внести в класс TEventObject процедуры и функции, предназначенные для отладки (GetDebugInfo, Debug, BeginDebug), а в каждом классе графического редактора, порожденном от TEventObject – перекрыть функцию GetDebugInfo. По нажатию на клавишу F1 в графическом редакторе создавать файл отладки, используя отладочные функции.

Задание 93. Расширить решение предыдущей задачи. Создать класс окна для визуализации текстового файла в графическом режиме. По нажатию на клавишу F2 на экране появляется окно с информацией об иерархии объектов редактора.

 

 

 


 

Список литературы.

1.   Абрамов С.А., Гнездилова Г.Г., Капустина Е.Н., Селюн М.И. Задачи по программированию. - М.: Наука, 1988. - 224 с.

2.   Архитектура среды для разработки приложений. Киев: "Крещатник", 1992 – 240 с.

3.   Вирт Н. Алгоритмы + структуры данных = программы / Пер. с англ. - М.: Мир, 1985. - 406 с., ил.

4.   Вирт Н. Систематическое программирование. Введение / Пер. с англ. - М.: Мир, 1977. - 183 с.

5.   Вьюкова Н.И., Галатенко В.А., Ходулев А.Б. Систематический подход к программированию. - М.: Наука, 1988. - 208 с.

6.   Епанешников А., Епанешников В. Программирование в среде Turbo-Pascal 7.0. - 4-е изд. испр. и дополн. - М.:"Диалог-МИФИ", 1998. - 367с.

7.   Райли Д. Абстрация и структуры данных: Вводный курс: Пер. с англ. – М.:Мир, 1993. – 752 с., ил.

8.   Слинкин Д.А. Программирование. Часть 1. Язык программирования Турбо-Паскаль: Учебное пособие для студентов вузов. Шадринск: Изд-во Шадринского пединститута, 1999. - 143 с.

9.   Справочник программиста и пользователя/Под ред. А.Г. Шевчика, Т.В. Демьянкова. - М.: "Кварта", 1993. - 128с.

10. Толковый словарь по вычислительным системам/Под ред. В.Иллингуорта и др.: Пер. с англ. А.К. Белоцкого и др.; Под ред. Е.К. Масловского. - М.: Машиностроение, 1990. - 560с.: ил.

11. Фаронов В.В. Турбо-Паскаль (в 3 книгах). Кн. 3. Практика программирования. Часть 1. – М.: Учебно-инженерный центр "МВТУ – ФЕСТО ДИДАКТИК", 1993. – 256 с., ил.

12. Федоров А. Borland Pascal: практическое использование Turbo Vision 2.0. Киев: "Диалектика", 1993. – 273 с., ил.

13. Turbo Vision для языка Pascal. Описание. М: "И.В.К.-Софт", 1992. – 224 с.

14. Turbo Vision для языка Pascal. Справочник. М: "И.В.К.-Софт", 1992. – 288 с.


 

 

 

 

 

 

Учебное пособие для студентов вузов

 

 

 

 

 

 

 

 

Слинкин Дмитрий Анатольевич

 

 

ПРОГРАММИРОВАНИЕ

ЧАСТЬ 2

МЕТОДЫ ПРОГРАММИРОВАНИЯ НА ТУРБО-ПАСКАЛЕ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Лицензия ЛР №020057 от 7 апреля 1997 г.

Формат 60´90 / 1/16, усл. печ. л. 4.4, тираж 100 экз.



[1] Звездочка означает увеличенный объем задания

[2] При создании класса примитива следует продумать иерархию – от какого класса он будет порожден, какие классы будут потомками, стоит ли создавать промежуточный (абстрактный) класс и т.д

[3] Две звездочки означают повышенную сложность и увеличенный объем задания