Лабораторная работа № 5
Аналитические  кривые на двумерной плоскости. Управление графическими объектами с использованием конечных автоматов
 
1. Цель работы
 
1. Освоить программирование аналитических (параметрических и непараметрических) кривых на двумерной плоскости.
2. Разработать программу в среде визуального проектирования C++Builder, позволяющую демонстрировать графики различных кривых, в зависимости от типа задаваемых функций и их параметров.
3. Освоить программирование меню для Windows-подобных приложений обработки графики.
4. Освоить программирование конечного автомата, реализующего интерактивное управление процессом прорисовки графиков.
 
2.  Постановка задачи
 
2.1. В проект, разработанный для лабораторных работ №1-№4 добавьте новый модуль с формой Form6.
2.2. На поле Form6 установите компонент PageControl с двумя вкладками, на каждой из которых создайте графическое окно, а также установите панель управления в соответствии с рисунком 7.
2.3. В графических окнах обеих вкладок создайте системы декартовых координат с сетками.
2.4. В графических окнах должны наблюдаться следующие события:
1) Построение заданных графиков в режиме «бегущей дорожки» после нажатия на кнопку "Пуск". На первой вкладке должна быть построена непараметрическая кривая, согласно варианту из таблицы 1, на второй вкладке – параметрическая кривая из таблицы 2.
  
2) Скорость построения графиков, амплитуда и характер кривой каждого из графиков должны задаваться средствами регулирования, расположенными на панели управления;
3) Остановка процесса построения графиков после нажатия на кнопку "Пауза". Возможность продолжения процесса построения графика после повторного нажатия на кнопку "Пуск";
4) Сохранение графиков в своих окнах после переключения вкладок. Возможность продолжения процесса построения графиков с помощью кнопки "Пуск", если они не были достроены перед переключением вкладок;
5) Очистка поля активного графического окна от графика, прорисованного в нем, после нажатия на кнопку "Очистка". При этом оси координат и сетка должны сохраняться;
6) Изменение скорости построения графика при изменении положения движка "Скорость построения";
7) Использование разделов и подразделов главного меню.
 
С графиками непараметрических кривых, приведенных в таблице 1 можно более подробно ознакомиться в книге И.Н. Бронштейна "Справочник по математике для инженеров и учащихся ВТУЗов", стр. 126, раздел 1.2. "Графики элементарных функций".

 
Для построения графиков из таблицы 2 используйте параметрические уравнения вида x=f(t) и y=f(t). Если уравнения заданы в полярной системе координат, их необходимо преобразовать в параметрические. Для параметров t  и φ необходимо определить более узкие и более конкретные пределы, чем те, что указаны в таблице. Это связано с тем, что количество точек, описывающих заданные кривые – ограничено как размерами графического окна, так и размером матрицы, где будут храниться координаты этих точек.
С графиками параметрических кривых, приведенных в таблице 2 можно более подробно ознакомиться в том же самом справочнике И.Н. Бронштейна, стр. 137, раздел 1.3. "Важнейшие кривые".
 
3.  Краткие теоретические сведения
 
3.1. Представление  кривых
 
Кривая может быть представлена либо в виде алгебраического выражения, т.е. аналитически, либо как совокупность точек (двумерный массив, таблица).
Аналитическое представление имеет ряд преимуществ: - это точность, компактность записи и простота вычисления промежуточных точек. Аналитическое представление позволяет определить наклон и радиус кривизны, а при точечном представлении для этого требуется численное дифференцирование. При аналитическом представлении кривой можно точно определить положение любой точки, а при точечном нужна интерполяция, причем в общем случае результат интерполяции не принадлежит кривой.
Аналитическое представление выгоднее, когда для удовлетворения заданному критерию необходимо непрерывно изменять форму кривой.
Математическая кривая может быть представлена в параметрической или непараметрической форме.
 
3.2.  Непараметрические  кривые
 
Непараметрическая кривая задается в виде явной или неявной функции.
      Для плоской кривой явное непараметрическое представление имеет вид:          y = f(x)
 
Пример уравнение прямой в непараметрическом виде:                                          y = mх + b
 
При этом одному значению х соответствует только одно значение y, поэтому замкнутые или многозначные кривые, например окружность, явно представить нельзя.
Неявное представление в виде  f(x,y) = 0 позволяет обойти это ограничение.
 
3.3.  Параметрические  кривые в  декартовых  координатах
 
В параметрическом виде каждая координата точки кривой представлена как функция одного параметра. Значение параметра задает координатный вектор точки на кривой. Для двумерной кривой с параметром t координаты точки равны: 
                                                                                                                                           x = x(t)
                                                             y = y(t) 

      Тогда векторное представление точки на кривой будет иметь вид:
        P(t) = [x(t) y(t)]
 
Чтобы получить непараметрическую форму, нужно исключить  t  из двух уравнений и вывести одно в терминах  x  и y.
Параметрическая форма позволяет представить замкнутые и многозначные кривые.
Так как точка на параметрической кривой определяется только значением параметра, эта форма не зависит от выбора системы координат. Конечные точки и длина кривой определяются диапазоном изменения параметра. Часто бывает удобно нормализовать параметр на интересующем отрезке кривой к виду: 0 <= t <= 1. Осенезависимость параметрической кривой позволяет с легкостью проводить с ней аффинные преобразования.
На рисунке 1 сравниваются непараметрическое и параметрическое представление окружности в первом квадранте. Непараметрический вид

                                          
 
показан на рисунке 1а. Точки на дуге соответствуют равным приращениям х. При этом дуга состоит из отрезков разной длины, и получается весьма приблизительное графическое представление окружности. Кроме того, расчет квадратного корня - вычислительно дорогостоящая операция.
 
 
 
Рисунок 1 – Представление окружности для первого квадранта
 
Стандартная параметрическая форма единичной окружности:          x = cosθ                       0 <= θ <= 2π
y = sinθ
 
где параметр θ - геометрический угол, отмеряемый против часовой стрелки от положительной полуоси х. На рисунке 1b изображена дуга, построенная по равным приращениям параметра в пределах 0 <= θ <= π/2. При этом точки располагаются на одинаковом расстоянии вдоль окружности, и окружность выглядит гораздо лучше. Недостаток такого представления — сложность вычисления тригонометрических функций.
Параметрическое представление кривой не единственно, например,
 
также представляет дугу единичной окружности в первом квадранте. На рисунке 1с показан результат для равных приращений  t. Он лучше, чем у явного, но хуже, чем у стандартного параметрического представления. Однако последнее уравнение проще с вычислительной точки зрения, т.е. это компромиссное решение.
 
3.4.  Параметрические  кривые в полярных координатах
 
Многие кривые на плоскости удобно описывать как функции радиуса-вектора и полярного угла – в полярных координатах.
Уравнение кривой в полярных координатах в общем виде:          r = r(φ).
 
Уравнение единичной окружности в полярных координатах:      r = 1.
 
Декартовы координаты точки (x,y) на плоскости связаны с полярными координатами (r,φ)  соотношениями:
                                x = r cos(φ)
                                y = r sin(φ)
 
Для примера, построим график архимедовой спирали, заданной в полярных координатах:

 
 
Рисунок 2 – График архимедовой спирали
 
3.5.  Построение кривых по контрольным точкам
 
Как аналитические, так и табличные графики строятся по точкам, которые затем соединяются друг с другом определенным образом.
Существует два основных способа соединения этих точек между собой: в режиме интерполяции и в режиме аппроксимации.
Интерполяция – это построение кривой, проходящей через контрольные точки.
Аппроксимация – это приближение кривой к заданным точкам с некоторой точностью отстояния от них.
Графическая интерполяция реализуется в виде линейной, кубической, или сплайновой техники.
При аппроксимации используют различные функции: линейные, нелинейные, метод наименьших квадратов, полиномы и т.д.
 
В данной лабораторной работе при построении графиков будет использован метод линейной интерполяции, при котором узловые точки, описывающие заданную функцию, соединяются прямолинейными отрезками между собой.


4. Интерфейс программы
 
Начало работы программы лабораторной работы №5 аналогично предыдущим лабораторным работам. После нажатия на кнопку "Lab5", расположенной на главной форме Form1, должна запускаться форма Form6, с установленной на ней многостраничной панелью PageControl1 с двумя вкладками TabSheet1 и TabSheet2 , на которых в свою очередь располагаются графические окна  PaintBox1 и PaintBox2. На этой же форме устанавливается панель управления Panel1 (см. рис. 3).
На панели управления должны быть установлены следующие стандартные компоненты:
- окошки ввода SpinEdit1SpinEdit6 предназначены для ввода параметров графиков;
- движок TrackBar1 предназначен для изменения скорости построения графиков;
- кнопки   Button1 ("Пуск"), Button2 ("Пауза") и Button3 ("Очистить"), предназначены для запуска, остановки процесса построения графиков и очистки графических окон;
- таймер Timer1 - предназначен для обеспечения построения графиков в режиме "бегущей дорожки".
Кроме этого на форме должно быть создано меню с разделами "Файл", График" и "Сервис".

 
 
Рисунок 3 – Вид окна формы Form6 после нажатия на кнопку "Пуск"
 
На рисунке 3 приведен пример формы Form6 . На поле графического окна, расположенного на вкладкеTabSheet1 построен график непараметрической функции y1 = a1 sin(x1).
При запуске программы в графических окнах обеих вкладок должны прорисовываться только оси координат, сетка и уравнение графика (при условии, если в главном меню в разделе "Сервис" включены флажки "показывать оси" и "показывать уравнение"). Затем, после нажатия на кнопку "Пуск", должен запускаться обработчик таймера, в состав которого введен вызов функций прорисовки графика в режиме поэлементного добавления небольших отрезков графика к уже простроенной части. После выполнения заданного количества таймерных отсчетов и соответственно завершения построения графика, таймер останавливается. Кнопка "Пауза" также останавливает таймер, независимо от того, завершилось ли полное построение графика или нет. При повторном нажатии на кнопку "Пуск" построение графика должно продолжиться с того места, где он был остановлен. Кнопка "Очистить" должна вызывать перерисовку графического окна активной вкладки и заново перерисовывать оси координат, сетку и уравнение, но уже без графиков.
К наиболее значимым отличиям данной лабораторной работы от предыдущей можно отнести то, что запуском и остановкой таймера управляет конечный автомат. Кроме этого, для обеспечения дополнительных функций управления программой и для установки пользовательских настроек создано меню.
Все остальные функции, такие как построение осей координат, построение сетки, обработчики событий для стандартных компонентов абсолютно идентичны предыдущим лабораторным работам.
 
5. Структура программы
 
На рисунке 4 изображена структурная схема программы.
Так же как и в предыдущих лабораторных работах, эта структура состоит из трех основных групп функций:
- функций инициализации исходных данных и графического окна;
- функций конвейера обработки изображения;
- функций обработки событий.
 
 Как видно из структурной схемы, для каждого графика создан свой графический конвейер, в состав которых включена функция, реализующая конечный автомат для управления прорисовкой графиков.
  
Рисунок 4 – Структурная схема программы
 
 
5.1. Разработка конечного автомата
5.1.1. Предварительные пояснения
 
Прежде чем перейти к рассмотрению принципов построения автоматной программы, напомним основные определения теории конечных автоматов:
Конечный автомат (КА)– Конечный автомат (Finite-state machine) – это абстрактная модель, описывающая поведение объекта с конечным количеством состояний, пути его перехода из одного состояния в другое, условия этого перехода, а также действия, выполняемые во время перехода или после.
Локальное событие – положительный результат вычисления некоторого логического выражения.
Состояние – зацикливание фрагмента программы, до наступления локального события.
Переход – переход из одного состояния в другое в результате осуществления локального события. При этом выполняется не только изменение текущего состояния, но и последовательность операторов, которая заставляет функционировать КА в новом состоянии.
 
Конечный автомат, который мы намереваемся здесь создать для обеспечения управления процессом прорисовки графиков, будет локальным автоматом, в отличие от главного автомата самого проекта, созданного без нашего участия. В нашу задачу будет входить не только создание этого локального автомата, но и обеспечение его правильного функционирования в составе главного автомата проекта.
 
Последовательность создания автомата следующая:
- составляем граф и таблицу переходов КА;
- строим блок-схему автомата в три этапа;
- определяем способы объединения разработанного локального КА с главным автоматом проекта;
- преобразуем блок-схему в программный код, описывающий функцию КА.
 
5.1.2. Построение графа переходов КА
 
На рисунке 5 изображен граф переходов автомата, управляющего прорисовкой графиков.
Перечислим все состояния, в которых может пребывать этот автомат:
- состояние А – исходное, график еще не нарисован, или после нажатия на кнопку "Очистка";
- состояние В – прорисовка графика в режиме "бегущей дорожки" после нажатия на кнопку "Пуск";
- состояние С – приостановка процесса прорисовки графика после нажатия на кнопку "Пауза";
- состояние D – завершение прорисовки после достижения конца графика.
Переходы из одного состояния в другое совершаются после следующих локальных событий:
- переход из состояния А в состояние В происходит по нажатию на кнопку "Пуск".
- переход из состояния В в состояние С происходит по нажатию на кнопку "Пауза".
- переход из состояния В в состояние D происходит при достижении 200 таймерных отсчетов;
- переходы из состояний В, С и D в состояние В происходит по нажатию на кнопку "Пуск";
- переходы из состояний В, С и D в состояние А происходит по нажатию на кнопку "Очистка".



Рисунок 5 – Граф переходов автомата
 
 
5.1.3. Построение блок-схемы КА
 
Алгоритм построения любой автоматной программы выполняется по единому универсальному принципу, в основе которого положена конструкция while-switch-case. Построение блок-схемы КА проведем в 3 этапа (шага).
 
ШАГ 1. Составляем базовую блок-схему автомата.
В начальной части программы устанавливаем исходное состояние автомата для переменной state=‘A и рабочее значение параметра цикла cycle=1.
      Затем, формируем структуру типа  while-switch-case. Количество ветвей   case   в ней равно числу состояний составленного графа переходов  (см. рис. 6).
 
 
 
Рисунок 6 – Базовая блок-схема КА
 
 
ШАГ 2. Выполняем первое уточнение блок-схемы.
Уточнение базовой блок-схемы заключается в частичном раскрытии содержимого блоков "Действия на переходе". На рисунке 7 изображена часть блок-схемы автомата с развернутым содержанием ветвей case"A"case"В", case"С" и case"D".
Как видим, каждая ветвь case является древовидной структурой, использующей связки if – else.
Количество вложений if–else в каждой ветви равно количеству переходов (т.е. выходных стрелок на графе) из конкретного состояния. В самом максимальном случае количество этих вложений может соответствовать числу управляющих элементов, использованных в автомате.
Из блок-схемы видно, что программа, находящаяся в одном их четырех состояний (A, B, C, D), совершает непрерывные циклические обходы while вдоль соответствующей ветви case по огибающей, без захода внутрь какой-либо из её вертикальных ветвей if–else, в режиме холостого ожидания.
Изменение направления с заходом в конкретную вертикальную ветвь начинается с того момента, когда пользователь производит управляющее воздействие на одну из кнопок. При этом происходит изменение соответствующего логического выражения:XAB, XВC, XВD и т.д. Такое изменение называется локальным событием, которое должно вести к изменению состояния автомата. Новое состояние необходимо зафиксировать с помощью команды типа state=‘B’. После этого в блоке операторов "Модификация" выполняются команды, обеспечивающие изменение изображения в соответствии с новой командой, иными словами переход из одного состояния в другое.
Примечание: назначение команды cycle=0, присутствующей в каждой из ветвей, разъясним далее.

 
 
Рисунок 7 –Часть блок-схемы КА после 1-го уточнения
 
ШАГ 3. Выполняем второе уточнение блок-схемы.
Второе уточнение структурной схемы заключается в раскрытии содержимого блоков "Модификация".
Прежде чем определять содержимое блоков модификации, нам необходимо установить критерии, по которым автомат будет решать, пришло ли время менять свое состояние. Для этого введем три переменных, фиксирующих манипуляции с кнопками "Пуск", "Пауза" и "Очистка". Присвоим им начальные значения:
pysk=false;
pau=false;
oth=false;
Значения этих переменных, а также переменной taimTek (счетчика таймерных проходов) будут влиять на логические выражения операторов if. Переход их в состояние true, (или taimTek=200), будет означать наступление локального события, после чего произойдет смена состояния автомата.
Чтобы локальное событие можно было зафиксировать, поместим в обработчики кнопок "Пуск", "Пауза" и "Очистка" соответствующие команды. Например, в кнопке "Пуск" запишем: pysk=true, аналогичные команды типа  pau=true  и  oth=true  поместим в остальные кнопки.
     Команды "выключения" (т.е. присвоения значения false) переменных pysk и oth поместим в блоки "Модификация", куда программа будет попадать после "проваливания" в соответствующие вертикальные ветви if. Это нужно для того, чтобы эти переменные сразу же были готовы к включению при следующем нажатии на кнопки "Пуск" и "Очистка".
Что касается переменных pau и taimTek, то их мы будем выключать не сразу, а только после наступления такого локального события как "Пауза".
Это связано с тем, что эти переменные описывают "протяженные" состояния, и их включение должно быть жестко зафиксировано вплоть до конкретного завершения процесса, наступающего только после срабатывания другой кнопки.
Теперь рассмотрим назначение переменной cycle.
Если бы разрабатываемый локальный автомат был главным, переменная cycle нужна была бы только для завершения работы программы.
Но так как при работе локального автомата постоянно приходится учитывать состояния кнопок управления, являющихся одновременно элементами главного автомата (т.е. самого проекта), то для определения того, произошло ли нажатие на кнопку необходимо на каждом проходе цикла while выходить из функции avtomat() в тело основной программы.
Для этого и нужна команда cycle=0  в каждой горизонтальной ветви. Новый заход внутрь автомата возможен только после присвоения ей значение cycle=1. Такую команду необходимо поместить в обработчики всех трех кнопок (см. п 7.16).
После разработки условий переходов перейдем к заполнению блоков "Модификация", для чего составим таблицу №3 с перечнем команд на каждом переходе.
  
 
Таблица 3. Команды блоков "Модификация"
Состояние
Переход
Условие перехода
(локальное событие)
Команда
перехода
Модификация на переходе
Восстановление
условий
перехода
Команды для таймера
Пуск
Стоп
А
Исходное
AB
pysk=true
state=‘B’
pysk=false
 
 
В
Прорисовка
BB
pysk=true
state=‘B’
pysk=false
taimTek=0
StartTmr()
 
BC
pau=true
state=‘C’
 
StopTmr()
BD
taimTek=200
state=‘D’
 
 
BA
oth=true
state=‘A’
oth=false
taimTek=0
StopTmr()
С
Пауза
CB
pysk=true
state=‘B’
pysk=false
pau=false
 
 
CA
oth=true
state=‘A’
oth=false
pau=false
taimTek=0
 
D
Завершение
DB
pysk=true
state=‘B’
pysk=false
taimTek=0
 
 
DA
oth=true
state=‘A’
oth=false
taimTek=0
 
 
Как видно из описаний блоков "Модификация", здесь, кроме команд pysk=false, pau=false, и oth=false, добавились еще три команды: StartTmr(), StopTmr() и  taimTek=0. Команды запуска и остановки таймера StartTmr() и StopTmr(), в общем-то, являются самыми главными командами, ради управления которыми и создан конечный автомат.
Как только автомат перейдет в состояние "В" по нажатию на кнопку "Пуск", программа зайдет в ветвь case"B", пройдет мимо всех вертикальных ветвей if–else по огибающей и зайдет в ветвь "Модификация В", откуда и будет запущена функция StartTmr(), т.е. будет дан старт процессу прорисовки графика.
Назначение же вертикальных ветвей if–else заключается в том, чтобы можно было выйти из состояния "В", т.е. из процесса прорисовки: либо после того как таймер выполнит все 200 проходов, либо по команде "Пауза", либо по команде "Очистка". По команде "Пуск" также можно остановить процесс прорисовки и начать его заново, не переходя в другое состояние и не останавливая таймер.
Команду StopTmr() надо вызывать в тех случаях, когда автомат должен перевести график в неподвижное состояние. Если при этом процесс прорисовки необходимо полностью завершить (при переходе в состояние "Исходное"), то потребуется еще и обнулить счетчик таймерных проходов в виде: taimTek=0.
Как уже говорилось ранее, работа программ, созданных в среде визуального программирования, характеризуется тем, что главным состоянием такой программы является циклический процесс ожидания в режиме КА какого-либо управляющего воздействия, производимого пользователем. Любой конечный автомат, созданный самим пользователем и если в нем используются компоненты главного КА, должен после каждого прохода по циклу while возвращаться в главную программу для ожидания там локального события. При нажатии пользователя на одну из кнопок управления должна запускаться команда Repaint(), входящая в состав обработчиков этих кнопок, для обеспечения перерисовки графического окна. При этом запускается графический конвейер, в состав которого входит функция  Avtomat(). Происходит очередной запуск КА. За это время в его теле выполняются все необходимые переходы из одного состояния в другое, запуск или остановка таймера, после чего он снова должен выйти в основную программу. Таким образом, его работа завершается до следующего локального события. Причем его новое состояние запоминается в переменных state,  pyskpauoth и taimTek.
Таймер, используемый в нашей программе, также обеспечивает выход функции Avtomat() в тело главной программы после каждого таймерного прохода. Поэтому, во время исполнения процесса прорисовки графика, т.е. в период работы таймера, автомат имеет возможность отрабатывать такие внешние события, как нажатия на кнопки "Пуск", "Пауза" и "Очистка".
Далее, при разработке программного кода файла реализации, в п. 7.15 будет создана функция Avtomat(), с текстом программы, соответствующим схеме на рисунке 7 и таблице 3.


6. СОЗДАНИЕ ДОПОЛНИТЕЛЬНОГО МОДУЛЯ ПРОЕКТА
 
6.1. Подключение дополнительного модуля Form6 к проекту
 
Откройте свой проект с созданными в нем модулями Form1, Form2Form3Form4 и Form5, создайте новый модуль с формой Form6 и подключите его к проекту аналогично тому, как это было сделано в лабораторной работе №1.
 
6.2. Компоновка формы Form6
 
1)  Установка многостраничной панели PageControl1
На вкладке Win32 палитры компонентов щелкните по значку PageControl, и перенесите его на поле Form6, как показано на рисунке 7. Для формирования новых страниц (вкладок) щелкните на поле этого компонента правой кнопкой мыши. Откроется контекстное меню, из которого выберите команду New Page. На поле компонента появится первая вкладка TabSheet1. Для создания второй вкладки проделайте то же самое еще раз. Появится вкладка TabSheet2. В окне Object Inspector, в строке Caption  введите название для каждой из вкладок "Непараметрические" и "Параметрические", как показано на рисунке. Не забудьте, что перед этим данную вкладку необходимо выделить.
 
2)  Установка компонентов  PaintBox1  и PaintBox2 на  вкладках TabSheet1 и TabSheet1:
Выделите вкладку TabSheet1 компонента PageControl на форме Form6 и установите на ней компонент PaintBox1 (который находится на вкладке System палитры компонентов). Появившийся объект растяните и разместите на поле вкладки так, чтобы его ширина Width и высота Height были одинаковы.
Аналогичным образом выделите вкладку TabSheet2 и установите на ней компонент PaintBox2.
 
3)  Установите панель Panel1 на форму  Form6, как показано на рисунке 7.
 
4)  Установите компоненты CSpinEdit1,  CSpinEdit2 и т. д., по необходимости, которые находятся на вкладке Samples панели компонентов. В окне Object Inspector для каждого из компонентов установите необходимые настройки для свойств MaxValue, MinValue  и Value.
 
5) Установите движок TrackBar1 "Скорость движения", и настройте его следующим образом:
Max = 10;
Min = 1;
Position = 1;
TickStyle = tsManual;
 
6)  Установите Timer.
На вкладке System палитры компонентов выберите компонент Timer  и поместите его на панели Panel1. Этот элемент виден только в проектировщике формы. Когда приложение запущено, этот элемент скрыт. Установите в настройках таймера параметр   interval = 100.
 
7) Установите кнопки Button1 ("Пуск"), Button2 ("Пауза")  и Button3 ("Очистить").
 
На этом компоновка формы Form6 заканчивается, сохраните проект и запустите его для проверки на отсутствие ошибок.
 
7. РАЗРАБОТКА ФАЙЛА РЕАЛИЗАЦИИ Unit6.cpp
 
7.1. Объявление переменных и функций
 
Из рисунка 4, с изображенной на нем структурной схемой программы, можно определить весь перечень функций, которые предлагается использовать в примере к данной программе. Описания прототипов функций разработчика, присутствующих на этой схеме, объявите глобально:

 void GrafKonv1(void)           - функция запуска непарам. графического конвейера;
  
void GrafKonv2(void)           - функция запуска парам. графического конвейера;
void InitGraf1(void)           - функция вычисления координат непарам. графика;
void InitGraf2(void)           - функция вычисления координат параметр. графика;
void ShowStat1(void)           - функция прорисовки статич. элем. в 1-м окне;
void ShowStat2(void)           - функция прорисовки статич. элем. во 2-м окне;
static void ShowOsi(void)      - функция прорисовки осей координат;
static void ShowStk(void)      - функция прорисовки сетки;
void ShowUrawn1(void)          - функция прорисовки уравнения непарам. графика;
void ShowUrawn2(void)          - функция прорисовки уравнения параметр. графика;
void ShowGraf1(int taim)       - функция прорисовки непарам. графика;
void ShowGraf2(int taim)       - функция прорисовки параметр. графика;
void StartTmr(void)            - функция запуска таймера;
void StopTmr(void)             - функция остановки таймера;
void Avtomat(int *cycle, char *state, int *taimTek, bool *pysk, bool *pau, bool *oth)–
                               - функция, реализующая конечный автомат;
В составе вышеперечисленных функций будут использоваться как локальные переменные, так и глобальные. Уже известные нам по предыдущим лаб. работам ширину и высоту графического окна, цену деления отсчетов осей и количество делений осей надо объявить глобально в виде: static int W, H, W1, H1, W2, H2, h, nOs. Остальные глобальные переменные будут перечислены далее, в процессе описания создания функций программы.
Для прорисовки графиков нам потребуется также объявить глобально две матрицы, размером 2х200, для хранения в них координат (x и  y) этих графиков, по двести точек в каждой. Координаты будем хранить как экранные, поэтому тип элементов этих матриц будет  int:
 
int matrGraf1[2][200];
int matrGraf2[2][200];
 
7.2.  Разработка функции исходной инициализации FormCreate
 
Создайте обработчик события FormCreate для формы  Form6 и впишите в него команды исходной инициализации переменных, используемых далее в функциях программы:
 
nOs=10;                                            // количество отсчетов на полуосях системы координат
col1=clRed;      col2=clRed;           //  параметры исходного цвета графиков
 
Переменные col1 и col2 объявите глобально в виде: TColor col1, col2;.
Далее, в ходе разработки функций программы, сюда для инициализации будут добавлены еще несколько переменных.
 
7.3.  Обработчики событий для  PaintBox1  и PaintBox2
 
На вкладке TabSheet1 выделите компонент PaintBox1 и создайте для него обработчик события в файле Unit6.cpp аналогично тому, как это делалось в первой лабораторной работе, при создании графического окна. В тексте программного кода обработчиков будут небольшие изменения:
 
void __fastcall TForm6::PaintBox1Paint(TObject *Sender)
{
 W1=PaintBox1->Width;
 H1=PaintBox1->Height;
 
 FPC1->Brush->Style=bsSolid;
 FPC1->Brush->Color=clWhite;
 FPC1->Rectangle(Rect(0,0,W1,H1));
 
 GrafKonv1();
}
 
Для компонента PaintBox2 обработчик создается аналогично, только вместо W1, H1 и FPC1 поставьте W2   H2 и FPC2, а вместо GrafKonv1() – GrafKonv2().  
Здесь - FPC1 и FPC1  это имена макросов. Их надо прописать в начальной части программы в виде:
 
#define FPC1 Form6->PaintBox1->Canvas
#define FPC2 Form6->PaintBox2->Canvas
 
Это позволит заменить длинные, часто повторяющиеся в программе цепочки типа Form6->PaintBox1->Canvas на сокращенные буквосочетания типа FPC1 (или FPC2  для PaintBox2).
Закомментируйте временно функции GrafKonv() и запустите проект на исполнение, чтобы убедиться в том, что на каждой из вкладок появились белые прямоугольники графических окон.
 
7.4. Разработка функций графических конвейеров
GrafKonv1() и GrafKonv2()
 
Содержание функций GrafKonv1  и GrafKonv2  в какой-то мере похоже на аналогичные функции в предыдущих лабораторных работах. Отличия заключаются в том, что в этой программе отсутствуют матричные видовые преобразования координат графиков, так как в этом нет необходимости. Координаты точек графиков здесь вычисляются сразу, для всех точек, по формулам, в соответствии с параметрами, вводимыми в окошки CSpinEdit, после чего производится прорисовка графиков. Поэтому, нет необходимости и в создании функции нормировки координат. Но, в связи с тем, что прорисовка графиков должна производиться в режиме бегущей дорожки, во время построения которых разрешено пользоваться кнопками "Пауза", "Пуск" и "Очистка" (т.е. вмешиваться в работу функции прорисовки до её завершения), здесь будет разработана функция, реализующая конечный автомат, для обеспечения надежной и корректной работы этих кнопок.
 С этой целью графические объекты разделим на две части. К первой части отнесем оси, сетку и уравнение и поместим их в функцию ShowStat. Это статические объекты, не требующие дополнительной обработки. Ко второй части отнесем сам график, и будем прорисовывать его с помощью команд конечного автомата. Тогда функция графического конвейера для первой вкладки будет иметь следующий вид:
 
void GrafKonv1(void)
{
InitGraf1();
   ShowStat1();
Avtomat(&cycle1,&state1,&taimTek1,&pysk1,&pau1,&oth1);
}
Здесь функция InitGraf1() выполняет подготовительный расчет координат всех точек графика, функция ShowStat1() прорисовывает фоновые неподвижные (статические) объекты, т.е. оси, сетку и текст уравнения, а функция Avtomat() чертит сам график в режиме бегущей дорожки.
Текст функции  GrafKonv2() составляется аналогично.
Подготовьте функции к отладке, т.е. заглушите вызовы всех, еще не разработанных функций, входящих в состав GrafKonv() и раскомментируйте вызовы GrafKonv()  в обработчиках FormPaint. После этого можно запустить проект на исполнение для проверки.
 
7.5. Разработка функций расчета координат графиков InitGraf1()  и InitGraf2()
 
В этих функциях надо выполнить следующие действия:
- прочитать значения коэффициентов уравнений графиков из окон ввода CSpinEdit;
- задать начальное значение переменной x1 (или t, для параметрического графика);
- рассчитать в цикле координату y1  для каждого значения переменной x1  графика;
- перевести полученные координаты в экранные;
- запомнить вычисленные координаты точек в матрице координат;
- увеличить переменную x1 на величину шага её изменения.
Пример программного кода функции расчета координат для графика построения синусоиды имеет вид:

void InitGraf1(void)
{
 a1=StrToFloat(Form6->CSpinEdit1->Text);
 x1=-10;
 
 for(int i=0; i<200; i++)
 { y1=a1*sin(x1);
    matrGraf1[0][i]=W1/2+int(h*x1);
    matrGraf1[1][i]=H1/2-int(h*y1);
    x1=-10+i*0.1;
 }
}
 
Функция расчета координат для 2-го графика InitGraf2() выполняется аналогично, с учетом заданных формул из таблицы 2. Объявите глобально переменные a1, x1, y1 (а также и другие, если они есть) и запустите проект на исполнение, для проверки на отсутствие ошибок в программном коде. Не забудьте убрать заглушки с вызовов этих функций.
 
ПРОРИСОВКА СТАТИЧЕСКИХ ОБЪЕКТОВ
 
7.6. Разработка функций ShowStat1() и ShowStat2()
 
В задачу функций ShowStat() данной лабораторной работы входит последовательный запуск функций, ответственных за прорисовку следующих графических объектов:
- осей координат;
- сетки;
- текста уравнения функции.
Перечисленные объекты – это неподвижные статические элементы графического окна, которые необходимо каждый раз перерисовывать заново при переключении вкладок, при срабатывании кнопок или изменениях в подразделах меню.
Кроме этих объектов сюда необходимо отнести и графики или части графиков, уже прорисованные в графических окнах на момент переключения вкладок или нажатия на кнопку "Пауза".
Сейчас, в составе этой функции выполним простую, без использования кнопок и эффектов "бегущей дорожки" прорисовку графиков. Это требуется только ради удобства отладки программы в качестве промежуточного этапа, для перехода к построению графиков в динамическом режиме, с помощью таймера и конечного автомата. С учетом этого текст функции ShowStat1() будет иметь вид:
 
void ShowStat1(void)
{
   ShowOsi();                        // Прорисовка осей координат
   ShowStk();                        // Прорисовка сетки
   ShowUrawn1();                     // Прорисовка уравнения
 
   FPC1->MoveTo(matrGraf1[0][0], matrGraf1[1][0]);
   for(int i=1;i<200;i++)
       ShowGraf1(i);                 // Статическая прорисовка графика
}
Текст функции  ShowStat2() составляется аналогично, с соответствующими изменениями.
В дальнейшем, мы внесем поправки в эти функции, связанные с разработкой конечного автомата и работой меню. Подготовьте функцию к отладке и запустите программу на исполнение для проверки.
 
7.7. Разработка функций прорисовки осей координат и сетки
ShowOsi() и ShowStk()
 
В файл Unit6.cpp добавьте заголовки двух функций: void ShowOsi(void) и void ShowStk(void).
Текст программного кода этих функций почти полностью совпадает с такими же функциями в л.р. №2, за исключением небольшого отличия, связанного с тем, что у нас установлены две вкладки, с отдельными системами координат. Поэтому в каждой из этих функций, в начальной их части, надо предварительно выполнить проверку, какая из вкладок является активной (открытой) в данный момент и заменить переменные W1, H1, W2, H2 на унифицированные, W и H. Это позволит нам использовать одни и те же функции прорисовки осей ShowOsi и сетки ShowStk для разных вкладок. Для функции ShowOsi() новое добавление имеет вид:
 
 if(Form6->PageControl1->TabIndex==0)
        { FPC=FPC1;
          W=W1;
          H=H1;
        }
 else { FPC=FPC2;
          W=W2;
          H=H2;
        }
 
Далее, в функции ShowOsi()  надо прописать уже известные вам команды прорисовки осей.
Аналогичное добавление выполняем и в теле функции прорисовки сетки ShowStk().
Свойство TabIndex   определяет номер активной вкладки: у первой вкладки оно равно нулю, у второй – единице, и т.д.
Переменную FPC объявите глобально в виде: TCanvas *FPC, она имеет тот же тип, что и макросы FPC1 и FPC2, служащие для замены цепочек вида  Form6->PaintBox1->Canvas и  Form6->PaintBox2->Canvas.
Снимите установленные заглушки, сохраните проект и запустите на исполнение. В окне программы можно будет увидеть графические окна с системами координат и сетками на каждой из вкладок.
 
7.8. Разработка функций прорисовки уравнений графиков
ShowUrawn1() и ShowUrawn2()
 
Назначение этих функций – в том, чтобы выводить на поле соответствующего графического окна, в его нижнюю часть, текст уравнения графика из таблицы 1 или 2, как показано на рисунке 7.
Текст функции  ShowUrawn1() может иметь, например, такой вид:
 
void ShowUrawn1(void)
{
 AnsiString text;
 text = "y1 = a1 sin(x1)";
 FPC1->TextOutA(25, H1-20, text);
}
Здесь, сначала устанавливается тип и кодировка шрифта для текстовой переменной text, затем этой переменной присваивается конкретный текст, а именно, текст заданного уравнения и затем выполняется непосредственная прорисовка текста с помощью команды TextOutA.
Текст функции  ShowUrawn2() составляется аналогично.
Сохраните проект и запустите программу на исполнение. На поле графических окон, в их нижней части, должны прописываться тексты уравнений ваших графиков.
 
7.9. Разработка функций прорисовки графиков ShowGraf1() и ShowGraf2()
 
Здесь необходимо сначала установить свойства пера для задания ширины и цвета линий графиков. Затем выполняется прорисовка 1/200-й части графика, начиная с того места, где это перо было установлено  в функции ShowStat(), после чего свойства пера возвращаются в исходное состояние:
 
void showGraf1(int taim)
{
 FPC1->Pen->Width=4;                    // Устанавливаем ширину пера
 FPC1->Pen->Color=clRed;
 
 FPC1->LineTo(matrGraf1[0][taim], matrGraf1[1][taim]);
 
 FPC1->Pen->Width=1;                    // Восстанавливаем ширину пера
 FPC1->Pen->Color=clBlack;
}
Функция прорисовки 2-го графика showGraf2 выполняется аналогично.
Не забывайте, что макрос FPC1 используется для прорисовок в первом графическом окне, а FPC2 – во втором графическом окне.
Сохраните проект и запустите программу на исполнение. На этом этапе графики будут прорисовываться сразу, без всяких управляющих воздействий на кнопку "Пуск".
 
На этом, создание программы в статическом режиме завершается. Главное на этом этапе – обеспечить работу функций прорисовки, достаточную для выполнения элементарного построения графиков. Это даст вам возможность поработать над правильной записью формул расчета координат графиков и выбора пределов для переменных. Вы сами убедитесь, что это не так просто, как кажется.
Здесь пока не работает ни одна из кнопок. Сейчас в этом нет необходимости. Далее, при программировании динамического режима будет показано, что надо делать для обеспечения работы всех пунктов задания.
 
ДИНАМИЧЕСКАЯ ПРОРИСОВКА ГРАФИКОВ
 
7.10 Предварительные пояснения
 
Как было сказано в разделе "Постановка задачи" к данной лабораторной работе, при построении графиков необходимо соблюсти следующие условия:
1) Запуск прорисовки производится по нажатию на кнопку "Пуск";
2) Прорисовку производить в режиме "бегущей дорожки";
3) Прорисовку можно приостановить кнопкой "Пауза";
4) Дорисовку приостановленного графика можно продолжить повторным нажатием на кнопку "Пуск";
5) Кнопкой "Очистка" можно удалить график вне зависимости от того, в каком состоянии он находится: прорисовки, паузы или завершения;
6) Изображения графиков или их частей должны восстанавливаться после переключения вкладок или переключений в меню.
 
Чтобы обеспечить надежную и четкую обработку всех этих условий разработаем конечный автомат для управления прорисовкой графиков. Так как процессы управления прорисовкой абсолютно идентичны для обоих графиков, создадим общую функцию реализации конечного автомата, пригодную для обоих графиков. Различия будут отображаться только в содержимом фактических параметров при вызове этой функции.
 
Прежде чем переходить к составлению автомата, рассмотрим еще одну важную проблему, а именно: как обеспечить прорисовку графиков в режиме "бегущей дорожки".
Под бегущей дорожкой в нашем случае понимается такой процесс прорисовки (а точнее дорисовки), при котором график строится путем последовательного присоединения к его исходной или уже прорисованной части маленьких прямолинейных отрезков, из которых состоит график. При этом создается анимационный эффект, в котором график как бы бежит из своей начальной части в конечную, до полного своего построения.
В данной лабораторной работе мы условились прорисовку графиков выполнить с использованием таймера. В нашем случае, для выполнения анимации таймер более предпочтителен по сравнению с циклом For, например, так как таймер позволяет выполнять обработку событий от нажатия кнопок внутри своего обработчика, в то время как любая другая функция этого не позволит до полного завершения своей работы, без применения специальных методов C++Builder.
Таймер запускается командой Timer1->Enabled=true, после которой в самом обработчике таймера должна запускаться функция прорисовки 1/200 части графика. После достижения 200 таймерных проходов, таймер необходимо остановить командой Timer1->Enabled=false.
Здесь приведены самые общие правила использования таймера. Однако для нормального процесса прорисовки необходимо учитывать следующие факторы: во-первых, номер активной вкладки, так как мы будем использовать один таймер для всех графиков, во-вторых – в каком состоянии находится график: либо это процесс прорисовки, либо пауза, либо завершение. Поэтому, для того чтобы учесть все эти особенности создадим две функции: StartTmr() и StopTmr(), откуда и будем запускать или останавливать таймер.
Функция StartTmr() должна запускаться по нажатию на кнопку "Пуск".
Функция StopTmr() должна срабатывать в следующих ситуациях:
- когда построение графика завершилось;
- когда была нажата кнопка "Пауза" (только для КА);
- когда была нажата кнопка "Очистка" (только для КА).
С учетом вышеприведенных пояснений разработаем функции, позволяющие выполнять прорисовку графиков в режиме "бегущей дорожки", но пока без КА. К ним относятся следующие функции:
- обработчик события кнопки "Пуск";
- функция запуска таймера - StartTmr();
- обработчик события для таймера;
- функция остановки таймера - StopTmr().
Далее, рассмотрим процесс разработки этих функций более подробно.
 
7.11. Обработчик события для кнопки "Пуск"
 
Создадим обработчик события для кнопки "Пуск" (Button1) и впишем в него следующее:
 
void __fastcall TForm6::Button1Click(TObject *Sender)
{
 if(Form6->PageControl1->TabIndex==0) PaintBox1->Repaint();
 else                                 PaintBox2->Repaint();
    StartTmr();                   // временная функция, пока не создан КА
}
 
Из текста программного кода видно, что здесь сначала проверяется, какая вкладка открыта, затем запускается перерисовка графического окна этой вкладки с помощью команды Repaint(), что в свою очередь запускает графический конвейер и в заключение вызывается запуск таймера.
Графический конвейер, как мы помним, прорисовывает оси, сетку, уравнение и сам график в статическом режиме. Сейчас мы собираемся прорисовку графика возложить на таймер, поэтому команды, присутствующие в функциях ShowStat1() и ShowStat2() и отвечающие за прорисовку графиков, необходимо временно закомментировать в виде:
 
 // FPC1->MoveTo(matrGraf1[0][0], matrGraf1[1][0]);
 // for(int i=1;i<200;i++)
 //     ShowGraf1(i);                 // Статическая прорисовка графика
 
Совсем удалять эти команды не надо, они нам еще пригодятся, когда мы возьмемся реализовывать такие качества как восстановление графиков после переключения графических окон или после изменения параметров видимости элементов изображения с помощью меню.
Далее, когда мы разработаем конечный автомат для управления кнопками, запуск функции StartTmr() можно будет убрать из этого обработчика, т.к. запуском таймера будет руководить конечный автомат. А конечный автомат будет запускаться при каждой перерисовке графического окна (как видно из текстов функций GrafKonv1  и GrafKonv2), т.е. после простой команды Repaint().
Теперь можно на время закомментировать команду StartTmr() и запустить проект для проверки, чтобы убедиться, что ничего пока не прорисовывается, кроме осей, сетки и уравнения.
 
7.12. Разработка функции запуска таймера StartTmr()
 
Самой главной командой в этой функции является команда присвоения свойству Enabled, принадлежащего компоненту "Timer1", значения true, после чего таймер запускается самостоятельно. Но перед запуском таймера необходимо выполнить следующие подготовительные действия:
- проверяем, какая вкладка активна;
- устанавливаем значение переменной - счетчика таймерных проходов taimTek в исходное состояние (после разработки КА эту команду надо будет удалить);
- устанавливаем указатель пера в исходное положение, используя команду MoveTo;
- читаем значение временно́го интервала из компонента TrackBar1 "Скорость построения".
Тогда функция запуска таймера будет иметь вид:
 
void StartTmr(void)
{
 if(Form6->PageControl1->TabIndex==0)
   { taimTek1=0;                                       // временная функция
     FPC1->MoveTo(matrGraf1[0][taimTek1], matrGraf1[1][taimTek1]);
   }
 else
   { taimTek2=0;                                       // временная функция
     FPC2->MoveTo(matrGraf2[0][taimTek2], matrGraf2[1][taimTek2]);
   }
                                            // вычисляем интервал таймера
 Form6->Timer1->Interval=11-(Form6->TrackBar1->Position);
 Form6->Timer1->Enabled=true;               // включаем таймер
}
 
Переменные taimTek1 и taimTek2 объявите глобально. Раскомментируйте команду StartTmr() в обработчике Button1Click() и запустите проект для проверки, чтобы убедиться в отсутствии ошибок.

7.13. Обработчик события для таймера  Timer1
 
Таймер является стандартным компонентом пакета C++Builder. В задачу таймера входит непрерывное, повторное выполнение всех, входящих в его обработчик команд до тех пор, пока его не остановят командой Timer1->Enabled=false.
Поэтому, нам не надо специально организовывать цикл (как это было сделано в п. 7.6), для последовательной прорисовки всех двухсот отрезков, из которых состоит график, но проверку условия выхода из таймерных повторов надо будет сделать. После чего в состав условного оператора, выполняющего эту проверку, можно уже включить собственно функцию ShowGraf1(taimTek1) для прорисовки каждого отрезка нашего графика. Эта функция должна рисовать только тот отрезок, который соответствует номеру таймерного прохода, т.е. значению taimTek1. Таймер сделает двести таймерных проходов, нарисует все двести кусочков графика и прекратит свою работу:
 
void __fastcall TForm6::Timer1Timer(TObject *Sender)
{
 if(Form6->PageControl1->TabIndex==0)
    {if(taimTek1<200)
       {ShowGraf1(taimTek1);
        taimTek1++;
       }
     else StopTmr();
    }
 else
    {if(taimTek2<200)
       {ShowGraf2(taimTek2);
        taimTek2++;
       }
     else StopTmr();
    }
}
 
Как видим, здесь процесс изображения графиков происходит в режиме дорисовки, а не перерисовки. При этом оси, сетка и уравнения остаются неизменными, пока таймер добавляет к уже прорисованной части графика все новые и новые отрезки.
Перед тем как запустить проект на исполнение создайте функцию StopTmr() (см. п. 7.14), после чего, запустив проект, можно пронаблюдать процесс построения графиков в режиме "бегущей дорожки".
 
7.14. Разработка функции остановки таймера  StopTmr()
 
В содержимое этой функции введем только одну команду:    Form6->Timer1->Enabled=false;
 
На этом этап программирования части программы, выполняющей прорисовку графиков в режиме "бегущей дорожки" закончен. Прорисовка графиков выполняется по нажатию на кнопку "Пуск". Прекращение прорисовки графика производится автоматически, после 200 таймерных отсчетов. Кнопки "Пауза" и "Очистка" пока не работают. Можно, конечно сделать, чтобы эти кнопки работали и здесь, но мы сделаем это более красиво, когда разработаем конечный автомат.
 
7.15. Разработка функции КА
 
После того, как в разделе 5.1. нами был рассмотрен алгоритм построения блок-схемы конечного автомата, служащего для управления процессом прорисовки графиков, можно без всяких проблем написать программный код функции Avtomat():
 
void Avtomat(int *cycle, char *state, int *taimTek, bool *pysk, bool *pau, bool *oth)
{
 while(*cycle)
 switch(*state)
 {
   case 'A':
      if(*pysk==true){*state='B';
                      *pysk=false;
                     }
      else            *cycle=0;
      break;
 
   case 'B':
      if(*oth==true) {*state='A';
                      *oth=false;
                       StopTmr();
                      *taimTek=0;}
      else
        if(*pau==true){*state='C';
                        StopTmr();}
        else
          if(*pysk==true) {*state='B';
                          *taimTek=0;
                           *pysk=false;}
          else
            if(*taimTek==200) *state='D';
 
            else              {StartTmr();
                              *cycle=0;}
     break;
 
   case 'C':
      if(*oth==true)   {*state='A';
                        *pau=false;
                        *oth=false;
                        *taimTek=0;}
      else
        if(*pysk==true){*state='B';
                        *pysk=false;
                        *pau=false;
                       }
        else            *cycle=0;
      break;
 
   case 'D':
      if(*oth==true)   {*state='A';
                        *oth=false;
                        *taimTek=0;}
      else
        if(*pysk==true){*state='B';
                        *pysk=false;
                        *taimTek=0;
                       }
        else            *cycle=0;
      break;
 }
}
 
Как видно из текста программы, все переменные здесь используются как указатели. Это связано с тем, что мы используем один автомат для прорисовки двух разных графиков. Чтобы такое можно было реализовать, мы применяем ввод данных через список формальных параметров в описании функции автомата. С помощью этих параметров, используемых как параметры-переменные, мы сможем на только вводить необходимые значения для каждого графика, но и выводить их обратно в свои исходные переменные после преобразования внутри функции. Здесь необходимо использовать символ "&" перед именами переменных в списке фактических параметров при вызове функции и символ "*" в списке формальных параметров в описании функции. Символ "*", являющийся оператором раскрытия ссылки, поставлен также перед всеми переменными, передаваемыми по параметрам и в самом теле функции.
Объявите глобально и проинициализируйте в функции FormCreate() следующие переменные: cycle1, cycle2, state1, state2, pysk1, pysk2, pau1, pau2, oth1, oth2, taimTek1, taimTek2. Тип этих данных и их начальные значения должны быть понятны из предыдущих пояснений.
 
На этом создание конечного автомата завершено, но он еще не готов работать, пока не созданы обработчики для кнопок "Пуск", "Пауза" и "Очистка" и не внесены изменения в функции ShowStat1() и ShowStat2().
 
7.16. Создание обработчиков событий для кнопок "Пуск", "Пауза" и "Очистка"
 
Обработчик события для кнопки "Пуск" нами был уже создан в п.7.11. Сейчас, после создания КА мы перепишем его содержание в другом виде:
 
void __fastcall TForm6::Button1Click(TObject *Sender)
{
 if(Form6->PageControl1->TabIndex==0)
    {cycle1=1;
     pysk1=true;
     PaintBox1->Repaint();
    }
 else
    {cycle2=1;
     pysk2=true;
     PaintBox2->Repaint();
    }
}
 
Как видим, здесь добавлены две команды, принимающие участие в изменении состояния автомата при нажатии на кнопку "Пуск".
Создайте аналогичные обработчики для кнопок "Пауза" и "Очистка". Содержание команд в них абсолютно идентичен обработчику кнопки "Пуск", за исключением оператора pysk=true. Здесь вместо него необходимо вставить либо   pau=true, либо oth=true.
 
7.17.  Корректировка функций ShowStat1() и ShowStat2()
 
Тексты программного кода для этих функций первоначально были разработаны в разделе 7.6.
Работа этих функций обеспечивает постоянное восстановление изображения осей, сетки и уравнений при каждой перерисовке графических окон.
При работе с программой у пользователя может возникнуть ситуация когда, прорисовав графики функций в одном или обоих графических окнах, он пожелает переключить вкладки, чтобы, например, сравнить графики. Переключение вкладок ведет к перерисовке содержимого окон. Поэтому, функции ShowStat1() и ShowStat2() должны содержать не только вызовы функций прорисовки осей координат, сетки и уравнений, но и вызовы функций прорисовки графиков ShowGraf1() и  ShowGraf2(). Но перед прорисовкой необходимо выполнить проверку, был ли проинициализирован данный график или нет. В качестве признаков инициализации используем уже известные нам переменные pysk1, pysk2, pau1, pau2, oth1, oth2, taimTek1, taimTek2. Поэтому в составе функции ShowStat1() во-первых, снимем заглушки с команд прорисовки графиков, которые были закомментированы ранее, во-вторых, добавим перед этими командами оператор if, для выполнения необходимых проверок и, в-третьих, внесем изменения в параметры оператора цикла For.
Тогда, функция ShowStat1() примет следующий вид:
 
void ShowStat1(void)
{
 ShowOsi();
 ShowStk();
 ShowUrawn1();
 
 if(((taimTek1>0)&&(oth1==false)&&(pysk1==false))||((pau1==true)&&(oth1==false)))
     {FPC1->MoveTo(matrGraf1[0][0], matrGraf1[1][0]);
      for(i=1;i<(taimTek1);i++)
          ShowGraf1(i);
   }
}
Аналогичное добавление, с учетом соответствующих поправок, введем в функцию ShowStat2().
Эти корректировки еще не последние для функций ShowStat1() и ShowStat2(). Далее, при разработке функций меню мы рассмотрим еще одно, окончательное изменение в составе этих функций.
 
На этом этапе заканчивается программирование графической части проекта.
Снимите заглушки с вызовов функции Avtomat() в функциях GrafKonv1()  и GrafKonv2().
Удалите команду taimTek=0 из функции startTmr().
Сохраните проект и запустите на исполнение. Убедитесь в правильной работе программы по прорисовке графиков при различных комбинациях нажатия кнопок и переключений вкладок.
 
ПРОГРАММИРОВАНИЕ ГЛАВНОГО МЕНЮ
 
7.18. Компоновка разделов и подразделов  меню
 
1) На вкладке Standard  панели компонентов выберите компонент MainMenu и перенесите его на форму Form6. Расположите его значок в любом удобном месте формы, так как в окне рабочего проекта этот значок виден не будет.
 
2) В окошке Инспектора объектов (Object Inspector) выберите свойство Items для компонента   MainMenu и щелкните по окошку рядом с ним. Откроется окно "Form6->MainMenu1" (конструктор меню) с маленьким окошком, выделенным синим цветом, без текста. Другой способ открытия конструктора меню – дважды щелкнуть по компоненту MainMenu.
 
3) Для выделенного синего окошка в Инспекторе объектов в строке Caption введите название первого раздела меню – "Файл", после чего нажмите Enter. В результате на форме появится первый раздел меню.
 
4) В окне конструктора меню, рядом со словом "Файл" появится второе выделенное окошко синего цвета. Введите для него название второго раздела "График" в строке Caption Инспектора объектов. Затем то же самое сделайте для раздела меню "Сервис".
 
5) В окне конструктора меню выделите строку "Файл". Под ним появится окошко, выделенное синим цветом. Для него в Инспекторе объектов в строке Caption введите название первого подраздела разделаменю – "Новый". Для сохранения изменения нажмите Enter.
 
6) В окне конструктора меню, под словом "Новый" появится второе выделенное окошко синего цвета. Введите для него название второго подраздела "Открыть" в строке Caption Инспектора объектов. Затем то же самое сделайте для третьего подраздела этого же раздела меню "Сохранить как" и для четвертого подраздела "Закрыть".
 
7) Аналогично для раздела меню "График" создайте подразделы: "Цвет графика", "Пуск", "Остановить" и "Очистить".
 
8) Для раздела меню "Сервис" создайте подразделы "Показывать оси", "Показывать уравнение", "О программе". Для подразделов "Показывать оси" и "Показывать уравнение" установите свойство Checked  в состояние true, тогда напротив этих подразделов появятся флажки, которые можно будет включать или выключать.
 
На этом компоновка разделов и подразделов главного меню заканчивается. Сохраните проект, запустите его на исполнение и убедитесь в том, что все разделы меню развертываются в списки подразделов.
 
7.19.  Создание обработчиков событий для  подразделов меню
 
Заголовки функций-обработчиков событий для всех подразделов меню создаются одинаково: двойным щелчком по строчке конкретного подраздела в конструкторе формы.
Содержимое этих функций можно разделить на две группы.
К первой группе относятся функции меню, являющиеся дублерами кнопок управления. Так, например, программный код обработчика меню "Пуск" полностью копирует содержимое обработчика кнопки "Пуск". Обработчик меню "Остановить" копирует обработчик кнопки "Пауза" и т.д.
Ко второй группе относятся функции, которые требуют самостоятельной разработки. К ним относятся функции-обработчики подразделов меню: "Новый", "Открыть", "Сохранить как", "Закрыть", "Цвет графика", "Показывать оси", "Показывать уравнение" и "О программе".
Для работы таких меню как "Открыть", "Сохранить как" и "Цвет графика" на поле формы Form6 необходимо установить значки использования компонентов стандартных диалоговых окон. Порядок их установки рассмотрен ниже.
 
7.20. Обработчики событий для  подразделов меню
 "Пуск", "Остановить" и "Очистить"
 
Создайте обработчики для подразделов меню "Пуск", "Остановить" и "Очистить" и впишите в них команды, содержащиеся в аналогичных обработчиках для кнопок "Пуск", "Пауза" и "Очистить".
 
7.21.  Обработчик событий для  подраздела меню  "Новый"
 
Назначение этого подраздела – в приведении поля графического окна в исходное состояние, перед новой прорисовкой, поэтому здесь надо выполнить операции, аналогичные тому, что мы выполняли при нажатии на кнопку "Очистка", за исключением того, что нет необходимости вызывать функцию stopTmr();
 
7.22.  Обработчик событий для  подраздела меню  "Открыть"
 
На вкладке Dialogs  панели компонентов выберите компонент OpenDialog и перенесите его на форму Form6. Расположите его значок в любом удобном месте формы.
Создайте обработчик для подраздела меню "Открыть" и впишите в него следующие команды:
 
void __fastcall TForm6::N5Click(TObject *Sender)
{
 OpenDialog1->Filter = "*.bmp";
 OpenDialog1->InitialDir = "C:\\";
 if(OpenDialog1->Execute())                         // открываем окно открытия
   {
    Graphics::TBitmap *b;
    b = new Graphics::TBitmap;                      // создаем битмап
    b->LoadFromFile(OpenDialog1->FileName);         // загружаем в него файл
 
    if(Form6->PageControl1->TabIndex==0)
      {for(int i=0; i<W1; i++)
         for(int j=0; j<H1; j++)             // переводим из битмапа на экран
           FPC1->Pixels[i][j] = b->Canvas->Pixels[i][j];
      }
    else
      {for(int i=0; i<W2; i++)
         for(int j=0; j<H2; j++)             // переводим из битмапа на экран
           FPC2->Pixels[i][j] = b->Canvas->Pixels[i][j];
      }
    delete b;
    openfile = true;                        // устанавливаем флажок открытия,
   }                                        //  чтобы окно не перерисовалось
}
 
Переменную openfile объявите глобально и проинициализируйте в FormCreate() в виде: openfile=false;.
 
7.23.  Обработчик событий для  подраздела меню  "Сохранить как"
 
Данный раздел меню позволяет сохранять картинки графиков, созданных в графических окнах данного модуля в файлах с расширением .bmp.
На вкладке Dialogs  панели компонентов выберите компонент SaveDialog и перенесите его на форму Form6. Расположите его значок в любом удобном месте формы.
Создайте обработчик для подраздела меню "Сохранить как" и впишите в него следующие команды:
 
void __fastcall TForm6::N6Click(TObject *Sender)
{
 SaveDialog1->Filter = "*.bmp";
 SaveDialog1->InitialDir = "C:\\";
 if(SaveDialog1->Execute())                    // открываем окно сохранения
    {
     Graphics::TBitmap *b;
     b = new Graphics::TBitmap;                 // создаем временный битмап
 
     if(Form6->PageControl1->TabIndex==0)
       {b->Width=W1;                            // задаемемуразмеры
        b->Height=H1;
        for(int i=0; i<W1; i++)
          for(int j=0; j<H1; j++)               // запоминаем в битмапе картинку
            b->Canvas->Pixels[i][j]=FPC1->Pixels[i][j];
       }
     else
       {b->Width=W2;                            // задаем ему размеры
        b->Height=H2;
        for(int i=0; i<W2; i++)
          for(int j=0; j<H2; j++)               // запоминаем в битмапе картинку
            b->Canvas->Pixels[i][j]=FPC2->Pixels[i][j];
       }
     b->SaveToFile(SaveDialog1->FileName);      // записываем битмап в файл
     delete b;                                  // удаляем битмап
 }
}
 
7.24.  Обработчик событий для  подраздела меню  "Закрыть"
 
Здесь надо прописать всего одну команду:
 
void __fastcall TForm6::N7Click(TObject *Sender)
{
 Application->Terminate();                    // Закрываем приложение
}
 
7.25.  Обработчик событий для  подраздела меню  "Цвет графика"
 
На вкладке Dialogs  панели компонентов выберите компонент ColorDialog и перенесите его на форму Form6. Расположите его значок в любом удобном месте формы.
Создайте обработчик для этого подраздела меню и впишите следующие команды:
 
void __fastcall TForm6::N13Click(TObject *Sender)
{
 if(ColorDialog1->Execute())
   {if(Form6->PageControl1->TabIndex==0)
       col1=ColorDialog1->Color;
    else
       col2=ColorDialog1->Color;
   }
}
 
Как видно из этого текста, в переменных col1 и col2 будут сохраняться выбранные цвета графиков для каждой вкладки отдельно.
Чтобы эта функция работала, необходимо внести следующие изменения в программу:
1) Объявите глобально переменные col1 и col2 в виде:    TColor col1, col2;
2) Проинициализируйте переменные col1 и col2 в FormCreate() в виде:
col1=clRed;
col2=clRed;
Это необходимо для того, чтобы задать нашим графикам первоначальные цвета, даже если мы не выбрали цвет через меню.
3) В функциях showGraf1() и  showGraf2() замените команды:
 
FPC1->Pen->Color=clRed;   и   FPC2->Pen->Color=clRed;
 
на:       FPC1->Pen->Color=col1;     и   FPC2->Pen->Color=col2;
 
7.26.  Обработчик событий для  подраздела меню  "Показывать оси"
 
Этот обработчик работает, если установлен флажок напротив строки подраздела меню "Показывать оси". При выключении флажка должна происходить перерисовка графического окна, таким образом, чтобы оси и сетка не прорисовывались, а график - прорисовывался, т.е. восстанавливался, если он до этого присутствовал на рисунке. Что и видно из примера:
 
void __fastcall TForm6::N11Click(TObject *Sender)
{
 N11->Checked = !N11->Checked;                        // инвертируем флажок
 if(Form6->PageControl1->TabIndex==0)
    PaintBox1->Repaint();
else
    PaintBox2->Repaint();
}
 
Здесь график будет прорисовывается быстро, без анимации, с использованием готовых, раннее проинициализированных координат.
Для того чтобы решить, требуется ли прорисовывать оси или нет, надо знать, установлен ли флажок "Показывать оси" в меню. Поэтому, в составе функций ShowStat1() и ShowStat2() необходимо ввести проверки состояния компонента N11Click .
Для этого вместо команд:
    ShowOsi();
    ShowStk();
введите:
if(N11->Checked==true)
   {
    ShowOsi();
    ShowStk();
   }
 
7.27.  Обработчик событий для  подраздела меню  "Показывать уравнение"
 
Принцип разработки этой функции аналогичен предыдущей. Сначала здесь необходимо проинвертировать состояние флажка возле строки "Показывать уравнение" в меню, затем проверить, какая вкладка активна, и затем запустить перерисовку графического окна:
 
void __fastcall TForm6::N12Click(TObject *Sender)
{
 N12->Checked = !N12->Checked;        // инвертируем флажок
 
 if(Form6->PageControl1->TabIndex==0)
    PaintBox1->Repaint();             // запускаем перерисовку 1-го окна
 else
    PaintBox2->Repaint();             // запускаем перерисовку 2-го окна
}
При перерисовке, т. е. при перезапуске функций ShowStat1() и ShowStat2() программа сама определит, надо ли прорисовывать уравнения, так как в этих функциях должна быть прописана проверка состояния компонента N12Click. В них, вместо команды:
 
     ShowUrawn1();
введите:
 if(N12->Checked==true)
     ShowUrawn1();
 
Аналогично для ShowStat2(), с учетом необходимой корректировки.

7.28. Обработчик событий для  подраздела меню  "О программе"
 
Создайте новую форму Form7 небольшого размера, настройте видимость формы и добавьте команду препроцессорной обработки:  #include "Unit7.h".
Открытие формы производите по команде инициализации, помещенной в обработчике данного подраздела меню:
 
void __fastcall TForm6::N14Click(TObject *Sender)
{
 Application->CreateForm(__classid(TForm7), &Form7);
}
 
На поле формы поместите справочный текст, который на ваш взгляд может представлять интерес для пользователя, использующего вашу программу в учебных или научных целях.
 
 
8. Контрольные вопросы
 
1. Дайте определение, что такое аналитическая кривая.
2. Перечислите достоинства аналитического представления графиков.
3. Назовите способы задания аналитических кривых.
4. Что такое непараметрическая кривая и способы её задания.
5. Перечислите известные вам типы функций непараметрических кривых и изобразите их графики.
6. Что такое параметрическая кривая и способы её задания.
7. Представление параметрической кривой в декартовых координатах.
8. Представление параметрической кривой в полярных координатах.
9. Напишите формулы преобразования уравнений параметрических кривых при переходе из декартовых координат в полярные и обратно.
10. Перечислите и изобразите известные вам виды параметрических кривых.