Программирование в AmigaOS. Часть третья

Создание MUI классов

Андрей Черешнев

3. Введение

Разработка объектно-ориентированного приложения состоит из двух важных частей: представление создаваемой программы в виде независимых блоков (классов) и затем "связывании" их между собой сообщениями. MUI позволяет создавать свои собственные классы на основе встроенных MUI классов или уже раннее созданных.

Если сравнить структурный и объектно-ориентированный подход в написании программ, то последний имеет много преимуществ: вносить изменения, искать и исправлять ошибки стало намного проще, ведь это касается не всей программы целиком, а только конкретного класса, также деление программы на классы предоставляет мощные механизмы программистам, например когда для открытия еще одного окна Интернет браузера требуется только одна команда - создание объекта класса ИнтернетОкно.

Созданные классы следует рассматривать как "новые" встроенные MUI классы, т.о. все о чем было написано в предыдущей статье в полной мере относится и к этой.

3.1 Типы классов

Существует два типа: классы которые создаются программистами только в своих программах - private custom классы и классы представленные в виде отдельных файлов - public custom классы, такие классы могут распространятся отдельно от программ и использоваться другими программистами, например класс Busy (файл Busy.mcc). Public класс может "развиваться" отдельно от использующих его программ! Мы будем работать только с private custom классами.

3.2 Создание класса

Для создания используется функция MUI_CreateCustomClass(), в ее аргументах указывается класс предок (или родительский класс), размер структуры данных класса и указатель на "dispatcher" функцию. Рассмотрим их подробнее:

Класс предок - используется как некая платформа предоставляющая основные методы и атрибуты создаваемому классу. Например если мы хотим создать класс ОсновноеОкноПрограммы то классом предком должен быть MUIC_Window. Теперь все что есть в классе MUIC_Window доступно и в ОсновноеОкноПрограммы.

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

struct MyText_Data
{
    char text[256];
};
"Dispatcher" функция - когда мы или MUI посылаем объекту сообщение, за работу принимается эта функция. Она имеет стандартный вид и состоит из оператора "switch" который определяет какому имени метода соответствует функция обработчик. Пример:
...
case MUIM_MyText_Print : return(mPrint(cl,obj));
...
Если у нас есть объект класса MyText, ему можно послать сообщение - "вывести себя на экран" т.е. DoMethod(объект, MUIM_MyText_Print). DoMethod() вызывает нашу dispatcher-функцию со значением MUIM_MyText_Print, далее происходит вызов функции обработчика - mPrint() в ней выводится на экран значение поля данных объекта - text. Допустим нашему объекту послали сообщение для которого у него нет обработчика. Что делать? Для этого в конце dispatcher-функции пишут строку:

return(DoSuperMethodA(cl,obj,msg));

Функция DoSuperMethodA() посылает это сообщение классу предку!

Вернемся к самой функции создания класса MUI_CreateCustomClass(). Результат ее работы - указатель на созданный класс (struct MUI_CustomClass *) или 0 если не смогла его создать. В конце работы программы созданные классы удаляются функцией MUI_DeleteCustomClass(удаляемый_класс). Пример создания класса MyText:

cl_MyText = MUI_CreateCustomClass(NULL, MUIC_Text, NULL,
                                  sizeof(struct MyText_Data),
                                  MyText_Dispatcher);

3.3 Создание объектов Custom классов

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

text_obj = NewObject(cl_MyText->mcc_Class, NULL, TAG_DONE);

В данном случае мы просто создаем объект text_obj класса MyText, или:

text_obj = NewObject(cl_MyText->mcc_Class, NULL,
                     MUIA_Text_Contents, "Всем привет!",
                     TAG_DONE);
где дополнительно устанавливаем атрибут объекта - MUIA_Text_Contents.

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

3.4 Определение методов класса

3.4.1 Предопределенные методы

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

Когда мы создаем объект custom класса функцией NewObject(), она вызывает метод OM_NEW (конструктор) с аргументами заданными при ее вызове. В обработчике "OM_NEW" - функции mNew() (см. файл "cl_class.c" ) происходит само создание объекта, т.е. создаются нужные GUI объекты, присваиваются значения данным объекта, устанавливаются "извещения" и т.д.

В обработчике обязательно должен быть вызов функции DoSuperNew() , она вызывает метод "OM_NEW" родительского класса и устанавливает нужные атрибуты:

obj = DoSuperNew(cl, obj,
                 атрибуты_родительского_класса,
                 TAG_MORE, msg->ops_AttrList);
Когда объект удаляется, вызывается метод OM_DISPOSE (деструктор). В обработчике "освобождают" память занятую объектом, память которую запрашивали у ОС в определении "OM_NEW" и другие действия (см. функцию mDispose() ).

3.4.2 Собственные методы

Вы можете создавать свои методы. Эти методы могут вызываться функцией DoMethod(), могут быть обработчиками событий и т.д. Для создания метода нужно определить какие действия он должен добавлять классу, задать имя:

#define MUIM_Класс_ИмяМетода (TAG_USER | 0x0001)

0x0001, 0x0002, ... - идентификатор метода, каждый метод должен иметь уникальный идентификатор в рамках класса!

Затем, добавить строку в dispatcher-функцию класса:

case MUIM_Класс_ИмяМетода : return(mФункцияОбработчик(cl,obj));

И, наконец, написать сам метод (см. пример определения метода "MUIM_Class_Quit" ).

3.5 Доступ к данным объекта

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

struct Класс_Data *data = INST_DATA(cl,obj);

ее пишут в начале определения метода. К примеру определение метода MUIM_MyText_Print можно записать так:

static ULONG mPrint(struct IClass *cl, Object *obj)
{
    struct MyText_Data *data = INST_DATA(cl,obj);

    puts(data->text);
    return(0);
}
Таким образом переменная data указывает на структуру данных объекта.

3.6 "Готовая" программа

MUI предоставляет большую свободу в выборе "стиля" программирования, я бы хотел предложить читателям использование т.н. шаблонных файлов и программы визуального создания GUI - MUIBuilder, далее приводится текст шаблонов для главного файла программы - main.c и определения класса "Сlass" - cl_class.c который можно использовать как шаблон окон вашей программы (потомок класса Window). Применение шаблонных файлов очень удобно т.к. уменьшает количество часто повторяющихся ошибок при наборе текста программы (в том числе и логических), простым добавлением строк кода в текст шаблона можно создавать новые программы т.е. шаблоны можно считать неким универсальным скелетом программ.

Файл main.h


/* файл main.h */

#include <libraries/mui.h>
#include <libraries/gadtools.h>
#include <exec/memory.h>

#include <proto/muimaster.h>
#include <proto/exec.h>

#include <stdio.h>
#include <stdlib.h>

/* макросы для компиляторов SAS/C и StormC */

#define REG(x) register __##x
#define SAVEDS __saveds

#if defined __STORM__
#define ASM
#define __asm
#define __stdargs
#else
#define ASM __asm
#endif

/* описание внешних переменных: */

extern struct MUI_CustomClass *cl_ClassName;
extern Object                 *app;

/* прототипы глобальных функций: */

extern ULONG xget(Object *obj, ULONG attr);
extern __stdargs Object *DoSuperNew(struct IClass *cl, Object *obj,
                                    ULONG tag1,...);
Файл main.c
/* файл main.c */

#include "main.h"
#include "cl_ClassName.h"

struct Library *MUIMasterBase;

LONG __stack = 8192;

struct MUI_CustomClass *cl_ClassName;
Object                 *app;

/*--------------------------------------------------------------------*/
__stdargs Object *DoSuperNew(struct IClass *cl, Object *obj, ULONG tag1,...)
{
    return((Object *)DoSuperMethod(cl, obj, OM_NEW, &tag1, NULL));
}

/*--------------------------------------------------------------------*/
ULONG xget(Object *obj, ULONG attr)
{
    ULONG result;

    get(obj, attr, &result);
    return(result);
}

/*--------------------------------------------------------------------*/
static void init_libs(void)
{
    MUIMasterBase = OpenLibrary("muimaster.library", MUIMASTER_VMIN);
    if(!MUIMasterBase)
    {
        puts("Can't open muimaster.library");
        exit(20);
    }
}

/*--------------------------------------------------------------------*/
static int init_classes(void)
{
    cl_ClassName = MUI_CreateCustomClass(NULL, MUIC_Window, NULL,
                                     sizeof(struct Class_Data),
                                     Class_Dispatcher);
    return(cl_ClassName ? TRUE : FALSE);
}

/*--------------------------------------------------------------------*/
static void exit_classes(void)
{
    if(cl_ClassName) MUI_DeleteCustomClass(cl_ClassName);
}

/*--------------------------------------------------------------------*/
static void all_exit(void)
{
    if(app)
        MUI_DisposeObject(app);
    exit_classes();
    CloseLibrary(MUIMasterBase);

    exit(0);
}

/*--------------------------------------------------------------------*/
int main(void)
{
    Object *win;

    init_libs();
    if(init_classes())
    {
        app = ApplicationObject,
            MUIA_Application_Title, "Class Example",
            MUIA_Application_Base, "CLASSEXAMPLE",
            SubWindow, win = NewObject(cl_ClassName->mcc_Class,NULL,
                                       MUIA_Window_Title, "Class Example",
                                       MUIA_Window_ID, 1,
                                       TAG_DONE),
            End;

        if(app)
        {
            set(win, MUIA_Window_Open, TRUE);
            {
                ULONG sigs = 0;

                while (DoMethod(app,MUIM_Application_NewInput,&sigs)
                                != MUIV_Application_ReturnID_Quit)
                {
                    if (sigs)
                    {
                        sigs = Wait(sigs | SIGBREAKF_CTRL_C);
                        if (sigs & SIGBREAKF_CTRL_C) break;
                    }
                }
            }
            set(win, MUIA_Window_Open, FALSE);
        }
    }
    all_exit();
}
Файл cl_class.h
/* файл cl_class.h */

struct Class_Data
{
    struct Class_Obj
    {
        /* поля структуры Class_Obj копируются из файла "cl_classGUI.h" */

    } *obj_gui;
};

/* Методы и атрибуты класса: */
#define MUIM_Class_Quit (TAG_USER | 0x0001)

/* end */

ULONG SAVEDS ASM Class_Dispatcher(REG(a0) struct IClass *cl,
                                  REG(a2) Object *obj,
                                  REG(a1) Msg msg);
Файл cl_class.c
/* файл cl_class.с */

#include "main.h"
#include "cl_ClassName.h"

/*--------------------------------------------------------------------*/
static ULONG mDispose(struct IClass *cl, Object *obj, Msg msg)
{
    struct Class_Data *data = INST_DATA(cl,obj);

    FreeVec(data->obj_gui);
    return(DoSuperMethodA(cl, obj, msg));
}

/*--------------------------------------------------------------------*/
static ULONG  mQuit(struct IClass *cl, Object *obj)
{
    /* посылает объекту "app" сообщение о выходе из программы: */
    DoMethod(app, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);

    return(0);
}

/*--------------------------------------------------------------------*/
static ULONG  mNew(struct IClass *cl, Object *obj, struct opSet *msg)
{
    struct Class_Data *data;
    struct Class_Obj  *Object;
    /* Раздел переменных: */

    /* end */

    if (!(Object = AllocVec(sizeof(struct Class_Obj), MEMF_PUBLIC|MEMF_CLEAR)))
        return(NULL);

    /* Раздел создания GUI объектов для класса: */

    /* end */

    obj = DoSuperNew(cl, obj,
                     WindowContents, GROUP_ROOT_0,
                  /* Если окно имеет меню, используйте строку:  */
                  /* MUIA_Window_Menustrip, Object->MN_label_1, */
                     TAG_MORE,msg->ops_AttrList);

    data = INST_DATA(cl,obj);
    data->obj_gui = Object;

    /* Раздел "извещений" для созданных объектов: */
    DoMethod(obj,
             MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
             obj,
             1,
             MUIM_Class_Quit);

    /* end */

    return((ULONG)obj);
}

/*--------------------------------------------------------------------*/
ULONG SAVEDS ASM Class_Dispatcher(REG(a0) struct IClass *cl,
                                  REG(a2) Object *obj,
                                  REG(a1) Msg msg)
{
    switch (msg->MethodID)
    {
        case OM_NEW               : return(mNew(cl,obj,(APTR)msg));
        case OM_DISPOSE           : return(mDispose(cl,obj,(APTR)msg));

        case MUIM_Class_Quit      : return(mQuit(cl,obj));
    }
    return(DoSuperMethodA(cl,obj,msg));
}
Файл "main.с": в функции main() сначала происходит открытие библиотек - init_libs(), затем создание классов - init_classes(). Далее создается "главный" объект программы - app класса Application. Создается объект нашего класса "Сlass" - win, его мы связываем с app (атрибут SubWindow). Потом окно win появляется на экране - set(). С этого момента программа ждет внешних событий. Когда мы выходим из программы окно закрывается - set() и вызывается функция all_exit() в которой мы "отдаем" ОС все что у ней "заняли" в файле "main.c"

Вспомогательные функции которые можно использовать во всех классах программы:

char *title;
title = (char *)xget(win, MUIA_Window_Title);
puts(title);
В переменную title заносится адрес заголовка окна объекта win и затем title выводится на экран.

Перейдем к классу "Class". Файл "cl_class.h" содержит определение структуры данных класса - struct Сlass_Data . Одним из полей структуры является указатель obj_gui на структуру Сlass_Obj, ее определение создаст для нас MUIBuilder. В структуру Сlass_Data вы можете добавлять свои поля. Далее идут объявления методов и атрибутов класса, также можете добавлять сюда свои.

Файл "cl_class.с" содержит только определения методов класса и dispatcher-функции. Рассмотрим подробнее метод OM_NEW . "Раздел переменных", "создания GUI объектов" - сюда помещаются результаты работы MUIBuilder. Функцией DoSuperNew() создается сам объект т.е. окно. В "извещения" мы записываем реакцию объекта на различные события, сюда уже записана реакция на закрытие окна: если закрываем окно, то выполняется метод MUIM_Class_Quit. Далее в этой статье мы рассмотрим реакцию на нажатие кнопки и выбора пункта меню программы.

Функция AllocVec() библиотеки "exec.library" запрашивает нужное количество памяти у ОС, а FreeVec() освобождает занятую память. В данном случае резервируется область памяти размера структуры Сlass_Obj, она нужна для того чтобы обращаться к GUI объектам нашего класса из любого его метода. Например в определении какого-нибудь метода можно получить значение атрибута GUI объекта TX_label_0:

str = (char *)xget(data->obj_gui->TX_label_0, MUIA_Text_Contents);

3.7 Создание класса "HelloWorld"

Воспользуемся нашими шаблонами для главной программы и класса "Сlass". Создайте каталог с именем, например НelloWorld, и скопируйте туда все файлы-шаблоны, для того чтобы работать с их копиями, ведь оригиналы могут также использоваться для других программ и классов. Переименуйте файлы "сl_class.h" и "cl_class.c" в cl_helloworld.h и cl_helloworld.c соответственно, чтобы сразу видеть какие классы где определены.

Теперь наша задача поменять абстрактное имя класса "Сlass" на более определенное - "HelloWorld". Сделайте замену во всех файлах слова "ClassName" на HelloWorld и "Сlass_" на HelloWorld_. Если вы используйте компилятор SAS/С, то эти действия можно выполнить в окне Shell, сделав каталог "НеlloWorld" активным и выполнив две команды:

splat -o ClassName HelloWorld  #?
splat -o Class_    HelloWorld_ #?
С этого момента мы будем работать только с определением класса "HelloWorld" т.е. файлами "cl_helloworld.h" и cl_helloworld.c" не касаясь остальной части программы! Мы наделим его свойствами вывода в окно сообщения, и реакциями на действия пользователя (кнопки, меню и т.д.). Для этого воспользуемся MUIBuilder.

3.8 Работа с программой MUIBuilder

MUIBuilder позволяет визуально создавать GUI для программ из встроенных MUI классов. Программа является бесплатной (freeware) ее можно найти в AmiNet.

3.8.1 Создание GUI элементов окна

На простом примере создания GUI для класса "HelloWorld" рассмотрим работу с MUIBuilder.

Сначала в главном окне MUIBuilder создаем "рабочее" окно кнопкой "New Window".

пример

После этого открывается новое окно "Window Attributes" в котором представлены GUI объекты "рабочего" окна. Как мы уже знаем, создание графического интерфейса для программы состоит из расположения GUI элементов в группах (Group объектах), так и здесь, но для нас уже создан "корневой" Group объект - GROOP_ROOT_0.

пример

В него мы поместим два объекта - текст (Text) и кнопку (KeyButton). Для этого кликните по кнопке "Add Child" в окне "Window Attributes". Откроется новое окно "Object Choice" где показаны GUI объекты которые можно создать.

пример

Кликните по кнопке "Text" и введите для нее в новом окне "Text Attributes" в "Preparse" - \33с и в "Text Сontent" - Hello, World!. Как только нажали "ОК" появится "рабочее" окно с сообщением Hello, World!. Итак, мы создали первый объект класса "Text" с именем TX_label_0.

Повторите действия начиная с "Add Child" но уже для создания кнопки, в строке "Title" напишите - Ok, а в ярлыке "Area" - "Сontrol Char" - символ 'о' для задания "горячей" клавиши. В "рабочее" окно добавится кнопка Ok, т.о. оно в точности показывает как будут располагаться GUI элементы в окне нашей программы.

Теперь осталось добавить меню в "HelloWorld". Для этого в окне "Window Attributes" перейдите в ярлык "Attributes" и нажмите кнопку "Menu". Откроется окно "Menu Creation". Давайте добавим в него меню "Project" и пункт "Quit" - выход.

пример

Кликните по "New Title" и в "Entry" введите - Project, чтобы добавить пункты к этому меню нажмите кнопку "MenuItem" и введите - Quit, также в "Short Сut" поставьте символ для выбора меню по комбинации "ПраваяАмига + символ" - 'Q'. Теперь можно нажимать "Quit". Все. Создание GUI элементов для класса "HelloWorld" закончено. Переходим к переводу GUI в строки кода на Си.

В окне "Window Attributes" кликните по кнопке "OK". В главном окне MUIBuilder нажмите на кнопку "Code". Откроется окно управлением создания строк кода - "Code". Нужно перейти в ярлык "Options", снять отметки с "Environment" и "Notification". Далее в строку "Code" введите путь и имя файла в который помещается код, например ram:cl_classGUI.

пример

Все готово, можно переводить, для этого в ярлыке "Сode" кликните по кнопке "App Code". На "RAM:" диске появятся два файла - cl_classGUI.с и cl_classGUI.h.

Текущую работу ("рабочие" окна с GUI объектами) можно сохранить для последующих исправлений/добавлений в файле с расширением .MUIB. В дальнейшем, простым нажатием на кнопку "App Code" можно отразить в Си файлах любые изменения которые произошли в "рабочем" окне.

Многочисленные примеры создания GUI для программ находятся в каталоге MUIBuilder/BuilderSave.

3.8.2 Использование результатов работы с MUIBuilder

Вернемся к определению класса "HelloWorld". Последнее, что нужно сделать для класса это поместить созданные MUIBuilder строки в файлы cl_helloworld.h и cl_helloworld.c, а также установить "извещения" для реакций на выбор пункта меню и нажатия кнопки. Из файла cl_classGUI.h поля структуры ObjApp вставляем в определение структуры HelloWorld_Оbj файла cl_helloworld.h, после этого она должна выглядеть так:

struct HelloWorld_Data
{
    struct HelloWorld_Obj
    {
        APTR    MN_label_1;
        APTR    TX_label_0;
        APTR    BT_label_0;
        char *  STR_TX_label_0;
    } *obj_gui;
};
Поля APTR App и APTR WI_label_0 нам не нужны, т.к. объекты классов Application и Window мы сами создаем в нашей программе. Из файла cl_classGUI.с в "Раздел переменных:" метода OM_NEW, вставляем объявления переменных (указатели на объекты) в нашем случае это одна строка:
APTR    MNlabel1Project, MNlabel1Quit, GROUP_ROOT_0;
И в "Раздел создания GUI объектов для класса:" строки создания следующих GUI объектов:
Object->STR_TX_label_0, Object->TX_label_0,
Object->BT_label_0, GROUP_ROOT_0, MNlabel1Quit, MNlabel1Project,
Object->MN_label_1
Строки создания объектов Object->WI_label_0 и Object->App копировать в файл "cl_helloworld.с" не нужно.

И, наконец, установим "извещения". В "Раздел извещений для созданных объектов:" добавляем реакцию объекта на нажатие кнопки "OK":

DoMethod(Object->BT_label_0,
         MUIM_Notify, MUIA_Pressed, FALSE,
         obj,
         1,
         MUIM_HelloWorld_Quit);
Теперь, если пользователь кликнет по кнопке "OK" или нажмет клавишу 'O', то МUI пошлет сообщение объекту - MUIM_HelloWorld_Quit.

В этот же раздел добавим реакцию на выбор пункта меню "Quit":

DoMethod(MNlabel1Quit,
         MUIM_Notify, MUIA_Menuitem_Trigger, MUIV_EveryTime,
         obj,
         1,
         MUIM_HelloWorld_Quit);
Реакция такая же как и на нажатие "OK". Удалите символы комментария "/* */" в аргументах вызова функции DoSuperNew() метода OM_NEW:
MUIA_Window_Menustrip, Object->MN_label_1,
В результате мы имеем программу состоящую из 4 файлов на языке Си, два из которых представляют определение независимого класса "HelloWorld" - окно с текстовым сообщением, кнопкой и меню.

пример

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

в начало функции:

Object *win2;
в создание app добавить еще один объект:
SubWindow, win2 = NewObject(cl_HelloWorld->mcc_Class,NULL,TAG_DONE),
                            MUIA_Window_Title, "Class Example 2",
                            MUIA_Window_ID, 2,
                            TAG_DONE),
и строки открытия/закрытия окна (как и для win):
set(win2, MUIA_Window_Open, TRUE);
set(win2, MUIA_Window_Open, FALSE);
Результат - создаются два объекта класса "HelloWorld" связанные с объектом app, а на экране появляются два независимых окна!


© 1999-2006  Андрей Черешнев
Редактор GoldED | Начало
Сайт создан в системе uCoz