Разработка объектно-ориентированного приложения состоит из двух важных частей: представление создаваемой программы в виде независимых блоков (классов) и затем "связывании" их между собой сообщениями. MUI позволяет создавать свои собственные классы на основе встроенных MUI классов или уже раннее созданных.
Если сравнить структурный и объектно-ориентированный подход в написании программ, то последний имеет много преимуществ: вносить изменения, искать и исправлять ошибки стало намного проще, ведь это касается не всей программы целиком, а только конкретного класса, также деление программы на классы предоставляет мощные механизмы программистам, например когда для открытия еще одного окна Интернет браузера требуется только одна команда - создание объекта класса
ИнтернетОкно
.Созданные классы следует рассматривать как "новые" встроенные MUI классы, т.о. все о чем было написано в предыдущей статье в полной мере относится и к этой.
Существует два типа: классы которые создаются программистами только в своих программах - private custom классы и классы представленные в виде отдельных файлов - public custom классы, такие классы могут распространятся отдельно от программ и использоваться другими программистами, например класс Busy (файл Busy.mcc). Public класс может "развиваться" отдельно от использующих его программ! Мы будем работать только с private custom классами.
Для создания используется функция 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);
Объекты создаются функцией 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.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" ).
Каждый созданный объект имеет свои данные, заданные как поля структуры данных класса. Для того чтобы получить доступ к этим данным из методов, используется следующая конструкция:
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
указывает на структуру данных объекта.
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"Вспомогательные функции которые можно использовать во всех классах программы:
DoSuperNew()
- применяется в определении метода OM_NEW.xget()
- более удобный вариант функции get(). Позволяет получить значение атрибута заданного объекта. Пример:В переменную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);
Воспользуемся нашими шаблонами для главной программы и класса "С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.
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 | Начало |