Пакет GUI
Источник: Visual Prolog Help/PFC/GUI
Перевод выполнил Игорь Литуновский.
Редактор перевода Елена Ефимова
Введение
Основная цель пакета GUI - обеспечивать удобный способ работы с окнами, элементами управления, событиями и другими средствами GUI (графического интерфейса пользователя).
Пакет GUI представляет объектно-ориентированный подход к работе с окнами, диалогами и элементами управления. Этот пакет встроен в верхний слой VPI. Он предоставляет новый уровень абстракции при простоте использования. При этом обеспечивается совместимость с программами VPI, так как оба стиля, GUI и VPI, могут быть использованы в одной программе.
Сервисы, предоставляемые пакетом GUI
Пакет GUI предоставляет и отвечает за следующие средства:
- Набор обычных компонентов GUI: окна, диалоги, формы, элементы управления (флажки, поля редактирования, групповые блоки, пиктограммы, списки, раскрывающиеся списки, редактируемые списки, кнопки, переключатели, полосы горизонтальной и вертикальной прокрутки, надписи, специальные элементы управления).
- Система обработки событий, которая отмечает, когда пользователь взаимодействует с одним из компонентов GUI и передает эту информацию приложению.
- Контейнеры - объекты, в которые могут добавляться элементы управления (контейнерами могут быть диалоги, контейнерные элементы управления, формы).
- Управление размещением - возможность изменять размер, задавать положение и перемещать объекты GUI.
- Поддержка графических операций: нарисовать дугу, залить многоугольник, обрезать прямоугольник и т.д.
Наиболее важные интерфейсы, составляющие пакет GUI, описаны в следующих разделах.
Интерфейс window
Центральным понятием в пакете GUI является окно (window). Корневой интерфейс window поддерживается всеми окнами, формами, диалогами и элементами управления, определенными в пакете GUI. Этот интерфейс определяет основные предикаты управления окнами, которые могут быть применены к любому компоненту GUI.
Интерфейс window - это конструкционный тип (т.е. тип объектов) для класса window, который реализует основное управление окнами для всех интерфейсов пакета GUI. Однако прикладные программисты никогда не должны использовать конструктор window::new/1 класса window, для того чтобы явно создавать объекты типа window в своих программах. Они всегда должны создавать окна, используя конструкторы типов applicationWindow, documentWindow, formWindow, childWindow или dialog.
Объектно-ориентированное программирование хорошо подходит для разработки оконных систем графического интерфейса пользователя. Принцип создания новых компонентов GUI с помощью наследования существующих компонентов или замены некоторых их характеристик сохраняет время и силы. Оно также способствует облегчению понимания отношений между компонентами GUI.
Интерфейс window содержит следующую информацию о наблюдаемом поведении и внешнем виде экранных объектов:
- местоположение этих объектов;
- являются ли они видимыми или невидимыми;
- могут ли они принимать ввод (который может быть включен или отключен);
- управление их состоянием;
- добавление приемников (listener) и настройка ответчиков (responder);
- управление таймерами.
В интерфейсе window имеются предикаты для получения, добавления и настройки многих из этих параметров. См. интерфейс window настоящего справочного руководства для детального изучения соответствующих предикатов.
Элементы управления GUI
Термин "элемент управления" обычно применяется к маленьким окнам специального вида, выполняющим определенные функции. Они размещаются внутри других окон, в основном в качестве компонентов диалогов и форм. Элементы управления обеспечивают пользователя средствами набора текста, задания параметров и вызова команд.
Объекты элементов управления создаются конструкторами соответствующих классов. Элемент управления может быть создан только в окне-контейнере (диалоге, контейнерном элементе управления, обрамляющем окне). Поэтому окно-контейнер должно быть определено в процессе создания элемента управления. Например, шаблон вызова процедуры создания нового объекта поля редактирования может выглядеть так:
Поле = editControl::new(ДиалогРодитель),
Для того чтобы создать подокно внутри окна, которое не является окном-контейнером (т.е. окна типа documentWindow, applicationWindow и пр.), вместо элемента управления следует использовать дочернее окно.
Пакет GUI поддерживает стандартные элементы управления (которые поставляются в пакете standardControl в папке standardControl) и общие элементы управления (common controls).
Стандартными элементами управления являются флажки, поля редактирования, групповые блоки, пиктограммы, списки, раскрываюшиеся списки, редактируемые списки, кнопки, переключатели, полосы горизонтальной и вертикальной прокрутки, надписи и специальные элементы управления (custom controls).
На данный момент в общие элементы управления входят dateControl, editorControl, listViewControl, imageControl, messageControl, tabControl, treeControl, treeViewControl. В дальнейшем будут добавляться другие общие элементы управления.
C помощью объектного подхода в GUI значительно легче создавать новые пользовательские элементы управления. При объявлении интерфейса нового элемента управления следует указать, что он поддерживает интерфейс control пакета GUI (и/или, возможно, некоторые другие интерфейсы). Это снабдит новый элемент управления объявлениями всех свойств (доменов, предикатов и констант), обычно необходимых элементам управления. При этом, если в имплементации класса этого элемента управления указать, что он является наследником какого-либо класса, интерфейс которого поддерживается данным интерфейсом, то класс нового элемента управления также будет иметь реализацию всех необходимых свойств.
Таким образом, новый пользовательский элемент управления будет так же соответствовать системе GUI, как любой из стандартных элементов управления пакета GUI. Останется только объявить и реализовать дополнительные свойства нового элемента управления. Более того, теперь будет легче соответствовать главной модели обработки событий пакета GUI. Нужно будет объявить домены для дополнительных приемников (listener) и ответчиков (responder), а также объявить и реализовать предикаты добавления (add) и установки (set) этих обработчиков событий для элементов управления.
События элементов управления активируют приемники и ответчики, добавленные (установленные) к объектам элементов управления, которые вызывают соответствующие реакции.
Пакет GUI поддерживает несколько типов элементов управления. Каждый тип имеет соответствующий интерфейс:
- checkButton (флажки),
- editControl (поля редактирования),
- groupBox (групповые блоки),
- iconControl (пиктограммы),
- listBox (списки),
- listButton (раскрывающиеся списки),
- listEdit (редактируемые списки),
- button (кнопки),
- radioButton (переключатели),
- scrollControl (полосы горизонтальной и вертикальной прокрутки)
- textControl (надписи).
Каждый элемент управления имеет предикаты, соответствующие тому, что он делает. Например, полосы прокрутки имеют предикаты задания и получения диапазона прокрутки, пропорции прокрутки и позиции бегунка.
Интерфейс containerControl представляет элемент управления, который может содержать другие элементы управления или контейнеры. То есть в него могут добавляться другие элементы управления. С другой стороны, он может быть сам добавлен в другие окна-контейнеры или контейнерные элементы управления (в качестве элемента управления). Это очень удобно, когда необходим составной элемент управления или поддиалог.
См. интерфейс и класс каждого элемента управления в справочной системе для получения более подробной информации.
Контейнеры GUI
Контейнер - это вид окна, в котором могут содержаться несколько элементов управления и/или другие вложенные контейнеры.
Обычно элементы управления не отображаются напрямую. Они добавляются в контейнер, а затем на экране отображается диалог или форма, содержащая эти элементы управления (с помощью предиката show()).
Диалоги
Объекты типа dialog содержат следующую информацию:
- размер диалога и его местоположение;
- система координат диалога;
- количество элементов управления в диалоге;
- порядок переходов между элементами управления по клавише табуляции;
- символические ускорители;
- предикат show(), который создает и визуализирует на экране все, что соответствует объектам GUI этого диалога.
Объекты типа dialog имеют предикаты для получения и настройки этих атрибутов, а также для добавления и удаления из них элементов управления. Поскольку dialog является наследником drawWindow, он поддерживает все предикаты управления курсором, рисованием и т.д.
Жизненный цикл диалога и его элементов управления можно описать следующим образом:
- Создание объекта диалога (dialog::new/1).
- Создание объектов элементов управления в диалоге (конструктор new элемента управления).
- Создание экранной визуализации диалога (window::show/0).
- В отображенных на экране диалогах можно создавать дополнительные элементы управления, которые могут быть визуализированы (с помощью предиката show элемента управления).
Следует отметить, что к отображенным модальным диалогам нельзя добавить новые элементы управления напрямую. Их нужно добавлять в обработчиках событий диалога (приемниках или ответчиках).
- Взаимодействие пользователя с экранным отображением диалога (вызов приемников и ответчиков событий элементов управления диалога).
- Визуализация элементов управления и диалога уничтожается.
- Никаких визуальных компонентов более не существует, но все экземпляры объектов (диалога и элементов управления) все еще существуют, следовательно, программное взаимодействие с этими объектами все еще возможно.
- Экземпляры объектов диалога и элементов управления освобождаются (явно) и собираются сборщиком мусора.
Диалоги могут быть модальными, немодальными и дочерними (разновидность немодальных). Модальный диалог принуждает пользователя завершить ввод в этом диалоге, прежде чем он сможет продолжить - использовать другие окна и диалоги приложения и т.п. С точки зрения программы модальные диалоги предупреждают (или приостанавливают) дальнейшую работу программы с момента запуска предиката show до момента, когда пользователь закончит и закроет диалог.
Экземпляр объекта диалога, создавшего модальный диалог, все еще живет после уничтожения экранного представления диалога. Это значит, что запросы к экземпляру объекта могут проводиться после выполнения предиката show, а, следовательно, после того, как пользователь завершил ввод. Модальные диалоги обычно имеют собственный заголовок и оформление и могут быть перемещаемы пользователем.
Немодальные диалоги не ограничивают пользователя в отношении того, какое окно или диалог использовать для ввода данных. Точно так же, работа программы продолжается после вызова метода show, т.е. пока диалог открыт. Для получения реакции на ввод пользователя в диалог, должны быть зарегистрированы приемники. Немодальные диалоги, так же как и модальные, обычно имеют собственный заголовок и оформление.
Дочерний диалог - это вид немодального диалога, ограниченного в перемещении родительским окном. Дочерние диалоги используются тогда, когда требуется управлять элементами управления за пределами обычных режимов диалогов.
Ниже приведен пример кода, который по умолчанию автоматически генерируется IDE для вновь созданного диалога. Для каждого диалога IDE генерирует файлы, содержащие объявления интерфейса и класса, а также имплементацию класса. В файле, содержащем декларацию класса, объявляется конструктор new(), который реализуется следующим образом:
inherits dialog ... clauses new(Parent):- dialog::new(Parent), generatedInitialize(). % This code is maintained by the IDE. Do not update it manually facts buttonok : button. buttoncancel : button. buttonhelp : button. predicates generatedInitialize : (). clauses generatedInitialize():- Font = vpi::fontCreateByName("MS Sans Serif", 8), setFont(Font), setText("New Dialog"), setRect(rct(50,40,290,160)), setType(wd_Modal), setDecoration(titlebar([closebutton()])), buttonok := button::newOk(This), buttonok:setText("&OK"), buttonok:setPosition(12, 102), setDefaultButton(buttonok), buttoncancel := button::newCancel(This), buttoncancel:setText("Cancel"), buttoncancel:setPosition(96, 102), buttonhelp := button::new(This), buttonhelp:setText("&Help"), buttonhelp:setPosition(180, 102). % end of automatic code
Здесь написано, во-первых, что имплементация данного класса наследует имплементацию класса dialog. В результате реализация данного класса наследует реализацию всех предикатов интерфейса dialog.
Во-вторых, определяется конструктор класса new(). Этот конструктор создает объект типа dialog с помощью вызова dialog::new(Parent), а затем вызывается предикат generatedInitialize(), который инициализирует создание кнопок диалога (по умолчанию), оформление диалога, указывает тип диалога, шрифт, заголовок диалога и т.д.
Заметим, что код предиката generatedInitialize() находится в разделе, который автоматически обслуживается IDE, так что программист не должен изменять код в нем. Этот раздел начинается со строчки "% This code is maintained by the IDE. Do not update it manually".
В этом разделе мы видим объявление и определение предиката generatedInitialize().
Отдельный предикат generatedInitialize() необходим, так как программисту может понадобиться добавить свой собственный код в конструктор new(), и этот код не должен очищаться при автоматическом обновлении кода.
Первое, что делает предикат generatedInitialize(), - это вызов предиката vpi::fontCreateByName, для того чтобы создать шрифт (Font), который будет установлен (с помощью window::setFont/1) в качестве шрифта для рисования текста в диалоге.
Предикат window::setText/1 определяет заголовок "New Dialog".
Предикат window::setType/1 специфицирует, что созданный диалог имеет модальный тип.
Предикат frameDecoration::setDecoration/1 указывает, что в заголовке диалога будет кнопка "Закрыть". (Заметим, что нельзя создать кнопки frameDecoration::minimizeButton и frameDecoration::maximizeButton без frameDecoration::closeButton.)
Затем мы добавляем в диалог кнопки OK, Cancel и Help.
Экземпляры этих кнопок создаются при помощи вызовов соответствующих конструкторов класса button (button::newOk/1, button::newCancel/1 и button::new/1). Конструкторы элементов управления предполагают наличие контейнера, в который они должны быть добавлены.
Здесь этот контейнер указывается с помощью переменной This. Переменная This указывает на объект типа dialog, который в свою очередь поддерживает container. Данный объект типа dialog создан с помощью предиката dialog::new(Parent), вызванного в конструкторе диалога new().
Обсудим код, который IDE генерирует для обработки элементов управления.
buttonok := button::newOk(This), buttonok:setText("&OK"), ...
Как видно из первой строки этого кода, для хранения созданных объектов IDE применяет факты-переменные. В данном случае используется факт-переменная buttonok для кнопки OK. Во второй строке кода можно увидеть, как IDE использует факт-переменную buttonok, для того чтобы обратиться к объекту, созданному конструктором newOk, который вызывается в предыдущей строке.
Заметим, что использование фактов-переменных - это стандартный способ, применяемый IDE для хранения созданных объектов компонентов GUI и обращения к ним.
Отметим, что создание объектов компонентов GUI (которое осуществляется с помощью конструкторов), а также управление их состояниями и свойствами отделены от создания соответствующих визуальных элементов и отображения их на экране. Визуальное представление диалогов или любых других компонентов GUI всегда осуществляется предикатами show. Все созданные объекты GUI - это просто данные, которые в конечном счете могут быть визуализированы вызовом предиката show соответствующего объекта.
Например, если создать объект этого диалога и визуализировать созданный объект предикатом show, то после компиляции и запуска кода можно будет увидеть:
Новый диалог, по умолчанию созданный IDE
Заметим, что этот диалог - модальный, поэтому после вызова предиката show программа не будет продолжать выполняться до тех пор, пока диалог не будет закрыт (т.е. пока предикат show не вернет управление). Только после возврата предиката show, будут выполнены предписания, следующие за show.
Формы
Формы - это объекты типа formWindow. Интерфейс formWindow поддерживает интерфейсы dialog и documentWindow. Форма позволяет:
- создавать внутри нее элементы управления с позициями, относящимися к ее клиентской области (прямоугольнику);
- управлять этими элементами управления способом, которым настроена навигация клавиатуры;
- рисовать;
- управлять кнопками;
- управлять символическими ускорителями;
- управлять меню и т.д.
Основные различия между диалогами и формами заключаются в следующем:
- Формы - это дочерние окна главного окна приложения (окна задач), поэтому перемещение форм ограничено его клиентской областью. Диалоги можно перемещать за пределами клиентской области окна задач.
- В режиме приложения MDI. Окна документов, которые соответствуют формам, созданным в приложении, будут представлены в виде списка в пункте меню Window окна задач. Так что они могут быть упорядочены мозаикой, каскадом и по пиктограммам.
Ниже приведены основные различия между формами и окнами документов:
- Формы могут иметь элементы управления, а окна документов нет.
- Окна форм имеют фон диалогов, а окна документов всегда следует закрашивать с помощью предиката paintResponder.
Размещение элементов управления в контейнерах
Обычно программист просто "рисует" диалог или окно в соответствующем диалоге или редакторе окон в стиле WYSIWYG ("что видишь, то и получаешь"), для достижения требуемого вида и режима работы элементов управления. Затем эксперт кода IDE автоматически генерирует весь необходимый код для правильного задания размера и положения элементов управления, порядка перемещения с помощью табуляции между элементами управления, группировки элементов управления, навигации между сгруппированными элементами управления и т.п.
Поэтому здесь мы обсудим только те свойства, которые требуют некоторого специального внимания программистов.
- Элементы управления в диалоге (или форме) могут быть сгруппированы, если они будут созданы как дочерние по отношению к контейнерному элементу управления, который, в свою очередь, должен быть дочерним по отношению к диалогу (форме).
- Каждый тип элементов управления по умолчанию имеет некоторый размер. Этот размер определяется константами вида:
standardWidth = 48. standardHeight = 12.
определенными в интерфейсах элементов управления. Отметим, что эти константы указываются в предположении, что все размеры задаются в базовых единицах диалога.
Базовые единицы диалога.
По умолчанию, размеры диалогов (форм) и элементов управления кратны размерам выбранного для диалога шрифта. По умолчанию им является системный шрифт, но он может быть изменен пользователем. Это означает, что координаты диалогов и элементов управления исчисляются в базовых единицах диалога (БЕД). Когда системный шрифт будет изменен, изменятся и размеры элементов управления, и в результате весь диалог будет масштабирован соответствующим образом.
Предикат vpi::getBaseUnits/2 возвращает размер в пикселях базовых единиц диалога по горизонтали (ширина) #DBU_Width и по вертикали (высота) #DBU_Height. Они рассчитываются как средние значения ширина/4 и высота/8 символов для системного шрифта. Эти значения должны быть использованы для позиционирования и размещения диалога.
Если в диалоге выбран шрифт, отличный от системного, то фактические базовые единицы диалога (#BU_Width и #BU_Height) отличаются от базовых единиц диалога, получаемых предикатом vpi::getBaseUnits/2. Значения #BU_Width и #BU_Height выводятся из средних значений ширины и высоты символов данного шрифта. Эти значения можно определить с помощью предиката vpi::winDialogRatio/2 следующим образом:
vpi::winDialogRatio(ДескрипторДиалога, Коэффициент), Коэффициент = pnt(Коэфф_Ширины, Коэфф_Высоты), BU_Width = DBU_Width * Коэфф_Ширины / 100, BU_Height = DBU_Height * Коэфф_Высоты / 100,
Программист может указать, что компоненты диалога должны измеряться в пикселях, а не в используемых по умолчанию базовых единицах диалога. Это можно сделать с помощью предиката dialog::setUnit/1 и его вызова в виде:
dialog::setUnit(dialog::pixelUnit),
Этот предикат должен вызываться до того, как предикат window::show/0 отобразит на экране графическое представление диалога.
Предикат dialog::getUnit возвращает единицу измерения в координатной системе, используемой в текущем объекте диалога. Это может быть dialog::dialogBaseUnit (для координат в базовых единицах диалога) или dialog::pixelUnit (для координат в пикселях).
Редакторы диалогов IDE всегда работают с базовыми единицами диалога. Соответственно эксперты кода IDE при генерации кода для отображения спроектированного диалога предполагают, что все элементы измеряются в базовых единицах диалога.
Предикаты dialog::getPosition/2, control::setSize/2 и т.п. получают и устанавливают размеры и позицию в такой координатной системе, которая используется в данный момент в диалоге. Сведения о ней можно получить с помощью предиката dialog::getUnit.
Обновление внешнего вида диалога (формы) может быть временно приостановлено, а затем возобновлено с помощью следующих предикатов:
dialog::suspendLayout/0 и dialog::resumeLayout/0.
Окно экрана
Окно экрана (рабочего стола) - это абстракция, которая представляет весь экран. Окно экрана всегда является родительским окном по отношению ко всем окнам задач. Окна верхнего уровня приложений SDI также могут иметь в качестве родителя окно экрана, что позволяет им перемещаться за пределами соответствующих окон задач. Такие окна верхнего уровня находятся в списке панели задач Windows, так что можно переключаться к этим окнам с помощью клавиши Alt+Tab.
Окно задач
Интерфейс applicationWindow относится к окну задач приложения (Task Window). Предикат show создает и отображает окно задач на экране. Окно задач - это главное окно для приложений GUI. Каждое GUI-приложение может иметь одно и только одно окно задач.
Инициализация и завершение приложения всегда связаны с окном задач. Инициализация должна производиться с помощью предиката window::showListener, очистка при завершении - с помощью предиката window::destroyListener окна задач. Завершение приложения наступает в результате применения к окну задач предиката window::destroy/0. В частности, это может произойти в ответ на выбор пользователем пункта меню File|Exit. Обычно это действие производит событие, в результате которого происходит вызов предиката window::destroy/0.
Окно задач также обрабатывает некоторые специальные события VPI, которые не получают другие окна. Например, события динамического обмена данными (DDE) VPI, используемые для общения с другими приложениями, также посылаются обработчику событий окна задач.
Окно задач не является контейнерным окном. Следовательно, оно не может содержать элементы управления.
Окно документа
В пакете GUI окно документа - это окно, которое имеет в качестве родителя либо окно экрана, либо окно задач. Окна документов часто содержат меню, их можно сворачивать в пиктограмму и разворачивать во весь экран, но, в отличие от форм, они не могут содержать элементы управления.
В режиме многодокументного интерфейса (MDI, Multiple Document Interface) Windows окно задач является контейнером для всех окон документов, созданных приложением, и для их пиктограмм. Именно окно задач содержит меню любого активного окна MDI. В режиме MDI окна документов создаются конструкторами класса documentWindow.
В режиме интерфейса с одним документом (SDI, Single Document Interface) любое окно документа может объявить окно экрана своим родителем. Такие окна верхнего уровня размещаются независимо от окна задач и могут иметь собственные меню.
Интерфейс documentWindow поддерживает интерфейсы drawWindow и frameDecoration. В окнах документов (или окнах верхнего уровня) рамка (т.е. граница) может иметь различное оформление. Окна документов могут быть свернуты, развернуты и восстановлены. В приложениях MDI окна документов могут быть упорядочены мозаикой или каскадом, пиктограммы всех свернутых окон документов также могут быть упорядочены.
Интерфейс frameDecoration предоставляет объявления доменов и предикатов, которые отвечают за свойства объектов границы и заголовка окна. Предикаты из этого списка определяют:
- имеет ли окно строку заголовка;
- какие кнопки заголовка имеет строка заголовка окна;
- какого рода границу имеет окно.
Дочерние окна
Если у окна документа должна быть строка состояния, то можно использовать дочернее окно (т.к. в окне документа нельзя создавать элементы управления).
Обычно дочерние окна контролируют часть экрана с помощью некоторых изображений. Дочерние окна создаются конструкторами класса childWindow. При уничтожении родительского окна сначала автоматически уничтожаются все дочерние окна и элементы управления, созданные в этом родительском окне, а затем само это окно.
Схема использования пакета GUI
Основная идея состоит в том, что вы выполняете следующее:
- Создаете обычный проект GUI.
- Объявляете контейнеры (диалоги или формы) с окном задач в качестве родителя.
- В контейнеры добавляете элементы управления.
- С помощью эксперта кода (Code Expert) добавляете события и реализуете обработчики событий для элементов управления.
- Визуализируете созданные графические (экранные) объекты GUI.
- Взаимодействуете с ними и предусматриваете реакции программы.
- Уничтожаете экранные элементы GUI (при этом созданные объекты все еще существуют и с ними можно взаимодействовать программно).
- Затем механизм Visual Prolog автоматически освободит неиспользуемые объекты элементов управления GUI и уничтожит их во время сборки мусора.
Следует отметить, что конструкторы классов пакета GUI создают объекты соответствующих компонентов GUI в памяти программы, но они не создают соответствующих экранных представлений этих компонентов. Для того чтобы создать экранное представление какого-либо компонента GUI, т.е. для того чтобы создать графическое представление объекта и отобразить его на экране, нужно вызвать предикат show этого объекта GUIComponentObject:show().
Для создания объектов экранных окон программисты должны использовать следующие конструкторы классов, наследуемых из window: applicationWindow, documentWindow, formWindow и childWindow.
Жизненный цикл окна можно описать следующим образом:
- Создание объекта окна (с помощью конструктора new).
- Инициализация свойств объекта окна, рисование, управление расположением, добавление приемников событий и т.д. (с помощью предикатов set*, draw*, add*, ...).
- Визуализация объекта окна (show).
- Взаимодействие пользователя с экранным отображением окна (вызов приемников событий).
- Экранное отображение окна уничтожается.
- Никаких визуальных компонентов более не существует, но экземпляры объектов все еще живы; следовательно, программное взаимодействие с экземпляром объекта окна возможно.
- Экземпляр объекта окна освобождается (явно) и собирается сборщиком мусора.
Заметим, что разумно создавать объекты типа window, которые вмещают указанные окна пакета VPI, с помощью конструктора window::wrapVPIWin/1 класса window.
Обработка событий
В отличие от процедурных (консольных) программ, которые выполняются последовательно от начала до конца, оконные приложения GUI асинхронны. Программист не может определить порядок, в котором пользователь будет активировать команды меню или элементы управления. Система GUI активно задействована в планировании вычислительного процесса приложений.
Когда пользователь делает что-нибудь (нажимает кнопку, двигает мышью, щелкает на иконку и т.д.), аппаратный интерфейс генерирует событие, и система GUI посылает это событие его настоящему владельцу, обычно окну, активному в данный момент (обладающему фокусом). Эти события могут прибывать асинхронно, и именно они определяют настоящую последовательность действий приложения. Соответственно, такие программы называются приложениями, управляемыми событиями.
В приложениях, управляемых событиями, вместо последовательного исполнения от начала до конца система GUI использует "основной цикл окна", ожидая пользовательского ввода. Когда пользователь дотрагивается до любого экранного компонента приложения, оконная система ловит соответствующее событие и передает его заранее добавленному/зарегистрированному обработчику события. Это принято называть обратным вызовом, и обработчик события - это предикат обратного вызова, потому что оконная система вызывает его, когда происходит событие. Например, если кнопка имеет надпись Help (Справка), то соответствующий обработчик события должен активировать некоторую справочную систему. Обработка события нажатия кнопки означает обнаружение факта нажатия кнопки и запуск соответствующей операции.
Модель событий - это структура, которая конвертирует взаимодействие GUI (нажатие кнопки, щелчок мышью) в вызов предиката, который его обрабатывает. Модель событий описывает, как связать произвольные действия в оконной системе с некоторым заранее определенным набором событий, которые, в свою очередь, активируют соответствующие обработчики событий (предикаты обработки событий, определенные в вашем приложении), обрабатывающие исходные действия системы GUI.
Система GUI не может просто вызвать соответствующие предикаты обработки событий в вашем приложении! Потому что система GUI ничего не знает о вашем коде, пока ей не будет сказано запустить его. Системе GUI абсолютно неизвестно, какие компоненты GUI будет использовать ваше приложение и какие обработчики событий есть в вашем приложении, для того чтобы обработать события. Они должны быть как-то определены.
Модели событий
В версиях 6.x и 7.x Visual Prolog используются две модели событий.
Модель событий VPI. Эта модель событий использует набор событий (таких как vpiDomains::e_Create, vpiDomains::e_Control, vpiDomains::e_Char, vpiDomains::e_MouseUp, vpiDomains::e_Update и т.д.). Все события, относящиеся к какому-нибудь окну, посылаются одному и тому же предикату обработки событий (обработчику событий окна), ассоциированному с этим окном. Здесь мы не будем обсуждать эту модель событий; детальное описание этой модели событий можно найти в справке пакета VPI.
Модель событий пакета GUI. Отдельные специализированные приемники (listeners) и/или ответчики (responders) регистрируются индивидуально для каждого вида события в каждом экземпляре объекта компонента GUI.
Модель событий GUI можно назвать "основанной на регистрации" обработкой событий. Вместо того чтобы регистрировать один обработчик событий для окна, код вашей GUI программы говорит оконной системе:
"пошли каждый вид событий в компоненте GUI отдельному принимающему предикату, специально зарегистрированному для этого вида событий в этом экземпляре объекта компонента GUI".
Это гораздо более понятная модель. Ваш диалог имеет несколько элементов управления, которые могут вызывать (или генерировать) события (нажатия кнопок, щелчки мышью, пр.), поэтому ваш код должен зарегистрировать несколько приемников (listeners) и/или ответчиков (responders). Каждый из них должен обрабатывать определенный вид событий, для которых он предназначен.
В соответствии с моделью пакета GUI, каждая компонента GUI (диалог, элемент управления, окно и т.д.) представляется как отдельный объект. Каждый такой объект может запускать некоторые события. Если вы хотите обрабатывать определенного рода события объекта GUI, то вы должны добавить для этого объекта отдельные обработчики - приемники (listeners) обратного вызова и/или ответчики (responders), для каждого рода событий, которые вам нужно обрабатывать. Такой зарегистрированный приемник (или ответчик) будет вызван системой GUI, когда объект запустит событие соответствующего типа.
Такие приемники очень похожи на рабочих, сидящих в своих офисах и ждущих телефонных звонков. У каждого рабочего есть свой собственный телефон и своя единственная функция. Когда один из телефонов звонит, соответствующий рабочий отвечает на звонок и выполняет свои действия. Например, диспетчер железнодорожной станции может позвонить стрелочнику и приказать "переведи стрелку на запасной путь". Тогда стрелочник переведет стрелку с основного пути на запасной и ответит "Готово!".
Рассмотрим более программистский пример: выполним какое-нибудь действие при создании окна. Для этого мы можем написать некоторый предикат winShowListener, который должен быть объявлен так:
predicates winShowListener : window::showListener.
Предложения для этого предиката могут осуществлять требуемые действия. Вы должны добавить/зарегистрировать этот предикат к объекту окна. Вы можете сделать это с помощью вызова предиката window::addShowListener/1 следующим образом:
... ДочернееОкно = childWindow::new(ОкноРодитель), ДочернееОкно:setClientRect(rct(50,100,200,150)), ДочернееОкно:addShowListener(winShowListener), ДочернееОкно:show(), ...
Согласно этому коду, когда экранное представление окна ДочернееОкно (соответствующее объекту ДочернееОкно) будет создано с помощью предиката ДочернееОкно:show() (но прежде, чем окно отобразится на экране), система GUI вызовет зарегистрированный приемник winShowListener, который обработает событие создания окна ДочернееОкно.
Два типа обработчиков событий
В пакете GUI имеются два типа обработчиков событий, которые обрабатываются двумя разными способами.
Некоторые события (такие как создание окна, получение окном фокуса, перемещение окна, деактивация приложения и т.п.) всегда обрабатываются системой GUI одним и тем же стандартным способом. Эта обработка не зависит от действий, реализованных в обработчике, который ловит и обрабатывает такие события и который определен пользователем.
О таких событиях мы можем сказать: "Когда происходит событие такого типа, оно активирует соответствующий обработчик события (зарегистрированный для этого события), и запускается обработка (этим обработчиком) пойманного события. После того, как выполнение обработчика события завершается, он возвращает управление, и тогда система GUI выполняет всегда одну и ту же стандартную обработку этого события. Эта стандартная обработка события всегда будет одинаковой независимо от результатов запуска обработчика события."
События второго типа (такие как нажатие клавиши; событие, означающее, что сеанс Windows должен быть завершен; активация нажатия кнопки и т.д.) обрабатываются системой GUI разными способами. Эти способы зависят от результата, возвращаемого запущенным обработчиком событий. Поэтому обработчики таких событий ДОЛЖНЫ вернуть какой-то ответ. Обычно этот ответ может быть возвращен одним аргументом. Поэтому такие предикаты обработчиков событий, возвращающие один аргумент, обычно могут быть представлены в виде функций.
О таких обработчиках событий мы можем сказать: "Когда происходит событие второго типа, оно активирует соответствующий обработчик. Этот обработчик зарегистрирован для данного события или он по умолчанию является обработчиком для этого типа событий (он всегда должен существовать). После того как выполнение обработчика событий завершается, он возвращает некоторый ответ системе GUI. Тогда система GUI обрабатывает это событие способом, зависящим от ответа, полученного от запущенного обработчика событий."
Таким образом, можно сказать примерно следующее:
- Обработчики событий первого типа просто принимают свои события. Система GUI не ждет никакого ответа от таких обработчиков событий и всегда выполняет одну и ту же стандартную обработку для каждого типа событий, обрабатываемого такими обработчиками. Поэтому можно вообще не создавать таких обработчиков (обработчиков событий такого типа по умолчанию даже не существует). Так что такие обработчики событий можно назвать приемниками событий (event listeners).
- С другой стороны, обработчики событий второго типа не только принимают свои события, но также предоставляют ответы (выходные параметры) системе GUI. Система GUI предусматривает различную обработку вызванного события по умолчанию, в зависимости от полученного ответа. Поэтому такие обработчики событий можно назвать ответчиками событий (event responders). Поскольку системе GUI нужен ответ от таких ответчиков событий, ответчики событий всегда должны существовать (GUI предоставляет ответчики событий по умолчанию для таких событий).
Итак, в пакете GUI имеются два рода обработчиков событий: приемники событий и ответчики событий. Программист может определить и зарегистрировать (или нет) приемники событий для некоторых событий; оконная система GUI не требует регистрации приемников событий. С другой стороны, ответчики событий всегда должны существовать. Если программист не определяет и не регистрирует ответчик события явно, то пакет GUI по умолчанию предоставляет некоторый ответчик для этого события.
Приемники событий
Приемник события (listener) - это предикат обратного вызова, который может быть назначен объекту компонента GUI, для того чтобы принимать и обрабатывать посылаемые этому объекту GUI события определенного рода.
Типы приемников событий
Основные приемники событий определены в интерфейсах window и drawWindow. Заметим, что во всех таких приемниках событий первый аргумент используется для передачи приемнику объекта типа window, для которого зарегистрирован этот приемник.
В пакете GUI также имеются приемники событий, которые объявляются только для некоторых специфических компонентов GUI. Речь идет о следущем:
- Флажки, списки, кнопки и переключатели имеют приемники активации:
- checkButton::activatedListener,
- checkButton::stateChangedListener,
- listControl::activatedListener,
- button::activatedListener,
- radioButton::activatedListener,
- radioButton::stateChangedListener.
- Поля редактирования обладают приемниками изменения: editControl::modifiedListener.
- Раскрывающиеся списки и редактируемые списки имеют приемники событий для открытия и закрытия выпадающих списков:
- listControl::closeUpListener,
- listControl::dropDownListener.
- Раскрывающиеся списки, списки и редактируемые списки имеют приемники событий для изменения выбора: listControl::selectionChangedListener.
Как реализовать приемник события
Рассмотренные выше приемники событий могут быть зарегистрированы для большинства компонентов GUI: окон, диалогов, элементов управления и т.д. Каждый приемник события требует наличия следующих фрагментов кода.
Сначала нужно объявить предикат приемника события для желаемого типа события. Это можно сделать, например, следующим образом:
predicates dialogShowListener : window::showListener.
Здесь мы объявляем приемник события, который будет использоваться как приемник создания диалога. Теперь мы можем определить предложения для этого приемника события. Например так:
clauses dialogShowListener(_, _) :- Поле = editControl::new(This), Поле:setPosition(10,4), Поле:setSize(60,12), Поле:show().
Здесь вы должны помнить, что домен предиката window::showListener объявлен как процедура:
showListener = ( window Source, integer CreationData ) procedure (i,i).
После того, как вы определили приемники событий, вы можете зарегистрировать их в качестве приемников событий объектов компонентов GUI. Это можно сделать с помощью предикатов add_EventType_Listener. Здесь _EventType_ является сокращением для событий, перечисленных выше, в разделе Типы обработчиков событий.
Например, вы можете зарегистрировать описанный приемник dialogShowListener в качестве приемника события создания объекта диалога следующим образом:
Диалог = dialog::new(ОкноРодитель), Диалог:setRect(rct(100,100,194,190)), Диалог:setText("Модальный диалог"), Диалог:addShowListener(dialogShowListener), Диалог:show(),
Здесь мы используем предикат Диалог:addShowListener(dialogShowListener), для того чтобы добавить предикат dialogShowListener в качестве приемника события создания объекта диалога Диалог.
После того, как вы скомпилируете и запустите этот код, в результате вызова предиката DialogObj:show() будет создан и отображен на экране диалог, соответствующий объекту Диалог.
Заметим, что для модального диалога предикат Диалог:show() не выполнится, пока отображенный модальный диалог не будет закрыт. Так что после вызова предиката Диалог:show() вы не сможете добавить ни новые элементы управления, ни чего-либо еще в отображенный диалог. Однако в процессе обработки события создания для экранного представления диалога Диалог, оконная система вызовет зарегистрированный предикат обратного вызова dialogShowListener, который создаст поле редактирования Поле, и это поле будет помещено в диалог Диалог и появится в его экранном представлении.
Зарегистрированный (для объекта Диалог) предикат приемника создания (dialogShowListener) вызывается после того, как экранное представление диалога будет создано, но оно еще не будет видимо на экране. Это самое время, чтобы выполнить разовую хозяйственную работу (такую как заполнение базы данных), связанную с диалогом. Предикат, создающий экранное представление диалога (Диалог:show()), не выполнится (и диалог не появится на экране) до тех пор, пока не закончит выполнение код приемника dialogShowListener.
Заметим, что для объекта GUI можно зарегистрировать любое количество приемников событий для любого специального рода событий. Когда такое событие происходит, все зарегистрированные приемники событий будут вызваны в том порядке, в котором они были добавлены к объекту. Приемник события, однажды добавленный к объекту, не может быть удален; он будет существовать, пока не будет удален весь объект.
Ответчики событий
Как уже было сказано, только программист решает, регистрировать ли ему приемники событий. В случае если программист для некоторого объекта не регистрирует приемник событий некоторого рода, система GUI по умолчанию выполнит обработку событий этого типа. Чтобы выполнить эту обработку, системе GUI не нужен никакой ответ от приемника события. Вот почему такой тип обработчиков событий может быть назван "приемником". Потому что: "Они принимают свои события и могут реагировать на них, но не возвращают системе GUI никакого ответа. Система GUI по умолчанию осуществляет одну и ту же обработку таких событий, независимо от результата выполнения приемника такого события."
Однако некоторые типы событий система GUI обрабатывает по-разному, в зависимости от ответа, который может быть получен от вызванного обработчика события. Поэтому такие обработчики событий называются ответчиками событий (responders).
Новый ответчик событий можно установить следующим образом.
Где-нибудь в своем коде объявите новый ответчик событий, например, в виде:
predicates newActivatedResponder : button::activatedResponder.
Затем определите его предложения:
clauses newActivatedResponder(_PB) = button::noAction():- write("Я новый ответчик активации").
После этого вы можете установить этот ответчик для любого объекта GUI типа button. Например, вы можете написать:
% создать диалог Диалог = dialog::new(ОкноРодитель), ... % Кнопка Cancel КнопкаОтмены = button::newCancel(Диалог), ... СтарыйОтветчикАктивации = КнопкаОтмены:setActivatedResponder(newActivatedResponder),
Теперь вы можете скомпилировать и запустить этот код. Если вы нажмете кнопку Cancel, соответствующую объекту КнопкаОтмены, то она не закроет диалог Диалог, а выведет:
"Я новый ответчик активации".
В этом примере с помощью предиката button::setActivatedResponder устанавливается новый ответчик события активации кнопки newActivatedResponder для объекта GUI CancelButton. Заметим, что предикат setActivatedResponder возвращает ответчик события активации СтарыйОтветчикАктивации, используемый ранее по умолчанию для объекта CancelButton. Если вы сохраните его где-нибудь, то сможете восстановить старый ответчик СтарыйОтветчикАктивации для объекта CancelButton или установить его для любого другого объекта кнопки GUI в вашей программе.
Ответчик по умолчанию можно восстановить также с помощью предиката pushButton::resetActivatedResponder/0.
Расширения событий
Иногда возникает необходимость переопределить поведение, по умолчанию определенное для события, у которого нет ответчика.
Для этого можно использовать идею расширений. Следует отметить, что IDE не генерирует код для расширений. Предполагается, что они кодируются вручную.
Ниже приведен пример определения расширения.
implement myWindow inherits documentWindow supports documentWindowExtension delegate interface documentWindowExtension to defaultExtension facts defaultExtension : documentWindowExtension. clauses new(Parent):- defaultExtension := getDefaultDocumentWindowExtension(), documentWindow::newExtended(This,Parent). clauses onFocus(Source):- % Сделайте что-нибудь другое % ... % Вызовите обработку по умолчанию. Необязательно, но желательно defaultExtension:onFocus(Source). end implement myWindow
Здесь расширение несет ответственность за вызов поведения, определенного по умолчанию.
Операции рисования
Интерфейс drawWindow содержит предикаты для изменения настроек рисования, а также предикаты для обработки приемников событий клавиатуры и мыши. В случае если элемент управления (такой как кнопка) не предполагает рисование, он не поддерживает интерфейс drawWindow.
В оконном интерфейсе объявлены предикаты GUI, которые рисуют текст, пиксели, разнообразные геометрические фигуры и пиктограммы. Эти предикаты начинаются с префикса draw. Например, windowGDI::drawArc/5, windowGDI::drawFloodFill/2 и т.д. Объект windowGDI запрашивается ответчиком paintResponder для объектов типа drawWindow.
Интерфейс windowGDI содержит предикаты для рисования. Объект интерфейса windowGDI подается ответчику paintResponder.
Операции рисования используют текущие настройки рисования для окна, к которому они относятся в качестве инструментов для рисования. Инструментами для рисования являются перо, кисть, режим рисования, шрифт, цвет пера, цвет фона и режим заливки фона.
Инструменты для рисования
Настройки текущих инструментов для рисования для объекта типа window можно поместить в единственную переменную. С помощью этой переменной впоследствии настройки можно восстановить. Это можно сделать с помощью предикатов getDrawTools и setDrawTools следующим образом:
СтарыеИнструменты = ОбъектGdi:getDrawTools(), % Измените настройки рисования и рисуйте с измененными настройками ОбъектGdi:setDrawTools( СтарыеИнструменты ), % восстановите сохраненные настройки инструментов рисования
Компоненты инструментов для рисования определены в домене vpiDomains::drawtools:
drawtools = draw_tools ( pen Перо, brush Кисть, drawMode РежимРисования, font Шрифт, color ЦветТекста, color ЦветФона, bk_mode РежимЗаливкиФонаДляТекста ). % Смотрите Рисование Текста для более подробной информации
Все домены и константы, используемые для определения инструментов для рисования, объявлены в классе vpiDomains.
Обычно используются предикаты для изменения не всех инструментов, а только необходимых.
Предикаты, домены и константы, используемые для управления собственными инструментами для рисования, описаны ниже.
Перья
Пером рисуются линии и периметры фигур. Внешний вид линии - ее толщину, стиль и цвет можно изменять. Для этого перед тем, как будет выполняться рисование, необходимо настроить соответствующие атрибуты пера.
Домен перьев имеет вид:
pen = pen ( penwidth ТолщинаПераВПикселях, penStyle СтильПера, color ЦветПера ). penWidth = integer. penStyle = integer. color = integer.
Стиль пера может быть указан с помощью констант vpiDomains::ps_*** домена vpiDomains::penStyle.
Предикат windowGDI::setPen изменяет перо, которое используется для рисования в данном окне. Следующий код определяет перо для рисования сплошных красных линий толщиной в два пикселя:
ТолщинаПера = 2, Перо = vpiDomains::pen(ТолщинаПера, vpiDomains::ps_Solid, vpiDomains::color_Red), ОбъектТипаWindow:setPen(Перо),
Кисти
Фигуры заполняются с помощью кисти. Способы заливки определяются атрибутами кисти - цветом и стилем узора-заполнителя.
brush = brush(patStyle, color). patStyle = integer.
Узоры-заполнители могут быть указаны с помощью констант vpiDomains::pat_*** домена vpiDomains::patStyle.
Предикат windowGDI::setBrush изменяет для данного окна текущую кисть.
Режимы рисования
Режим рисования определяет способ, в соответствии с которым будут сочетаться пиксели, которые отображаются на экране, с уже нарисованными пикселями. Параметры режима рисования указываются с помощью констант домена vpiDomains::drawMode. Константами домена vpiDomains::drawMode являются константы vpiDomains::dm_***.
Обычным режимом рисования является режим vpiDomains::dm_CopyPen. Он просто присваивает пикселям новые значения. Режимы vpiDomains::dm_XorPen и vpiDomains::dm_Not можно использовать для рисования "резиновых нитей", при этом эффект растяжения достигается с помощью рисования прямоугольника и затирания его последующим рисованием.
Предикат windowGDI::setDrawMode определяет новый режим рисования:
ОбъектТипаWindow:setDrawMode(vpiDomains::dm_CopyPen),
Управление цветом
В пакете GUI применяется стандартная цветовая модель RGB, которая позволяет использовать 16 миллионов цветов. Вы составляете желаемый цвет из значений, указывая необходимые компоненты красного (от 0 до 255), зеленого (от 0 до 255) и синего (от 0 до 255) цвета.
В противоположность субтрактивному закрашиванию, которое так называется потому, что каждый добавленный цвет перекрывает цвет под ним, закрашивание при помощи сочетания интенсивности цветов называется аддитивным.
При аддитивном закрашивании цвет каждого пикселя на экране определяется добавлением значения интенсивности для каждого из основных цветов: красного, зеленого и синего. Например, если минимальная цветовая интенсивность на данном экране определена как 0, а максимальная - как 255, то белый пиксель будет обозначаться RGB-тройкой (255, 255, 255), черному пикселю будет соответствовать RGB-тройка (0, 0, 0), а голубому - RGB-тройка (0, 255, 255).
Сравним это со схемой рисования, в которой смешение трех нулевых значений цветов (0,0,0) создает черный пиксель, а смешение трех максимальных значений цветов (255,255,255) создает нечто похожее на белый пиксель.
Качество вашего видеоадаптера и принтера, возможно, не позволит вам отобразить все многообразие цвета. Черно-белый принтер производит печать в черном и белом цветах. Однако основное программное обеспечение GUI предусматривает богатые цветовые отображения и цветовые композиции, так что если вы попытаетесь нарисовать 2000 прямоугольников уникального цвета, GUI сумеет скомпоновать цвета и пиксели в этих прямоугольниках так, чтобы отобразить 2000 приблизительно различных цветов. В черно-белых устройствах оттенки серого достигаются посредством печати узоров пикселей, используемых для представления цветов.
В пакете GUI цвета представляет домен vpiDomains::color. Некоторые часто используемые цвета можно указать при помощи констант vpiDomains::color_***.
Если вы хотите, чтобы ваше приложение выглядело хорошо на черно-белом оборудовании, используйте цвета vpiDomains::color_Black, vpiDomains::color_DkGray, vpiDomains::color_Gray, vpiDomains::color_LtGray и vpiDomains::color_White. Для проверки, поддерживают ли устройства вывода данный цвет, имеется атрибут vpiDomains::attr_have_color, который можно использовать в предикате vpi::getAttrVal. В противном случае убедитесь, что контраст между цветами достаточно велик, для того чтобы два смежных, но различных цвета не слились в один и тот же серый цвет.
Для создания нужного цвета с помощью основных цветов - красного, зеленого и синего можно использовать предикат vpi::composeRGB. Яркость каждого основного цвета определяется как целое число, при этом значимы только 8 младших битов, что дает диапазон от 0 до 255.
Общий диалог vpiCommonDialogs::getColor позволяет пользователю выбрать любой из возможных цветов.
Получить цвет указанного пикселя в текущем окне можно с помощью предиката windowGDI::getPixelColor.
При выборе цвета для фона окон, приложение должно использовать настройки, выбранные в цветовой схеме Windows. Для этой цели используйте предикат window::getAttrVal с vpiDomains::attr_color_window, который вернет цвет фона области текущего окна и другие атрибуты цветовой схемы Windows vpiDomains::attr_color_***.
Рисование фигур
Ниже приведен обзор доступных операций рисования.
Для отображения битовых карт (bitmaps) можно использовать предикат windowGDI::pictDraw.
Предикат windowGDI::drawPixel/2 закрашивает заданный пиксель заданным цветом.
Обратную операцию выполняет предикат windowGDI::getPixelColor. Он возвращает цвет пикселя, находящегося в логических координатах pnt(X, Y) в текущем окне.
Для закрашивания области при помощи текущей кисти используется предикат windowGDI::drawFloodFill/2.
Текущая кисть определена последним вызовом предиката windowGDI::setBrush/1 для этого окна.
Закрашиваемая область указывается с помощью начальной точки StartPoint и ограничивается при помощи заданного цвета BoundaryColor.
Предикат windowGDI::drawIcon/3 запрашивает пиктограмму с указанным идентификатором ресурса ResourceIdentifier из сегмента ресурсов активного приложения и отображает ее в текущем окне.
Левый верхний угол пиктограммы помещается в точке с логическими координатами X, Y.
Рисование незакрашенных фигур
Следующие три предиката рисуют фигуры при помощи текущего пера, определенного последним вызовом предиката windowGDI::setPen/1.
Предикат windowGDI::drawLine/2 рисует линию, которая начинается в точке StartPoint и заканчивается в точке EndPoint.
Предикат windowGDI::drawPolyline/1 рисует ломаную с вершинами в точках, указанных в списке PointList.
Предикат windowGDI::drawArc/5 рисует эллиптическую дугу.
Рисование закрашенных фигур
Границы фигур рисуются текущим пером. Нарисованные фигуры затем автоматически заполняются текущей кистью.
Предикат windowGDI::drawEllipse/1 рисует овал (эллипс). Центр эллипса - это центр прямоугольника BoundRectangle, в который вписан эллипс.
Предикат windowGDI::drawPie/5 рисует замкнутую эллиптическую дугу. Эллипс, вписанный в заданный прямоугольник BoundRectangle, определяет кривую сектора. Кривая начинается в точке, где эллипс пересекает первый радиус (проходящий через стартовую точку Start) и продолжается в направлении против часовой стрелки до точки, где эллипс пересекает второй радиус (проходящий через последнюю точку Stop).
Предикат windowGDI::drawPolygon/1 рисует замкнутый многоугольник. Последняя точка из списка PointList соединяется системой с первой точкой.
Предикат windowGDI::drawRect/1 рисует заданный прямоугольник Rectangle.
Предикат windowGDI::drawRoundRect/3 рисует прямоугольник со скругленными углами.
Рисование текста
Предикат windowGDI::drawText/3 рисует в текущем окне заданную строку String. Предикат windowGDI::drawText/4 рисует первые DrawLength символов входной строки String. Значения XStart и YStart определяют логические координаты левого верхнего угла первого символа строки.
Функция windowGDI::drawTextInRect вписывает первые DrawLength символов строки String в ограничивающий прямоугольник Rectangle, используя текущий шрифт. Этот предикат может отформатировать текст в соответствии с заданным списком Flags параметров vpiDomains::dtext_***.
Часто размер представления текстовой строки необходимо знать заранее. Предикат windowGDI::getTextExtent/4 возвращает ширину Width и высоту Heigth прямоугольника, который вместит первые Length символов заданной строки String, если они будут нарисованы в нем шрифтом, приписанным текущему объекту drawWindow.
Строка = "Это тест", ОбъектGDI:getTextExtent(Строка, -1, Ширина, Высота),
Предикат windowGDI::setForeColor/1 определяет цвет Color в качестве текущего цвета для рисования текста, а предикат windowGDI::setBackColor/1 определяет цвет фона под текстом в текущем объекте window.
Стили фона в операциях рисования текста
Цвет фона под текстом, который выводится с помощью предикатов windowGDI::drawText/3 и windowGDI::drawText/4, определяется последним параметром РежимЗаливкиФонаДляТекста в структуре инструментов для рисования vpiDomains::bk_mode. (См. Инструменты для рисования.)
Для стиля фона vpiDomains::bk_Opaque сначала закрашивается прямоугольник (текущим цветом, определенным последним вызовом предиката windowGDI::setBackColor/1), а затем выводится строка. При стиле фона vpiDomains::bk_Transparent строка выводится на существующем фоне.
Текущий стиль фона для рисования текста можно изменить с помощью предиката windowGDI::setBackMode/1.
% Нарисовать Белый Текст на Желтом фоне ОбъектGdi:setForeColor(vpiDomains::color_White), ОбъектGdi:setBackColor(vpiDomains::color_Yellow), ОбъектGdi:setBackMode(vpiDomains::bk_Opaque), Текст = "Это некоторый текст", X = 20, Y = 20, ОбъектGdi:drawText(X, Y, Текст),
Управление шрифтом
Каждое окно имеет шрифт, который используется, в частности, для рисования текста. Этот шрифт можно получить с помощью предиката window::getFont и изменить с помощью предиката window::setFont/1.
В пакете GUI шрифты принадлежат домену vpiDomains::font, который объявлен как домен ::binary. Это значит, что шрифты можно хранить даже во внешних файлах, чтобы использовать их позже. Настройки шрифта нельзя увидеть напрямую, но предикат vpi::fontGetAttrs может запросить наиболее важную информацию.
С помощью стандартного диалога vpiCommonDialogs::getFont пользователь может выбрать необходимый шрифт. Шрифт также можно создать с помощью предиката vpi::fontCreate или vpi::fontCreateByName. Для этого нужно указать семейство шрифта (константа vpiDomains::ff_*** домена vpiDomains::fontFamily), стиль шрифта (список констант vpiDomains::fs_*** домена vpiDomains::fontStyle) и размер шрифта.
Шрифт, созданный с помощью этих предикатов, дает наилучшее приближение для запрошенных характеристик, которое может сделать текущая версия Windows. Хотя в некоторых случаях его размер может оказаться неожиданным. Размер полученного шрифта обычно не превышает требуемый размер, однако и такое может произойти.
Стиль шрифта и размер шрифта можно изменить с помощью предиката vpi::fontSetAttrs.
Метрика размера текущего шрифта может быть получена с помощью предиката vpi::winGetFontMetrics/4.
Системы координат
В логической системе координат, по умолчанию используемой в пакете GUI, ось X направлена слева направо, а ось Y сверху вниз. По умолчанию логическими единицами являются элементы экранного изображения (пиксели). Начало координат - точка (0,0) - это пиксель, который находится в левом верхнем углу пространства представления. Эта точка располагается ровно в левом верхнем углу ограничивающего прямоугольника. Можно также работать с другими логическими единицами и системами координат.
При создании, перемещении или изменении размеров дочернего окна используются координаты относительно родительского окна (родительские координаты). При манипуляциях с окнами задач и диалогами - координаты рассчитываются относительны экрана (экранные координаты).
При рисовании и выполнении аналогичных операций используются координаты относительно клиентской области (прямоугольника) окна (клиентские координаты).
Иногда необходимо преобразовать координаты одного окна в координаты другого окна. Для этого имеются предикаты window::mapPointTo и window::mapPointsTo, которые могут преобразовать координаты относительно текущего окна в координаты относительно другого объекта window.
Прямоугольники всегда определяются с помощью структуры rct(Лево, Верх, Право, Низ).
Важное замечание. Пакет GUI НЕ определяет прямоугольники в виде (X, Y, Ширина, Высота).
Обычно координаты выражаются в пикселях, что удобно для работы с редакторами, меню, деревьями, панелями инструментов и т.д. Разрешение экрана обычно представляется в виде числа пикселей по направлениям X и Y (например, 800 x 600 или 1024 x 768). Более правильным было бы выражать его в виде количества пикселей на сантиметр или дюйм. "Размер точки" - это расстояние между центрами соседних физических предсталений пикселей, что, разумеется, есть величина, обратная разрешению. Экраны с более высоким разрешением имеют большее количество пикселей, позволяя в одно и то же время отразить на экране больше информации. Удвоение разрешающей способности обеспечивает представление в 4 раза большего количества информации без потери качества. Но каждый элемент становится в 4 раза меньше, что вызывает необходимость более близкого просмотра или увеличения области изображения в 4 раза (дважды относительно диагонали активного пространства).
Отображение, масштабирование, панорамирование и наезд
Все эти слова обозначают одну и ту же основную операцию - отображение. Отображение позволяет приложению изменять логические координаты и системы масштабирования, которые конвертируются и масштабируются оконной системой GUI к действительным физическим координатам окна, делая таким образом приложение устройство-независимым.
Как только для окна устанавливается режим отображения, приложение должно давать и получать логические координаты вместо физических координат фактического устройства. Например, если логическая координата X изменяется от 10000 до 20000, а логическая координата Y изменяется от 10000 до -10000, то отображение должно преобразовать эти диапазоны в реальные, которые измеряются в фактических физических пикселях. Например, фактически диапазоны будут следующие: для X от 0 до 349 и для Y от 0 до 249.
Предикат drawWindow::setMapMode/1 изменяет режим отображения окна. Возможные режимы отображения представлены константами vpiDomains::mm_*** домена vpiDomains::mapmode.
По умолчанию всегда установлен режим vpiDomains::mm_Text, при котором координаты соответствуют фактическим пикселям на экране или принтере. Режим vpiDomains::mm_Text зависит от устройств. Остальные режимы от них не зависят. Режимы отображения Metric, English и Twips позволяют приложению работать с действительными физическими размерами, что особенно удобно для печати. Режим vpiDomains::mm_Arbitrary позволяет преобразовать любой диапазон логических координат в фактический диапазон координат в окне.
Предикат drawWindow::setMapScale/4 может быть использован для настройки в логических координатах любого масштаба.
При режиме vpiDomains::mm_Arbitrary, для координат X и Y выполняются следующие преобразования:
ФизКоорд = ФизОсн + (ЛогКоорд - ЛогОсн) * ФизРасш / ЛогРасш
Здесь ЛогКоорд - это координаты в логических единицах, а ФизКоорд - результирующие координаты в физических единицах. С помощью изменений только аргумента ФизОсн можно реализовать панорамирование (перемещение начала системы координат). Изменяя параметры ФизРасш и ЛогРасш, можно масштабировать изображение. Использование одного масштаба на обеих осях дает изотропное масштабирование, разных - анизотропное.
Предикаты drawWindow::convertDPtoLP и drawWindow::convertLPtoDP можно использовать для преобразований координат устройства (экранные пиксели, точки принтера или факса) в логические координаты и обратно.
Это удобно, потому что координаты в событиях мыши, обновления и др. всегда задаются в пикселях.