Руководство разработчика приложений Zune
Zune является объектно-ориентированным набором для разработки приложений с
графическим интерфейсом пользователя (GUI).
Это практически полный аналог MUI (Magic User Interface), как на уровне API, так
и на уровне Look&Feel, самого популярного на Amiga shareware-интерфейса Стефана Штунтца.
Таким образом, разработчики знающие MUI, почувствуют себя здесь "как дома",
а остальные смогут изучить понятия и особенности, общие для обоих средств.
Постулируется, что:
- Программист может затратить намного меньше времени при проектировании
интерфейса: в Zune нет привязки элементов интерфейса к абсолютным значениям,
среда чувствительна к кеглям шрифтов и сама адаптирует размеры и расположение
любых окон в зависимости от шрифтов пользователя. Zune предоставляет
семантический доступ к элементам проектируемого интерфейса, а его свойства
(такие, как отступ элемента от края окна в пикселях) регулируются автоматически.
- Пользователю намного легче контролировать вид и поведение интерфейса,
спроектированного программистом, он получает возможность специфической
настройки параметров окружения Zune.
Zune основан на системе BOOPSI (Basic Object Oriented Programming System for
Intuition), унаследованной от AmigaOS и используемой в объектно-ориентированном
программировании на Си. Классы Zune не являются дочерними по отношению к
существующим для элементов интерфейса классам BOOPSI (т.е., не являются простым
расширением их возможностей). Напротив, базовым классом (в иерархии Zune)
является класс Notify — дочерний относительно корневого класса BOOPSI.
Для понимания концепции Zune, более чем приветствуется знание парадигмы
объектно-ориентированного программирования (ООП). Вы можете воспользоваться
Google для поиска и изучения образовательных материалов, посвящённых ООП.
Также, желательно, владение такими ключевыми понятиями AROS (и AmigaOS), как
список тегов (taglist) и система BOOPSI. Хорошим подспорьем здесь, безусловно,
является руководство "Amiga Reference Kernel Manuals" (известное как RKM).
Поскольку Zune является аналогом MUI, вся документация, имеющая отношение к MUI,
применима и к Zune. В частности, последняя версия инструментария для разработчиков
интерфейсов MUI доступна здесь. Среди прочих, в этом LHA-архиве есть 2 документа
особенно рекомендуемых к прочтению:
Также этот архив содержит документацию (MUI autodocs), которая является
описанием и для всех существующих классов Zune.
Класс (class), по сути, является лишь абстракцией, типом, описывающим объекты
этого класса с общими структурой и поведением, и задаётся именем, родительским
классом и диспетчером (dispatcher). Описание вводится путем указания типа и
свойств класса:
- имя: в случае, если класс является общим (public) - это строка, характеризующая
название класса и его область видимости, что делает его доступным любому приложению
в системе. В случае, если класс является локальным (private) - отсутствует, и
такой класс не может использоваться нигде, кроме одного-единственного приложения.
- родительский класс: все классы BOOPSI формируются в иерархическом порядке,
и являются дочерними по отношению к корневому классу rootclass. Это позволяет
каждому подклассу иметь собственную версию операции, производную от
родительской, либо ту же самую, что и у родителя. Класс,
содержащий подклассы, также называют базовым (base class) или суперклассом (super class).
- диспетчер: предоставляет доступ ко всем операциям класса, которые называют
методами. Гарантирует, что каждая операция будет
обеспечена исполнением соответствующего ей кода или будет передана
родителю данного класса (суперклассу).
В BOOPSI типом класса является Class * или IClass.
Объект является структурной единицей (реализацией) класса. Каждый объект
обладает своими свойствами (состоянием), но при этом поведение всех объектов
одного класса одинаково. Объект относится к нескольким классам, если исчислять
их от его класса до корневого класса rootclass, что определяется свойством
наследования.
Для объекта типом BOOPSI является Object *. Он не содержит доступных
напрямую полей.
Атрибут находится в связи с структурой данных (переменными состояния) каждого
объекта: изменять эти данные непосредственно вы не можете. Возможно только
установить или получить значения атрибутов объекта (также называемых свойствами)
для изменения его внутреннего состояния. Атрибуты объекта (со стороны системы)
ассоциированы с тегами (Tag) (со стороны программиста) (это значения, имеющие
тип ULONG и связанные с TAG_USER).
Для изменения атрибутов объектов используются функции GetAttr() и
SetAttrs().
Атрибуты (один или несколько) могут быть следующих видов:
- Назначаемый при установке (Initialization-settable)(I) : Атрибут может
быть передан как параметр лишь при создании объекта.
- Доступный для изменения (Settable)(S) : Можно установить значение этого атрибута в
любое время (не только при создании объекта).
- Доступный для чтения (Gettable)(G) : Вы можете лишь получить значение атрибута.
Методом в BOOPSI называется функция, которой в виде параметров передаются
имя объекта, его класс и сообщение:
- объект: имя объекта, над которым производится действие.
- класс: класс, соответствующий объекту.
- сообщение: содержит идентификатор (ID) метода, определяющий функцию,
вызываемую диспетчером и передаваемые ей параметры
Для отправки сообщения объекту используется функция DoMethod(). Сначала метод
будет применён к указанному классу. Если в классе определен этот метод, то
сообщение будет обработано. В противном случае, будут перебираться родительские
классы до тех пор, пока сообщение не будет обработано одним из них, или не будет
достигнут rootclass (в этом случае так и не определённое сообщение будет
молча отвергнуто).
Рассмотрим основные приёмы объектно-ориентированного программирования с BOOPSI:
Попробуем запросить данные объекта MUI String:
void f(Object *string)
{
IPTR result;
GetAttr(string, MUIA_String_Contents, &result);
printf("String content is: %s\n", (STRPTR)result);
}
Здесь string - объект, MUIA_String_Contents - получаемый атрибут, &result -
указатель на строку с результатом этой операции. К тому же:
Object * является типом объектов BOOPSI.
- Для типизации возвращаемого значения должен использоваться тип IPTR,
поэтому это значение может быть целым числом либо указателем. IPTR всегда
сохраняется в памяти, и использование более ограниченного типа привело бы
порче её содержимого!
Когда мы запрашиваем атрибуты объекта MUI String: MUIA_String_Contents,
как и любой другой атрибут, имеет тип ULONG (это тег).
В приложениях Zune вместо указанных функций часто используются макросы get()
и XGET(). Например:
get(string, MUIA_String_Contents, &result);
result = XGET(string, MUIA_String_Contents);
Слегка изменим приведенную выше строку:
SetAttrs(string, MUIA_String_Contents, (IPTR)"hello", TAG_DONE);
- Указатели, передаваемые в качестве аргументов должны иметь тип IPTR
(указатель на целую переменную, может содержать адрес значений типа int)
иначе компилятор будет выдавать предупреждения.
- Вслед за атрибутом, функции SetAttrs передаётся список тегов, поэтому
перечисление должно заканчиваться на TAG_DONE.
Вам наверняка покажется полезным макрос set():
set(string, MUIA_String_Contents, (IPTR)"hello");
Однако, только с помощью SetAttrs() вы сможете установить несколько атрибутов
за один раз:
SetAttrs(string,
MUIA_Disabled, TRUE,
MUIA_String_Contents, (IPTR)"hmmm...",
TAG_DONE);
Рассмотрим наиболее часто применяемый в программах Zune метод, метод
обработки событий, вызываемый в основном цикле программы:
result = DoMethod(obj, MUIM_Application_NewInput, (IPTR)&sigs);
- Параметры функций метода не являются списком тегов, и не должны заканчиваться с
TAG_DONE
- Указатели приходится типизировать как IPTR, чтобы избежать предупреждений,
от чего смысл их применения не меняется
С начала, так с начала. Эта программа не разочарует новичка.
Рассмотрим наш первый реальный пример:
// gcc hello.c -lmui
#include <exec/types.h>
#include <libraries/mui.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/muimaster.h>
#include <clib/alib_protos.h>
int main(void)
{
Object *wnd, *app, *but;
// Создание GUI: окна wnd, текста Hello world! и кнопки Ok
app = ApplicationObject,
SubWindow, wnd = WindowObject,
MUIA_Window_Title, "Hello world!",
WindowContents, VGroup,
Child, TextObject,
MUIA_Text_Contents, "\33cHello world!\nHow are you?",
End,
Child, but = SimpleButton("_Ok"),
End,
End,
End;
if (app != NULL)
{
ULONG sigs = 0;
// Реакция на элемент закрытия окна и выход по клавише Escape
DoMethod(wnd, MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
(IPTR)app, 2,
MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
// Реакция на нажатие кнопки выхода
DoMethod(but, MUIM_Notify, MUIA_Pressed, FALSE,
(IPTR)app, 2,
MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
// Открываем окно wnd
set(wnd, MUIA_Window_Open, TRUE);
// Проверяем, что окно wnd действительно было открыто
if (XGET(wnd, MUIA_Window_Open))
{
// Основной цикл приложения Zune
while((LONG)DoMethod(app, MUIM_Application_NewInput, (IPTR)&sigs)
!= MUIV_Application_ReturnID_Quit)
{
if (sigs)
{
sigs = Wait(sigs | SIGBREAKF_CTRL_C);
if (sigs & SIGBREAKF_CTRL_C)
break;
}
}
}
// Уничтожаем наше приложение со всеми его объектами
MUI_DisposeObject(app);
}
return 0;
}
Мы не открываем библиотеки вручную т.к. это делается за нас автоматически.
Мы использовали макросы для облегчения программирования интерфейса программы.
Приложение Zune всегда имеет 1 (и только 1) объект Приложения (ApplicationObject):
: app = ApplicationObject,
Приложение может иметь 0,1 или более объектов окон WindowObject. Чаще всего окно -
одно единственное:
: SubWindow, wnd = WindowObject,
Будет хорошо, если заголовок окна будет содержать название приложения:
: MUIA_Window_Title, "Hello world!",
Окно может иметь 1 (и только 1) дочерний объект (Child), обычно это группа (group).
Наша группа будет вертикальной (VGroup), это означает, что все входящие в неё
дочерние объекты (children) будут группироваться по вертикали:
: WindowContents, VGroup,
Группа должна иметь, как минимум 1 дочерний объект. В нашем случае, это будет
обыкновенный текст (TextObject):
: Child, TextObject,
В Zune поддерживаются различные escape-коды (ниже, через 33c производится
центрирование текста) и перевод каретки ( n ):
: MUIA_Text_Contents, "\33cHello world!\nHow are you?",
Макрос End должен завершать описание любого макроса вида xxxObject (в
нашем случае, TextObject):
: End,
Теперь добавим в нашу группу второй дочерний объект, кнопку! Помимо мыши, она
будет откликаться на комбинацию клавиш RAmiga + O (укажем на это символом
подчёркивания до буквы "O"):
: Child, but = SimpleButton("_Ok"),
Завершаем описание группы:
: End,
Завершаем описание окна:
: End,
Завершаем описание программы:
: End;
И что, вы всё ещё нуждаете в графических инструментах для создания GUI ? :-)
Если окажется невозможным создание любого из объектов в структуре описанной
нами выше, Zune уничтожит все объекты (включая те, которые удалось создать)
и возвратит код ошибки. В обратном случае, вы получите полностью рабочий
Zune интерфейс приложения:
: if (app != NULL)
: {
: ...
Если работа приложения завершается, вызывается метод MUI_DisposeObject()
с передачей указателя на созданный объект приложения. Это необходимо для
уничтожения всех созданных объектов и освобождения всех использованных ресурсов:
: ...
: MUI_DisposeObject(app);
: }
Обработка сообщений значительно упрощает задание реакции программы на
возникающие события (такие как, нажатие кнопки). Принцип: мы получаем сообщение,
когда определённый атрибут определённого объекта примет
определённое значение:
: DoMethod(wnd, MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
Здесь мы ожидаем, когда атрибут MUIA_Window_CloseRequest объекта
нашего окна (wnd) будет установлен в TRUE (пользователь нажал кнопку),
В этом случае объект приложения получит сообщение, предписывающее ему
вернуть код MUIV_Application_ReturnID_Quit на следующей же итерации цикла
обработки событий:
: (IPTR)app, 2,
: MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
Поскольку в этом списке могут быть указаны любые параметры, необходимо указать число
дополнительных параметров, передаваемых MUIM_Notify: в этом случае, 2 параметра.
В случае c кнопкой "Ok" мы ожидаем, когда атрибут MUIA_Pressed` окажется установленным
в FALSE, что будет означать нажатую и отпущенную пользователем кнопку "Ok"
(реакция на простое нажатие кнопки является плохой практикой, т.к. вы можете
захотеть отпустить кнопку мыши вне кнопки, и таким образом отказаться
от действия. К тому же, мы можем и просто захотеть увидеть, как она выглядит
в нажатом состоянии). В остальном, всё аналогично предыдущему примеру
(посылается сообщение):
: DoMethod(but, MUIM_Notify, MUIA_Pressed, FALSE,
: (IPTR)app, 2,
: MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
Окно не будет открыто, пока вы не "попросите" Zune об этом:
: set(wnd, MUIA_Window_Open, TRUE);
Если объекты описанной нами выше структуры были созданы удачно, вы уже должны
увидеть окно. Но и эта операция может завершиться с ошибкой! Таким образом,
мы не должны забывать о проверке атрибута объекта окна, который должен быть
установлен в TRUE:
: if (XGET(wnd, MUIA_Window_Open))
Дорогие друзья, позвольте представить вам идеальный цикл интерфейса Zune:
: ULONG sigs = 0;
Не забывайте обнулять сигналы (sigs) ... далее показан тестовый цикл приложения с
использованием метода MUIM_Application_NewInput:
: ...
: while((LONG) DoMethod(app, MUIM_Application_NewInput, (IPTR)&sigs)
: != MUIV_Application_ReturnID_Quit)
Этому методу передаются сигналы событий, которые он должен обработать (сообщения
от Wait(), или 0), значение указателя sigs будет изменяться, принимая
значения ожидаемых Zune сигналов (очередных сообщений от Wait()) и в результате
это значение будет возвращено. Поэтому обнуление sigs в цикле необходимо. Этот
механизм возврата значений исторически был единственным способом реакции на
события. Однако, поскольку он был слишком "неудобоварим", впоследствии от него
стали отказываться в пользу создания отдельных классов и объектно-ориентированной
структуры приложения.
Тело самого цикла приложения весьма простое. Здесь мы видим лишь ожидание сигналов и
обработку нажатия Ctrl + С для обеспечения принудительного выхода из цикла:
: {
: if (sigs)
: {
: sigs = Wait(sigs | SIGBREAKF_CTRL_C);
: if (sigs & SIGBREAKF_CTRL_C)
: break;
: }
: }
Эта программа позволила вам начать изучение Zune и немного поработать
на дизайном GUI приложения, но не более того.
Чтобы собрать эту программу кросс-компилятором i386-aros-gcc, используйте следующую
команду:
i386-aros-gcc -o hello -D__AROS__ hello.c -lmui
Согласно комментариям к hello.c, приведенным выше, вы должны использовать
MUIM_Notify для вызова метода при возникновении ожидаемого вами события.
Если же требуется описать более специфичную реакцию программы на события,
необходимо воспользоваться одним из следующих алгоритмов:
- MUIM_Application_ReturnID: можно возвращать некий идентификатор ID на последующих
шагах цикла, и проверять это значение в цикле. Можно задать несколько таких
идентификаторов и в зависимости от возвращаемого значения вызывать разные
методы. Старый и дурацкий способ обхода цикла.
- MUIM_CallHook: использование стандартного для Amiga механизма вызова (callback)
пользовательских функий при возникновении событий, управляемого структурой Hook.
Способ, не имеющий ничего общего с ООП, но вполне допустимый.
- Использование ООП: вызов из тела цикла метода, принадлежащего одному из созданных
вами ранее классов. Это лучшее решение с точки зрения объектно-ориентированной
структуры программы, однако, оно мало подходит для новичков в ООП и
программистов, не привыкших тратить много времени на "изящества".
|
|