Настоящее учебное пособие является продолжением пособия "Логическое программирование. Часть 1. Основы программирования на языке Visual Prolog", которое посвящено основным теоретическим положениям логического программирования и основам программирования на современной версии языка Visual Prolog. Вторая часть посвящена разработке приложений с графическим интерфейсом пользователя в интегрированной среде разработки системы программирования Visual Prolog. Цель пособия — формирование практических умений по использованию логического программирования в практической деятельности, получение навыков создания приложений на языке Visual Prolog.
Содержание пособия соответствует программе и охватывает все темы второй части курса. Пособие состоит из трех разделов. В первом разделе рассматриваются основные элементы графического интерфейса пользователя и средства создания изображений. Второй раздел посвящен основным элементам управления. Создается приложение, в котором используются вкладки, списки, переключатели, групповые блоки, деревья, поля редактирования и другие элементы управления. В третьем разделе приводятся примеры разработки игр. Создается игра Джона Конвея "Жизнь". В игре "Крестики-нолики" для реализации хода компьютера используются правила. В игре "Гексагон" ход компьютера выбирается с помощью алгоритма альфа-бета отсечения, компьютер может играть на разных уровнях сложности. В каждой главе приводится список упражнений для самостоятельной работы. Пособие содержит большое количество иллюстраций. Завершает книгу список литературы и предметный указатель.
Язык Visual Prolog является объектно-ориентированным. В пособии при создании приложений с графическим интерфейсом пользователя объектно-ориентированное программирование сочетается с логическим и функциональным.
Версия системы Visual Prolog Personal Edition свободно доступна на сайте разработчика www.visual-prolog.com. При установке системы рекомендуется сразу установить набор примеров Visual Prolog Examples.
Создаются простейшие приложения с графическим интерфейсом пользователя в интегрированной среде разработки Visual Prolog. Рассматриваются основные средства рисования.
В приложении drawing строится золотой прямоугольник и приближенная золотая спираль. В последующих двух главах в него добавляются новые модули.
Ключевые слова: графический интерфейс, приложение, окно, редактор ресурса, свойство, событие, рисование, золотое сечение, градиентная заливка
Интегрированная среда разработки (IDE — Integrated Development Environment) Visual Prolog содержит все необходимые средства для создания приложений с графическим интерфейсом пользователя (GUI — Graphical User Interface). Она включает компилятор, отладчик, текстовый и графический редакторы, редакторы ресурсов, средства для быстрой навигации по проекту и средства автодополнения (IntelliSense), средства автоматизации сборки и автоматической генерации кода и др.
Вид приложения устанавливается при создании проекта: исполняемое или DLL (DLL можно создавать только в CE — Commercial Edition), с графическим интерфейсом пользователя (MDI или SDI) или консольное.
Создадим простейшее приложение с графическим интерфейсом пользователя. Выберем команду меню ):
Project Name: hello;Project Kind: MDI.Мультидокументный интерфейс (MDI — Multiple Document Interface) — это способ организации оконного интерфейса, в котором дочерние окна располагаются внутри одного окна.
(рис 1.1) Диалоговое окно New Project
Нажмем кнопку ).
(рис 1.2) Главное окно приложения hello
Созданное приложение содержит главное меню, панель инструментов, строку состояния, окно сообщений и окно About ("О программе").
Когда создается Windows-приложение, запускается цикл обработки сообщений о происходящих событиях. Операционная система обрабатывает сообщения в порядке очереди согласно приоритетам и передает сообщения программе, что приводит к вызову обработчиков событий.
В частности, когда пользователь выбирает пункт меню, система Windows посылает программе сообщение об этом. Ниже в программу добавляется обработчик соответствующего события.
Изначально в пункте меню ), при выборе которого приложение закроется (и завершится цикл обработки сообщений). Выбор остальных подпунктов ни к чему не приведет.
Сделаем так, чтобы при выборе пункта меню File -> New открывалось диалоговое окно Note (окно сообщений). Для этого потребуется его включить — открыть к нему доступ.
(рис 1.3) Пункт меню File -> New (a) выключенный; (b) включенный
В папке ).
(рис 1.4) Дерево проекта hello. Элемент TaskMenu.mnu
С помощью правой кнопки мыши открывается контекстное меню, команды которого содержат операции, которые можно выполнять над выделенным элементом. С помощью двойного клика мыши открывается его редактор.
Итак, с помощью двойного клика мыши по элементу ). В нем следует выделить подпункт New пункта меню File и снять флажок из поля Disabled (выключен).
(рис 1.5) Редактор меню
В поле Accelerator указана "горячая" клавиша (F7). Идентификатор ресурса пункта меню (id_file_new) указывается в поле Item.
Далее редактор меню нужно закрыть и сохранить сделанные изменения.
Компоненты ).
Добавим обработчик события выбора пункта меню ).
(рис 1.6) Эксперт главного окна
Последовательно раскроем узлы ). В результате в имплементации класса taskWindow (файл taskWindow.pro) появится автоматически сгенерированный код:
predicates
onFileNew : window::menuItemListener.
clauses
onFileNew(_Source, _MenuTag).
Напомним, что файлы с расширением i содержат интерфейсы классов, с расширением cl — декларации классов, с расширением pro — имплементации классов.
Предикат onFileNew — это обработчик события выбора команды меню File -> New. В его определение нужно поместить вызов окна сообщений — диалогового окна Note:
predicates
onFileNew : window::menuItemListener.
clauses
onFileNew(_Source, _MenuTag):-
vpiCommonDialogs::note("Hello", "Hello, World!").
Класс ).
(рис 1.7) Автодополнение. Список предикатов создания стандартных окон
Запустим приложение. При выборе команды меню ).
(рис 1.8) Приложение hello
Базовым классом окон является класс window. Интерфейс window наследуют все виды окон — формы, диалоговые окна, элементы управления и др. Создадим простейшую форму и диалоговое окно. Изменим определение предиката onFileNew следующим образом:
onFileNew(_Source, _MenuTag):-
Form = formWindow::new(This),
Form:setText("Hello!"),
Form:show().
Конструктор new/1 создает объект класса formWindow. Переменная Form хранит указатель на объект формы. Переменная This хранит указатель на объект текущего класса, в данном случае, класса taskWindow. Главное окно приложения является родительским окном формы. Предикат setText/1 помещает текст в строку заголовка окна. Предикат show визуализирует окно. По умолчанию автоматически устанавливаются остальные свойства окна (размер, положение и др.).
После запуска откроется пустая форма, в строке заголовка которой будет выведено: "Hello!" (рис. 1.9 (). Можно изменять размеры окна, сворачивать его и разворачивать, а также открыть несколько таких окон.
(рис 1.9) (a) Форма; (b) диалоговое окно
Заменим в коде имя класса ), которое заблокирует возможность взаимодействия с приложением до тех пор, пока не будет закрыто.
В дальнейшем окна создаются с помощью диалогового окна Create Project Item, а их основные свойства устанавливаются в визуальном редакторе.
Создадим проект под названием drawing (MDI). Он будет использоваться в трех первых главах для построения изображений.
По умолчанию в строке заголовка главного окна приложения пишется имя проекта (см. рис. 1.8 рис 1.8). Изменить надпись в строке заголовка, а также установить другие атрибуты главного окна приложения можно с помощью окна ). Для того чтобы открыть это окно, следует выделить элемент TaskWindow.win дерева проекта, открыть контекстное меню с помощью правой кнопки мыши и выбрать в нем пункт Attributes.
Изменим текст, содержащийся в в поле ). В результате главное окно приложения будет автоматически развернуто во весь экран.
(рис 1.10) Окно атрибутов главного окна приложения
Установить указанные атрибуты главного окна приложения можно и программным образом, с помощью предикатов setText/1 и setState/1. Достаточно изменить определение конструктора new в файле taskWindow.pro следующим образом:
new() :-
applicationWindow::new(),
generatedInitialize(),
setText("Тестовый проект"),
setState([wsf_Maximized]).
Изменим главное меню приложения. Для этого, как и ранее, с помощью элемента ).
(рис 1.11) Изменение главного меню приложения
Внесем в меню следующие изменения:
Edit: выделим его и нажмем клавишу Delete;Test: выделим пункт File, выберем пункт New всплывающего меню (или нажмем кнопку New Item панели инструментов окна) и напишем название Test;Test, выберем пункт New SubItem всплывающего меню и введем название rectangle;rectangle, выберем пункт New всплывающего меню и напишем название gradient (это элементы одного уровня, поэтому используется команда New, а не NewSubItem).При создании пунктов меню автоматически генерируются идентификаторы, поэтому для их названий нами использовалась латиница. Меню можно русифицировать с помощью пункта ).
Создадим форму. Для этого выделим корень дерева проекта (узел ).
(рис 1.12) Создание элемента проекта
Нажмем кнопку ). Редактор окна включает прототип (графический редактор) формы, окна Controls и Layout, которые используются для размещения в окне элементов управления, а также окно свойств Properties. Окно свойств содержит две вкладки. Вкладка Properties содержит таблицу свойств и используется для установки атрибутов окна. Вкладка Events содержит таблицу событий и используется для добавления предикатов обработки событий.
(рис 1.13) Редактор окна
По умолчанию форма содержит кнопки OK, Cancel и Help. Их следует удалить. Для того чтобы удалить кнопку, нужно ее выделить и нажать клавишу Delete.
Изменим строку заголовка окна с помощью поля . Далее нужно закрыть редактор окна и сохранить изменения. Достаточно нажать крестик на прототипе формы, остальные окна закроются автоматически. Открыть редактор окна можно с помощью элемента drawForm.frm дерева проекта.
Добавим обработчик события выбора пункта меню Тест -> Золотой прямоугольник. Для этого откроем эксперт окон с помощью элемента ). Определение предиката onTestRectangle следует изменить так, как показано ниже.
predicates
onTestRectangle : window::menuItemListener.
clauses
onTestRectangle(_Source, _MenuTag):-
_ = drawForm::display(This).
Предикат display/1 определяется следующим образом (см. файл drawForm.pro):
display(Parent) = Form :-
Form = new(Parent),
Form:show().
Сначала создается объект класса, а потом выполняется операция визуализации окна. Предикат возвращает указатель на объект формы. Здесь он не используется, поэтому в коде, приведенном выше, стоит анонимная переменная.
Скомпилируем и запустим приложение. При запросе компилятора о добавлении директивы ) следует нажать кнопку Add или Add All:
(рис 1.14) Добавление директивы include
При выборе пункта меню Тест -> Золотой прямоугольник открывается пустая форма.
Изображение в окне строит обработчик событий PaintResponder — предикат, который принадлежит предикатному домену paintResponder. По умолчанию ему дается имя onPaint.
Операционная система Windows непрерывно отслеживает события и посылает сообщения программе, что приводит к вызову предикатов обработки событий. В системе GUI используются обработчики событий двух видов — приемники (listener) и ответчики (responder). Обработчик события первого вида принимает сообщение, запускает обработку события некоторым стандартным способом и не возвращает никакого ответа. Обработчик второго вида возвращает некоторый ответ, который учитывается системой GUI. Примером первого вида обработчиков событий является обработчик события вызова команды меню, который уже использовался ранее (он принадлежит предикатному домену menuItemListener). Обработчик PaintResponder является примером обработчиков событий второго вида.
Для всех окон, кроме главного окна приложения (). Добавить предикат обработки событий можно и программным образом, с помощью соответствующих предикатов (например, addMenuItemListener).
Добавим предикат обработки события ).
(рис 1.15) Таблица событий
Закроем редактор формы. Автоматически в имплементации класса drawForm появится код
predicates
onPaint : window::paintResponder.
clauses
onPaint(_Source, _Rectangle, _GDI).
Первый аргумент предиката onPaint хранит указатель на объект окна, из которого поступило сообщение, второй — прямоугольную область, подлежащую перерисовке (в ней строится изображение), третий — указатель на объект класса
Все рисование в окне должно производиться с помощью предиката onPaint. Для начала просто изменим цвет фона:
onPaint(_Source, _Rectangle, GDI):-
GDI:clear(color_LightSeaGreen).
Предикат clear/1 закрашивает внутреннюю, или клиентскую, область окна одним цветом. Для того чтобы увидеть результат, нужно запустить приложение и открыть окно.
Нашей задачей является построение золотого прямоугольника и приближенной логарифмической спирали. Спираль строится как последовательность дуг — четвертей окружностей. Поэтому для начала построим обычный прямоугольник, не являющийся золотым, и проведем дугу эллипса в нем. Для этого изменим определение предиката onPaint так, как показано ниже:
onPaint(_Source, _Rectangle, GDI):-
GDI:clear(color_LightSeaGreen),
getClientSize(W, H),
GDI:setPen(pen(1, ps_Solid, color_DarkBlue)),
GDI:setBrush(brush(pat_Solid, color_DarkBlue)),
Rct = rct(W div 4, H div 4, 3*(W div 4), 3*(H div 4)),
GDI:drawRect(Rct),
GDI:setPen(pen(2, ps_Solid, color_White)),
GDI:drawArc(Rct,pnt(3*(W div 4), H div 4), pnt(W div 4, H div 4)).
Результат показан на рис. 1.16 (.
(рис 1.16) Прямоугольник и дуга эллипса
Предикат getClientSize/2 возвращает размеры сторон клиентской области окна — ширину и высоту. Ширина прямоугольника — это длина его горизонтальной стороны, а высота — вертикальной. Предикат setPen/1 устанавливает параметры пера, предикат setBrush/1 — параметры заливки, предикат drawRect/1 строит прямоугольник, предикат drawArc/3 проводит дугу эллипса.
Домены термов, представляющих инструменты рисования, принадлежат классу VPI означает Visual Programming Interface (интерфейс визуального программирования).pen, brush, pnt и rct, соответственно.
Точка описывается двумя целыми координатами (integer) в системе координат окна в виде pnt(X, Y). Началом координат окна является левый верхний угол клиентской области окна, ось абсцисс направлена вправо, ось ординат — вниз, единицей измерения является пиксель.
Предикат drawLine/2 проводит отрезок, соединяющий две точки. Например, добавим в определение предиката onPaint строку кода (в конец правила):
drawLine(pnt(0, H div 2), pnt(W, H div 2)).
В результате на рисунке появится горизонтальный отрезок белого цвета, разделяющий изображение на две половины.
Прямоугольник представляется термом rct(X1, Y1, X2, Y2), где (X1, Y1) и (X2, Y2) — координаты произвольной пары противолежащих вершин. В данном случае указаны координаты левой верхней вершины и правой нижней вершины (см. код).
Ширина прямоугольника, построенного выше, равна половине ширины окна, высота — половине высоты окна. Соответственно, площадь прямоугольника составляет четверть от площади клиентской области окна.
Предикату ). Точки указываются в направлении против часовой стрелки, как это выглядит для пользователя; в системе координат окна — по часовой стрелке. Поэтому вторым аргументом предиката drawArc является точка P, а третьим — точка R (см. код).
Если изменить размеры окна с помощью мыши, то изображение исказится, так как перерисовка выполняется по-минимому. Поэтому в редакторе окна следует добавить обработчик события изменения размеров окна ) и определить его так, как показано ниже.
predicates
onSize : window::sizeListener.
clauses
onSize(_Source):-
invalidate().
Предикат invalidate/0 помечает всю клиентскую область окна как недействительную и, вследствие этого, требующую перерисовки. В результате инициируется событие Paint, что затем приводит к вызову предиката onPaint.
Упражнение. Постройте изображение, приведенное на рис. 1.16 (.
Далее создается изображение, показанное на рис. 1.17 рис 1.17.
(рис 1.17) Золотой прямоугольник и приближенная золотая спираль
Построим золотой прямоугольник. Золотым называется прямоугольник, отношение длин сторон которого равно коэффициенту золотого сечения
$$\phi=\frac{1+\sqrt{5}}{2}$$Этот коэффициент будет использоваться в разных классах, поэтому для его хранения создадим отдельный класс. Для этого выделим корень дерева проекта и выберем команду (всплывающего) меню New in New Package (или используем сочетание клавиш Ctrl+N). В появившемся окне Create Project Item следует выбрать элемент Class, вписать в поле Name имя класса goldenRatio, убрать флажок из поля Create Interface и нажать кнопку Create. В декларацию класса (файл goldenRatio.cl) следует поместить объявление свойства.
properties
value : real.
В имплементацию (файл goldenRatio.pro) нужно добавить его определение.
class facts
value : real := (1 + math::sqrt(5)) / 2.
Далее модуль нужно скомпилировать с помощью команды меню Compile. В дальнейшем всегда предполагается, что модуль компилируется и, при запросе системы, подключаются соответствующие директивы include.
Теперь аналогичным образом создадим класс geom, который будет использоваться для вычисления параметров золотого прямоугольника и построения спирали. В декларацию класса geom (файл geom.cl) поместим объявление предиката, который возвращает координаты прямоугольника, близкого к золотому (координаты вершин округляются до целых). Аргументами предиката являются значения ширины и высоты клиентской области окна.
open core, vpiDomains
predicates
goldenRectangle: (integer W, integer H) -> rct Rectangle.
В раздел open декларации класса geom (файл geom.cl), а также его имплементации (файл geom.pro) следует добавить имя класса vpiDomains. Определение предиката приведено ниже.
open core, vpiDomains
clauses
goldenRectangle(Width, Height) = rct(X1, Y1, X2, Y2):-
X1 = math::round(Width / 8),
X2 = math::round(7 * Width / 8),
Hr = (X2 - X1)/goldenRatio::value, % высота прямоугольника
Y1 = math::round((Height - Hr) / 2),
Y2 = math::round(Y1 + Hr).
Напоминаем, что модуль нужно скомпилировать.
Вернемся к имплементации класса drawForm (файл drawForm.pro).
Прежде всего, в разделе констант определим цвета, которые будут использоваться для построения изображения: цвет рамки, ограничивающей прямоугольник, цвет его заливки, цвет кривой и цвет фона. Далее определим толщину линий и максимально допустимое количество итераций для построения кривой. Кроме этого, определим предикат outerRectangle/1, который возвращает прямоугольник, размеры которого превышают размеры золотого прямоугольника на толщину линии, чтобы кривая располагалась внутри прямоугольника.
constants
bgColor : color = color_LightSeaGreen.
lineColor : color = color_Black.
brushColor : color = color_MidnightBlue.
arcColor : color = color_White.
rctPenWidth = 5.
arcPenWidth = 2.
maxIterNum = 11.
facts
iterNum : positive := 7. % количество итераций
predicates
outerRectangle: (rct) -> rct.
clauses
outerRectangle(rct(X1, Y1, X2, Y2)) =
rct(X1 - rctPenWidth, Y1 - rctPenWidth,
X2 + rctPenWidth, Y2 + rctPenWidth).
Определение предиката onPaint следует изменить так, как показано ниже.
clauses
onPaint(_Source, _Rectangle, GDI):-
getClientSize(Width, Height),
GoldenRectangle = geom::goldenRectangle(Width, Height),
GDI:clear(bgColor),
GDI:setPen(pen(rctPenWidth, ps_Solid, lineColor)),
GDI:setBrush(brush(pat_Solid, brushColor)),
GDI:drawRect(outerRectangle(GoldenRectangle)).
Построенное изображение показано на рис. 1.18 рис 1.18.
(рис 1.18) Золотой прямоугольник
Спираль строится итеративным образом. Рассмотрим золотой прямоугольник . Для его сторон выполняется соотношение
$$|AB|=\phi|AD|,$$где $$$\phi$$$ — коэффициент золотого сечения.
(рис 1.19) Построение спирали
На первой итерации проводится дуга DE окружности с центром в точке G и радиусом AD, где E и G — точки, лежащие на сторонах AB и CD, соответственно, такие что прямоугольник AEGD является квадратом. Далее рассуждения повторяются для прямоугольника BCGE, который также является золотым.
Пусть точки A и D имеют координаты $$$(x_{1},y_{1})$$$ и $$$(x_{2},y_{2})$$$, соответственно. Найдем координаты точек E и G. Имеем: $$$\overline{AD}=(x_{2}-x_{1},y_{2}-y_{1})$$$. Вектор $$$\overline{AE}$$$ перпендикулярен вектору $$$\overline{AD}$$$, длины этих векторов совпадают, поэтому вектор $$$\overline{AE}$$$, с точностью до знака, равен вектору $$$$(y_{1}-y_{2},x_{2}-x_{1})$$$$, т. е. $$$\overline{AE}=k(y_{1}-y_{2},x_{2}-x_{1})$$$, где k = 1 или k = – 1. Далее, ориентация системы векторов $$$\overline{AE}$$$, $$$\overline{AD}$$$ совпадает с ориентацией ортов координатных осей, поэтому определитель матрицы, составленной из координат этих векторов, должен быть положителен:
Поэтому k = – 1. Следовательно,
Обозначим через O начало координат. Для координат точек E и G имеем:
Дуга ). Вершину F этого квадрата, противоположную вершине A, можно найти следующим образом:
Добавим в декларацию класса geom объявление предиката createSpiral, который возращает список троек, состоящих из двух точек и прямоугольника, определяющего дугу окружности. Количество элементов списка равно количеству итераций.
predicates
createSpiral: (positive N, rct GoldenRectangle) ->
tuple{pnt From, pnt To, rct Rectangle}* ArcList.
Ниже приведено определение предиката (файл geom.pro).
clauses
createSpiral(N, rct(X1, Y1, X2, Y2)) = spiral(N,
[pnt(X1, Y1), pnt(X2, Y1), pnt(X2, Y2), pnt(X1, Y2)]).
class predicates
spiral: (positive, pnt*) -> tuple{pnt From, pnt To, rct}*.
clauses
spiral(0, _) = []:- !.
spiral(N, RectVList) = [Arc | spiral(N - 1, NextRectVList)]:-
step(RectVList, Arc, NextRectVList).
class predicates
step: (pnt* VList, tuple{pnt From, pnt To, rct Rect} [out],
pnt* [out] NextVList).
clauses
step(VL, tuple(E, pnt(X2, Y2), rct(X1, Y1, Xf, Yf)), [B, C, G, E]):-
VL == [pnt(X1, Y1), B, C, pnt(X2, Y2)], % A, B, C, D
E = pnt(X1 + Y2 - Y1, Y1 + X1 - X2),
G = pnt(X2 + Y2 - Y1, Y2 + X1 - X2),
Xf = X1 + 2 * (X2 - X1) + 2 * (Y2 - Y1),
Yf = Y1 + 2 * (Y2 - Y1) + 2 * (X1 - X2).
Предикат step/3 выполняет одну итерацию. Он возвращает тройку параметров, определяющих дугу окружности, и следующий золотой прямоугольник.
Определение предиката onPaint (файл drawForm.pro) следует изменить так, как показано ниже.
clauses
onPaint(_Source, _Rectangle, GDI):-
getClientSize(W, H),
GoldenRectangle = geom::goldenRectangle(W, H),
GDI:clear(bgColor),
GDI:setPen(pen(rctPenWidth, ps_Solid, lineColor)),
GDI:setBrush(brush(pat_Solid, brushColor)),
GDI:drawRect(outerRectangle(GoldenRectangle)),
GDI:setPen(pen(arcPenWidth, ps_Solid, arcColor)),
ArcList = geom::createSpiral(iterNum, GoldenRectangle),
list::forAll(ArcList,
{(tuple(From, To, Rect)):- GDI:drawArc(Rect, From, To)}).
Реализуем возможность изменения количества итераций в работающем приложении. Пользователь сможет с помощью правой кнопки мыши вызвать диалоговое окно ).
(рис 1.20) Изменение количества итераций
После нажатия на кнопку OK кривая будет перерисована.
В редакторе окна drawForm добавим обработчик события MouseDownListener, которое возникает, когда кнопка мыши нажата. Ниже приведено его определение.
predicates
onMouseDown : window::mouseDownListener.
clauses
onMouseDown(_Source, _Point, _, mouse_button_right):-
Title = "Количество итераций",
Prompt = string::format("Введите целое число в пределах"
"\nот 0 до % включительно", maxIterNum),
InitString = toString(iterNum),
Str = vpiCommonDialogs::getString(Title, Prompt, InitString),
N = tryToTerm(positive, Str),
N <= maxIterNum,
!,
iterNum := N,
invalidate().
onMouseDown(_Source, _Point, _ShiftControlAlt, _Button).
Даже при небольшом количестве итераций может возникать искажение кривой. Это вызвано ошибками округления. Описанный выше метод построения кривой годится только для прямоугольника, который в точности является золотым. Изменяя размеры окна с помощью мыши, можно достичь того, чтобы кривая приняла нужный вид.
Ниже для построения аналогичного изображения используются предикаты GDI+. Изображение кривой сглаживается. Применяется градиентная заливка.
Аналогично тому, как это делалось ранее, в проекте drawing следует создать форму drawPlusForm. В редакторе формы нужно изменить заголовок окна и удалить все кнопки. Далее следует добавить обработчик события выбора пункта меню Тест -> Градиент и определить его следующим образом:
clauses
onTestGradient(_Source, _MenuTag):-
_ = drawPlusForm::display(This).
В редакторе окна drawPlusForm необходимо добавить обработчики событий PaintResponder, SizeListener и DestroyListener. Событие Destroy возникает при уничтожении объекта.
Обработчик события изменения размеров окна onSize определяется так же, как и ранее:
onSize(_Source):-
invalidate().
Подсистема Windows GDI+ — это улучшенный вариант интерфейса Windows GDI для представления графических объектов и передачи их на устройства отображения. Перед ее использованием следует вызывать предикат startUp, а по завершении — предикат shutDown. Предикат startUp инициализирует библиотеку GDI+, предикат shutDown очищает используемые ресурсы.
Откроем файл drawPlusForm.pro. В раздел open добавим имя класса gdiPlus_native:
core, vpiDomains, gdiPlus_native
Изменим определение конструктора new/1 и определим факт-переменную token так, как показано ниже.
clauses
new(Parent):-
formWindow::new(Parent),
generatedInitialize(),
%
token := gdiplus::startUp().
facts
token : unsigned := erroneous.
Ниже приведено определение предиката onDestroy.
predicates
onDestroy : window::destroyListener.
clauses
onDestroy(_Source):-
gdiplus::shutDown(token),
token := erroneous.
Создадим класс geomPlus (следует убрать флажок из поля Create Interface), в котором определим предикаты построения золотого прямоугольника и спирали.
В предикатах GDI+ способ представления геометрических фигур отличается от способа, использованного ранее. Прямоугольник описывается не координатами двух противоположных вершин, а координатами левой верхней вершины, а также его шириной и высотой.
При построении нашей кривой на каждом четвертом шаге положение прямоугольника и дуги окружности повторяется, поэтому расчеты достаточно сделать для первых четырех шагов (см. рис. 1.19 рис 1.19).
Дуга окружности (эллипса) описывается с помощью координат левой верхней вершины описанного прямоугольника, его ширины и высоты, а также величины начального угла и угла раствора дуги в градусах (рис. 1.21 рис 1.21). Дуга проводится в направлении против часовой стрелки в системе координат окна, т. е. для пользователя — по часовой стрелке.
(рис 1.21) Определение начального угла для построения дуги
Величина угла раствора дуги в нашем случае всегда равна 900. На первой итерации начальный угол равен 1800, на каждой последующей итерации он увеличивается на 900.
Ниже приведено объявление предикатов построения золотого прямоугольника и спирали (файл geomPlus.cl) и их определение (файл geomPlus.pro).
Предикат goldenRectangle/6 возвращает координаты левой верхней вершины прямоугольника, его ширину и высоту. Предикат createSpiral/5 возвращает список четверок параметров. Четверка содержит координаты левого верхнего угла квадрата, в котором проводится дуга окружности, длину его стороны, а также величину начального угла.
predicates
goldenRectangle: (integer W, integer H, integer Xr [out],
integer Yr [out], integer Wr [out], integer Hr [out]).
predicates
createSpiral: (positive, integer X, integer Y, integer Wr, integer Hr)
-> tuple{integer FromX, integer FromY, integer Side, real Angle}*.
open core, vpiDomains
clauses
goldenRectangle(Width, Height, X, Y, Wr, Hr):-
Wrect = 3 * Width / 4, % ширина в real
Hrect = Wrect / goldenRatio::value, % высота в real
Wr = math::round(Wrect), % ширина в integer
Hr = math::round(Hrect), % высота в integer
X = math::round(Width / 8),
Y = math::round((Height - Hrect) / 2).
clauses
createSpiral(N, X, Y, Wr, Hr) = spiral(N, 0, [pnt(X, Y),
pnt(X + Wr, Y), pnt(X + Wr, Y + Hr), pnt(X, Y + Hr)], 180).
class predicates
spiral: (positive N, positive Counter, pnt* VL, integer InitAngle)
-> tuple{integer X, integer Y, integer Side, real Angle}* ArcList.
clauses
spiral(N, I, VL, Angle) =
[Arc | spiral(N, I + 1, NextVL, (Angle + 90) mod 360)]:-
I <= N,
!,
step(I, VL, Angle, Arc, NextVL).
getSpiral(_, _, _, _) = [].
class predicates
step: (positive, pnt*, integer,
tuple{integer, integer, integer, real} [out], pnt* [out]).
clauses
step(I, VL, Angle, tuple(X, Y, Side, Angle), [B, C, G, E]):-
VL == [pnt(X1, Y1), B, C, pnt(X2, Y2)], % A, B, C, D
E = pnt(X1 + Y2 - Y1, Y1 + X1 - X2),
G = pnt(X2 + Y2 - Y1, Y2 + X1 - X2),
Side = 2 * (math::abs(X2 - X1) + math::abs(Y2 - Y1)),
R = I mod 4,
K1 = if R > 0, R < 3 then 1 else 0 end if,
X = X1 - K1 * Side,
K2 = if R > 1 then 1 else 0 end if,
Y = Y1 - K2 * Side.
Вернемся к имплементации класса drawPlusForm. Добавим параметры, определяющие цвет и толщину линий, цвет заливки и величину угла раствора дуги. Для фона и прямоугольника используется градиентная заливка, поэтому для них определяется по два цвета.
constants
lineColor : unsigned = black.
arcColor : unsigned = white.
bgColor1 : unsigned = lightcyan.
bgColor2 : unsigned = lightseagreen.
rctColor1 : unsigned = lightskyblue.
rctColor2 : unsigned = midnightblue.
unit = unitPixel.
rctPenWidth = 5.
arcPenWidth = 2.
sweepAngle = 90. % угол раствора дуги
facts
iterNum : positive := 10.
Ниже приведено определение обработчика onPaint.
clauses
onPaint(_Source, _Rectangle, GDI):-
getClientSize(Width, Height),
HDC = GDI:getNativeGraphicContext(IsReleaseNeeded),
Graphics = graphics::createFromHDC(HDC),
createDrawing(Graphics, Width, Height),
GDI:releaseNativeGraphicContext(HDC, IsReleaseNeeded).
Предикат getNativeGraphicContext/1 возвращает дескриптор контекста устройства, который предоставляет система Windows для рисования в клиентской области окна. Он передается в объект класса graphics, который выполняет рисование в окне. Этот объект создается предикатом createFromHDC/1. Предикат releaseNativeGraphicContext освобождает контекст устройства для использования другими приложениями, если второй его аргумент равен true; в противном случае он ничего не делает.
Изображение строит предикат ).
predicates
createDrawing: (graphics, integer Width, integer Height).
clauses
createDrawing(Graphics, Width, Height):-
% Установление режима сглаживания линий
Graphics:smoothingMode := smoothingModeAntiAlias,
% Перья для проведения линий
LinePen = pen::createColor(
color::create(lineColor), rctPenWidth, unit),
ArcPen = pen::createColor(
color::create(arcColor), arcPenWidth, unit),
% Фон
Gradient = linearGradientBrush::createLineBrushFromRectI(
gdiplus::rectI(0, 0, Width, Height),
color::create(bgColor1),
color::create(bgColor2),
linearGradientModeVertical),
Graphics:fillRectangleI(Gradient, 0, 0, Width, Height),
% Прямоугольник
geomPlus::goldenRectangle(Width, Height, X, Y, Wr, Hr),
Graphics:drawRectangleI(LinePen, X - 3, Y - 3, Wr + 6, Hr + 6),
GradientR = linearGradientBrush::createLineBrushFromRectI(
gdiplus::rectI(X, Y, Wr, Hr),
color::create(rctColor1),
color::create(rctColor2),
linearGradientModeForwardDiagonal),
Graphics:fillRectangleI(GradientR, X - 1, Y - 1, Wr + 2, Hr +2),
% Кривая
ArcList = geomPlus::createSpiral(iterNum, X, Y, Wr, Hr),
try list::forAll(ArcList, {(tuple(Xa, Ya, Side, StartAngle)):-
Graphics:drawArcI(
ArcPen, Xa, Ya, Side, Side, StartAngle, sweepAngle)})
catch _ do
succeed()
end try.
Результат показан на рис. 1.22 рис 1.22. Спираль изображается сглаженной, в отличие от кривой, которая строится в окне drawForm. Как и ранее, кривая может "ломаться" из-за погрешности вычислений. Ее вид можно восстановить с помощью изменения размеров окна.
(рис 1.22) Градиентная заливка. Сглаживание линий
При изменении размеров окна изображение мерцает. Для того чтобы избавиться от этого, выполним два действия. Прежде всего, в редакторе окна drawPlusForm добавим обработчик событий EraseBackgroundResponder и определим его так, как показано ниже. Событие EraseBackground возникает, когда требуется обновить фон.
predicates
onEraseBackground : window::eraseBackgroundResponder.
clauses
onEraseBackground(_Source, _GDI) = noEraseBackground.
Кроме этого, для построения изображения используем холст. Создадим объект класса pictureCanvas и осуществим рисование в нем. Аргументами конструктора new/2 являются ширина и высота рисунка. Предикат getPicture закрывает объект для рисования и возвращает рисунок.
Интерфейс pictureCanvas поддерживает интерфейс windowGDI, поэтому создание изображений на холсте выполняется теми же операциями.
В данном случае достаточно изменить определение предиката onPaint следующим образом.
clauses
onPaint(_Source, _Rectangle, GDI):-
getClientSize(Width, Height),
Canvas = pictureCanvas::new(Width, Height),
HDC = Canvas:getNativeGraphicContext(IsReleaseNeeded),
Graphics = graphics::createFromHDC(HDC),
createDrawing(Graphics, Width, Height),
Canvas:releaseNativeGraphicContext(HDC, IsReleaseNeeded),
Picture = Canvas:getPicture(),
GDI:pictDraw(Picture, pnt(0, 0), rop_SrcCopy).
Для первоначального знакомства с ]. Подробнее об интегрированной среде разработки ].
Примеры построения изображений с помощью предикатов GDI+ приведены также в проекте GDI+ Example из собрания примеров Visual Prolog Examples.
Упражнения
1.1. Добавьте возможность изменения количества итераций во время работы приложения для окна drawPlusForm.
1.2. Постройте изображение последовательности золотых прямоугольников, которые используются при построения спирали.
1.3. Используйте для проведения каждой дуги (четверти окружности) свой цвет.
1.4. Придумайте и постройте свой рисунок средствами
(a) windowGDI; (b) GDI+
(см. Help, GDI+ Example и MSDN).
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.