LINUX-BG   Адрес : http://www.linux-bg.org
Създаване на потребителски графичен интерфейс с Qt
От: Михаил Петров
Публикувана на: 21-03-2002
Адрес на статията: http://www.linux-bg.org/cgi-bin/y/index.pl?page=article&id=devs&key=338900525

Благодарение на Валентин Вълчев може да прочетете статията в [PDF формат], [TeX формат] и по-добър [HTML формат].

СЪЗДАВАНЕ НА ПОТРЕБИТЕЛСКИ ГРАФИЧЕН ИНТЕРФЕЙС С ПОМОЩТА НА QT
ИЛИ
ЕДНА ИДЕЯ ЗА ПРОГРАМЕН МОДЕЛ

 

I. Малко история или необходимостта от подобни разработки.

 

1.Дълбоко съм убеден, че без създаване на скучни програми, от рода на складови програми, счетоводства и подобни, Linux, като операционна система, не би могъл да заеме полагощото му се място на пазара на информационни продукти и технологии. Според мен това е пътя за налагане на по - добрата операционна система и популяризирането и. Но от някъде все пак трябва да се започне, е аз започнах от тук.

2.Създаването на подобни програмни модели би привлякло на наша страна приложните програмисти, към които и аз се причислявам, което много би помогнало за разработка на програми, ориентирани към конкретния потребител, защото негово величество потребителят не се интересува чак толкова от това дали програмата му е направена под Wndows, Linux или QNX, а от това дали дадения продукт върши някава работа и как я върши.

3.Както и да го въртим, не можем да си затворим очите за дългогодишното присъствие на Windows на пазара, което доведе до навика, потрбителя да изпълнява определени действия, като щтрака по икони, бутони и други атрибути на "привлекателния графичен интерфейс" и той търси тези неща, въпреки, че много често графичния интерфейс няма нищо общо с ефективността на даденото приложение, а по - скоро пречи на добрата работа на приложението.

4.Създаването на потребителски графичен интерфейс е по - скоро туткава работа, но без това няма да минем. И в тази връзка предлагам този програмен модел, с надеждата, че това донякъде ще подпомогне създаването на приложения, съдържащи такъв графичен интерфейс.

5.Признавам си без бой, че идеята не е изцяло моя, а е взета от идеята на MS VC++, за "Архитектурата Документ/Изглед", в което според мен има достатъчно много хляб. Аз просто претворих донякъде тази идея, като съм се стремил да избегна от нещата, които не ми харесват. Доколко всичко това е добро или не чак толкова оставам на вас да прецените. Считам, колкото и скандално да прозвучи, в нещото наречено Windows има достатъчно много идеи, които просто плачат за по - добра реализация, което от своя страна би довело до качествено нови идеи и разработки, а до по - благосклонно отношение на потребителите към Linux. Предпоставките в това отношение са достатъчно много и остава да ги използваме.

6.Тази статия я раждам в продължение на около три месеца, защото се оказва, че е доста трудно да се преведе написаното на С++, на нормален човешки език и в тази връзка моля да бъда извинен за вероятно не добрия стил на статията, но аз съм все пак приложен програмист а не журналист или писател. Нядявам се че написаното е достатъчно разбираемо ако не за обикновенните потребители то поне за програмистите.

7.Самите source code ще можете да свалите, едва след като си направя web страница някъде и обещавам, ако има интерес към тях да ги публикувам.

8.Не очаквайте да намерите в тази статия начин и описание на класовете на Qt, защото описанието на тази библиотека е достатъчно добро и подробно и нямам намерение да го повтарям. Тук ще опиша принципните постановки на програмния модел, който много ми се иска да нарека архитектура, но не съм толкова велик, като Microsoft, така че ще го наричам програмен модел.

 

II.Структурата DoraRuntimeClass и класът DoraClassObject.

 

За да създаваме потребителски графичен интерфейс е необходим да осигурим, освен изчертаването на прозорците, по начин, необходим за конкретното приложение, а също и механизъм за взаимодействие между отделните обекти. Това може да се осъществи с направата на RUNTIME механизъм, позволяващ достъпа от даден обект до други обекти от приложението, както и обратната връзка. За постигането на тази цел е необходимо обектите да се създават динамично по време на изпълнение. В настоящият програмен модел това се постига със съвокупността от една структура DoraRuntimeClass, класът DoraClassObject макросът RUNTIME_CLASS. Това само по себе си не е ника ново, нито необичайно. Освен това тук съм длъжен да упомена, че Qt, като графичен интерфейс има подобен механизъм, макар и реализиран по по - различен начи, имам предвид технологията signal/slot. Дължа да ви успокоя, че настоящият програмен модел по никакъв начин не нарушавва вътрешната логика на Qt, а по - скоро я използва, макар и неявно.

Структурата DoraRuntimeClass е дефинирана във файла с дефиниции DoraClassObeject.h, и изглежда по следния начин:

 

class DoraClassObject;

 

struct DoraRuntimeClass {

CString class_name;

DoraClassObject* (*m_pCreateView)(QWidget *parent = 0);

DoraClassObject* CreateView(QWidget *parent = 0);

DoraClassObject* (*m_pCreateDocument)();

DoraClassObject* CreateDocument();

};

 

Както се вижда в дефиницията на структурата няма нищо необичайно. Променливата class_name е от тип CString, който съм написал за да боравя по - лесно с променливи от типа char[], и той няма нищо общо с познатия до болка CString от MS VC++, като допълнително съм включил в него механизъм за конвертиране на паскалски стрингове в нулево базирани стрингове. Освен това в структурата има и две callback функции, като функцията CreateView служи за конструиране на обекти, които са прозорци, а CreateDocument е за конструиране на обекти, които не са прозорци.

 

Класът DoraClassObject е дефиниран в същият файл по следния начин:

 

class DoraClassObject {

public:

virtual DoraRuntimeClass *GetClassObject() const {

classDoraClassObject.class_name = "";

return &classDoraClassObject;

}

static DoraRuntimeClass classDoraClassObject;

public:

DoraClassObject() {}

virtual ~DoraClassObject() {}

};

 

Смятам, че в дефинуицията на класа, също няма нищо необичайно. Както се вижда има си конструктор и виртуален деструктор. Функцията GetClassObject служи за получаване на името на класа, който се конструира, с идеята да се използва по - натък за получаване на указател към съиответния обект, чрез който да получа достъп до функциите и променливите на съответния клас. За целта ще е необходима във всички наследници на този клас да се предефинира тази виртуална функция.

Връзката между структурата DoraRuntimeClass и класът DoraClassObject, се осъществява с един макрос RUNTIME_CLASS, дефиниран по следния начин:

 

#define RUNTIME_CLASS(class_name)(&class_name::class##class_name)

 

До тук сме извършили подготовката на необходимото за динамичното създаване на обектите в едно приложение. Структурата DoraRuntimaClass, класът DoraClassObject и макросът RUNTIME_CLASS, е необходимо да се разглеждат заедно, именно тяхната съвокупност осигурява необходимия механизъм за динамичното създаване на обекти и взаимодействието между тях.

Файлът с имплементациите е DoraClassObject.cpp, там няма кой знае какво, просто описанието на двете callback функции заедно със задаването на името на класа:

 

DoraRuntimeClass DoraClassObject::classDoraClassObject = {"DoraClassObject", NULL, NULL};

DoraClassObject *DoraClassObject::CreateView(QWidget *parent = 0) {

return (*m_pCreateView)(parent);

}

DoraClassObject *DoraClassObject::CreateDocument() {

return (*m_pCreateDocument)();

}

 

 

III.Базов изгледен клас и базов документен клас.

 

Нека да разделим обектите в едно приложение по следния начин - нека обектите, които показват някакви данни в прозорец на екрана, наречем изгледни класове, а обектите, които съхраняват данните на приложението и/или извършват обработката на данните, наречем документни класове. Тогава създавайки един базов клас DoraBaseView, служещ за производство на изгледни класове и един базов документен клас DoraBaseDocument, за документните класове.

Класът DoraBaseView е наследник на два класа, QWidget, идващ от Qt и описаният по - горе клас DoraClassObject, той е дефиниран във файла с дефиниции - DoraBaseView.h.

 

class DoraBaseView : public QWidget, public DoraClassObject

{

public:

DoraRuntimeClass *GetClassObject() const {

return &classDoraBaseView;

}

static DoraRuntimeClass classDoraBaseView;

static DoraClassObject *CreateView(QWidget *parent = 0);

static DoraClassObject *CreateDocument();

public:

DoraBaseView(QWidget *parent = 0, const char *name = 0) : QWidget(parent, name), DoraClassObject() {

}

~DoraBaseView() {}

 

private:

void paintEvent(QPaintEvent *e);

void resizeEvent(QResizeEvent *e);

void mousePressEvent(QMouseEvent *e);

void showEvent(QShowEvent *e);

void hideEvent)QHideEvent *e);

void focusInEvent(QFocusEvent *e);

void focusOutEvent(QFocusEevent *e);

 

public:

virtual void OnDraw(QPainter *p);

virtual void OnSize(int cx, int cy);

virtual void OnShowView();

virtual void OnHideView();

virtual void OnFocus();

virtual void OffFocus();

virtual void PostMouseRightButton();

virtual void PostMouseLeftButton();

virtual void PostMouseMidButton();

};

 

Както се вижда от дефиницията на класа, е необходимо да се дефинират първо статичната променлива classDoraBaseView, двете статични функции за динамичното създаване на обектите - CreateView и CreateDocument и да се предифинира виртуалната функция GetClassObject. Функциите от секция private са жиртуални функции дефинитрани в класа QWidget на Qt. Виртуалните функции от следващата секция public са дефинирани от мен и служат за приемане на съобщенията идващи от средата и обработвани от Qt. Те трябва да се предефинират в наследниците на този клас. По този начин предоставям на Qt, грижата да установи връзката с графичната среда, която от своя страна има грижата да се занимава с хардуера. Тук му е мястото да спомена, че изборът ми на Qt, вероятно не е най - удачния избор на графична библиотека, за разработка на подобен програмен модел, но когато започвах разработката му Qt някак много странно ми заприлича на нещо много познато, имам предвид MFC. Въпреки това обаче за мое учудване моделът проработи. Така, че продължавам с имплементацията на класа. Това е направено във файла с имплементации DoraBaseView.cpp.

Първо, всеки клас, разработван според този програмен модел, файлът с имплементации, трябва да започва със следният ред - DoraRuntimeClass DoraBaseView::classDoraBaseView={"DoraBaseView", DoraBaseView::CreateView, NULL}; за изгледен клас, в случая това е базовият клас DoraBaseView. Също така е необходимо да имплементира двете функции - CreateView и CreateDocument. За класове, които ще са изгледи функцията CreateView, връща указател към клас DoraClassObject а за документните класове би трябвало да връща нулев указател. Но аз предпочетох да разделя нещата, така че функцията CreateView, да използвам за изгледни класове, а CreateDocument за документните класове. За изгледните класове те изглеждат така:

 

DoraClassObject *DoraBaseView::CreateView(QWidget *parent = 0)

{

return new DoraBaseView(parent);

}

 

DoraClassObject *DoraBaseView::CreateDocument()

{

return NULL;

}

 

Тези функции, така написани са предназназначени за базовият изгледен клас, така че за производните му класове е необходимо да се пренапишат за конкретния клас. Тук няма до описвам виртуалните функции, защото те не са толкова интересни, и служат само за приемане на събщенията, които Qt изпраща към приложението. Какво точно изпълняват зависи от конкретното приложение. Ще можете да видите детайлно какво представляват от source кодовете, след като ги публикувам.

 

Нека сега подготвим базовият документен клас - DoraBaseDocument. Той не е нещо сложно. В него съм добавил механизъм за управление на изгледни класове, свързани с производния на него документен клас. За целта съм разработил един template клас, представляващ динамичен масив и една структура за елементите на масива и понеже нищо друго не ми идваше наум я кръстих RegistryStruct, тъй като тя ще се попълва в масива, съм декларирал променлива от тип динамичен масив, по следния начин:

typedef DoraArray<RegistryStruct> TypeRegistryItem;

и сега самата променлива

TypeRegistryItem localRegistry;

Дефиницията typedef е хубаво да бъде някъде в друг файл, където да се декларират всички масиви от този тип, за да не се претрупва излишно файлът с дефиниции на базовият документен клас. Е нека сега видим как е дефиниран този базов документен клас:

 

class DoraBaseDocument : public DoraClassObject

{

public:

DoraRuntimeClass *GetClassObject() const {

return &classDoraBaseDocument;

}

static DoraRuntimeClass classDoraBaseDokument;

static DoraClassObject *CreateView(QWidget *parent = 0);

static DoraClassObject *CreateDocument();

public:

DoraBaseDocument() : DoraClassObject() {}

~DoraBaseDocument() {}

public:

В тази секция следват няколко функции за управление на регистъра на свързаните към производните документни класове изгледи. Те извършват следното - полълват масива със елементите на структурата RegistryStruct, премахват ненужните елементи при премахване на изгледния обект, обновяват или по - точно подават сигнал за обновяване на изгледа и т.н.

private:

TypeRegistryItem localRegistry;

};

Дефиницията на класа е във файла DoraBaseDocument.h.

 

Нека сега да напишем имплементацията на класа. Това съм го направил във файла DoraBaseDocument.cpp:

 

DoraRuntimeClass DoraBaseDocument::classDoraBaseDocument={"DoraBaseDocument", NULL, DoraBaseDocument::CreateDocument};

DoraClassObject *DoraBaseDocument::CreateView(QWidget *parent = 0)

{

return NULL;

}

DoraClassObject *DoraBaseDocument::CreateDocument()

{

return new DoraBaseDocument();

}

 

Сега вече след като имаме базов изгледен клас и базов документен клас, можем спокойно да пристъпим към създаване на конкретно приложение според този програмен модел.

 

IV.Създаване на приложения по този програмен модел.

 

За да създаваме приложения с този програмен модел е необходимо да започнем от класа на приложението - DoraMainApp, който да формира класа на главния прозорец - DoraMainFrame, първият от всички изгледи на приложението - DoraCentralView и централния документен клас - DoraCentralDocument. Знаем по дефиниция, че всяка програма на С/С++, започва да се изпълнява от функция main. При мен тази функция има за задача да конструира указател към класа DoraMainApp и да извика неговата функция RunApp. Ето как изглежда това на практика във файла main.cpp.

 

 

int main(int argc, char **argv)

{

DoraRuntimeClass *doraClassApp = RUNTIME_CLASS(DoraClassApp);

DoraClassObject *doraClassObject = doraClssApp->CreateView();

mainApp = dynamic_cast<DoraClassApp *>(doraClassObject);

int result = mainApp->RunApp(argc, argv);

delete mainApp;

return result;

}

 

Дефинирайки глобалната променлива mainApp, ние получаваме глобален указател към главния клас на приложението. Където е необходимо да получим достъп до функциите и променливите на главния клас на приложението трябва да дефимираме тази променлива като extern DoraMainApp *mainApp; Но нека сега да видим какво представлява главния клас на приложението DoraMainApp, и е дефиниран във файла с дефинициите - DoraMainApp.h по следния начин:

 

class DoraMainApp : public DoraClassObject

{

public:

DoraRuntimeClass *GetRuntimeClass() const {

return &classDoraMainApp;

}

static DoraRuntimeClass classDoraMainApp;

static DoraClassObject *CreateView(QWidget *parent = 0);

static DoraClassObject *CreateDocument();

public:

int RunApp(int argc, char **argv);

public:

DoraMainFrame *mainFrame;

};

 

Както се вижда от дефиницията на класа, няма нищо необичайно в този клас, освен може би това, че този клас не е изгледен, въпреки което създаването му се извършва с функция CreateView, но той не показва никакъв прозорец. Първият прозорец на приложението е класът DoraMainFrame. Конструирането му се извършва във функция RunApp, от файла с имплементации - DoraMainApp:

 

DoraRuntimeClassDoraMainApp::classDoraMainApp = {"DoraMainApp",DoraMainApp:CreateView, NULL};

DoraClassObject *DoraMainApp::CreateView(QWidget *parent = 0)

{

return new DoraMainApp();

}

DoraClassObject *DoraMainApp::CreateDocument()

{

return NULL;

}

 

int DoraMainApp::RunApp(int argc, char **argv)

{

QApplication a(argc, argv);

QWidget *wid = QApplication::desktop();

 

a.setFont("times", 12, QFont::Normal, FALSE, QFont::AnyCharSet);

int wFrame = wid->width();

int hFrame = wid->height();

int w = (int)((wFrame * 2) / 3);

int h = (int)((hFrame * 2) / 3);

int x = (int)((wFrame - w) / 2);

int y = (int)((hFrame - h) / 2);

 

DoraRuntimeClass *runtimeMainFrame = RUNTIME_CLASS(DoraMainFrame);

DoraClassObject *objectMainFrame = runtimeMainFrame->CreateView(NULL);

mainFrame = dynamic_cast<DoraMainFrame *>(objectMainFrame);

mainFrame->OnCreate();

 

a.setMainFrame(mainFrame);

mainFrame->setGeometry(x, y, w, h);

mainFrame->show();

int result = a.exec();

 

return result;

}

 

Ще се спра по - обстойно на функцията RunApp, тъй като има доста неща които идват от библиотеката Qt. Редът QApplication a(argc, argv), еднозначно определя главния клас на приложението от гледна точка на Qt. Променливата *wid е указател към десктопа на графичната сред, в случая KDE. По нататък задавам общо валиден шрифт за приложението с израза a.setFont("times", 12, QFont::Normal, False, QFont::AnyCharSet). За повече информация за променливите и класовете на Qt, прочетете описанието на библиотеката, то е достатъчно добро и разбираемо. Следващите редове определят позицията на екрана, ширината и височината на прозореца, който се каним да покажем. В случая прозорецът има такива размери, че да покрие две трети от видимия екран.

Получаването на валиден указател на първия прозорец на приложението се извършва на три стъпки. Първо получавам указател към структурата DoraRuntimeClass с помощта на макроса RUNTIME_CLASS, Този указател се използва за конструиране на променлива от тип DoraClassObject, след извикване на функцията CreateView с нулев указател, което задава на Qt, че това е първият прозорец. Накрая преобразувам върнатия указател към указател към необходимия ми обект. Тук и навсякъде в програмния модел за преобразувне на указатели към определени обекти използвам операцията dynamic_cast<T>(v). Така аз имам RTTI (RunTime Type Information), за всеки обект сформиран по този начин. В скоби казано такова преобразуване при Microsoft изглежда така: mainFrame = (DoraMainFrame *)GetRuntimeClass(), но това си е в крайна сметка техен проблем. Методът който използвам за преобразуване на указатели е много по - безопасен и надежден.

Но да продължим нататък. С израза а.setMainFrame(mainFrame), задавам на Qt, главният прозорец на приложението - DoraMainFrame. След това задавам началната позиция x, y, ширината w и височината h, на прозореца, след което го показам. Променливата rezult съдържа резултата от изпълнението на приложението, която променлива се връща във функцията main.

Сега нека се върнем към обекта на главния прозорец на приложението - DoraMainFrame. Него съм го дефинирал във файла с дефиниции DoraMainFrame.h:

 

class DoraMainFrame : public QWidget, public DoraClassObject

{

public:

DoraRuntimeClass *GetClassObject() const {

return &classDoraMainFrame;

}

static DoraRuntimeClass classDoraMainFrame;

static DoraClassObject *CreateView(QWidget *parent = 0);

static DoraClassObject *CreateDocument();

public:

DoraMainFrame(QWidget *parent = 0, const char *name = 0) : QWidget(parent, name), DoraClassObject() {}

~DoraMainFrame() {}

public:

void OnDraw(QPainter *p);

void OnSize(int cx, int cy);

private:

void paintEvent(QPaintEvent *e);

void resizeEvent(QResizeEvent *e);

public:

DoraCentralDocument *GetDocument();

void OnCreate();

public:

DoraCentralView *m_pCentralView;

DoraBaseDocument *m_pBaseDocument;

QRect clientRect;

};

 

Така дефиниран този клас не се отличава кой знае колко от базовият клас DoraBaseView, с изключение на функцията GetDocument и двете променливи m_pCentralView и m_pBaseDocument. Функцията GetDocument връща указател към класа на главния документ, чрез променливата m_pBaseDocument, а променливата m_pCentralView, съхранява указател към първият изгледен клас на приложението. Сега нека видим какво представлява самият клас. Функциите са описани във файла с имплементации - DoraMainFrame.cpp.

DoraRuntimeClass DoraMainFrame::classDoraMainFrame={"DoraMainFrame", DoraMainFrame::CreateView, NULL};

DoraClassObject *DoraMainFrame::CreateView(QWidget *parent = 0)

{

return new DoraMainFrame(parent);

}

DoraClassObject *DoraMainFrame::CreateDocument()

{

return NULL;

}

 

Функциите OnDraw и OnSize, не са чак толкова интересни, тяхната задача е даопределят размерите на показвания прозорец и да го изрисъват. Така, че аз няма да се спирам на тях, а ще опиша функцията OnCreate, защото тя заслужава по - голямо внимание. Тази функция е входната точка на класа, определяща и конструираща обектите, които се порацдат от съответния клас, разбира се бих могъл да я направя жиртуална в някакъв базов клас, обаче така бих лишил класовете, които ще създавам от мобилност. По - добре е такава функция да е уникална за всеки клас. Какво представлява функция OnCreate, в класа DoraMainFrame.

void DoraMainFrame::OnCreate()

{

DoraRuntimeClass *runtimeDocument = RUNTIME_CLASS(DoraCentralDocument);

DoraClassObject *objectDocument = runtimeDocument->CreateDocument();

DoraCentralDocument

*centtalDocument=dynamic_cast<DoraCentralDocument*>(objektDocument();

m_pBaseDocument = dynamic_cast<DoraCentralDocument *>(centralDocument);

 

DoraRuntimeClass *runtimeView = RUNTIME_CLASS(DoraCentralView);

DoraClassObject *objectView = runtimeView->CreateView(this);

m_pCentralView = dynamic_cast<DoraCentralView*>(objectView);

m_pCentralView->OnCreate(this);

}

 

Както се вижда от описанието на OnCreate, функцията първо конструира обект от клас DoraCentralDocument, който е и първият документен клас на приложението, след това конструира обект от клас DoraCentalView, който е и първият изгледен клас на нашето приложение. Не бъркайте класа DoraMainFrame с този клас, DoraMainFrame е рамката на приложението, а DoraCentralView е първият изглед. Понеже засега се ограничавам до рамките на едно приложение, не мисля че е необходимо да запазвам указателите на създадените обекти в registry.

Нека видим сега, какво представлява функцията GetDocument. Както споменах по горе нейната единствена задача е да върне указател към централния документ на приложението DoraCentralDocument.Тя е малка и зглежда така:

DoraCentralDocument *DoraMaunFrame::GetDocument()

{

DoraCentralDocument *centralDocument;

centralDocument = dynamic_cast<DoraCentralDocument*>(m_pBaseDcument);

return centralDocument;

}

За да бъде един привично изглеждащ прозорец, остава да закачим, към него един клас за меню, един клас за лента с инструменти - ToolBar и евентуално един клас за StatusBar. Така на този етап направихме един прозорец, който може да се минимизира и максимизира, благодарение на Qt. Притежаващ меню, лента с инструменти и лента за състояние. Сега можем спокойно да го минимизираме и максимизираме до безкрайност, но това приложение все още не върши никаква полезна работа, от гледна точка на потребителите. Втакъв случай нека се опитаме да създадем нещо, което върши някаква работа. Например един клас за показване и определяне на дати и време, нещо като календарче, с включен в него часовник.

Такъв клас се конструира от класа DoraCentralView и ще се активира от менюто. Този клас нека се състои от три изгледни класа, един документен клас и два PushButtons с наименования "Промени" и "Откажи". Ще направим един изгледен клас, който ще го натоварим със задачата да управлява другите два изгледа, а именно класът CalendarView и ClockView, освен това той ще има грижата за осъществяване на комуникацията между тях и документния клас и прехвърлянето на данните от локалния документен клас и централния документен клас на приложението - DoraCentralDocument. Като начало нека декларираме две променливи в класа DoraCentralDocument - CString curentDate и CString curentTime.

Сега ще деклариераме централния изгледен клас - DateAndTimeView. Това ще направим във файла с декларации - DateAndTimeView.h:

 

class DateAndTimeView : public DoraBaseView

{

public:

DoraRuntimeClass *GetClassObject() const {

return &classDateAndTimeView;

}

static DoraRuntimeClass classDateAndTimeView;

static DoraClassObject *CreateView(QWidget *parent = 0);

static DoraClassObject *CreateDocument();

public:

DateAndTimeView(QWidget *parent = 0, const char *name = 0) : DoraBaseView(parent, name) { }

~DateAndTimeView() { }

public:

void OnDraw(QPainter *p);

void OnSize(int cx, int cy);

public:

DateAndTimeDocument *GetDocument();

void OnCreat(DoraClassObject *pObject, DoraBaseView *pView);

public:

DoraClassObject *m_pObject;

DoraBaseView *m_pView;

DoraBaseDocument *m_pDocument;

CalendarView *m_pCalendar;

ClockView *m_pClock;

public:

QRect clientRect;

QRect clockRect;

QRect calendarRect;

DoraPushButton *buttonApply;

DoraPushButton *buttonCancel;

};

 

Дължа да ви предупредя, че това не е целия клас, а само онази част, необходима за илюстрация на идеята, заради която съм сътворил целият този модел. Нека сега да видим и файла с импементации - DateAndTimeView.cpp:

 

extern DoraMainApp *mainApp;

 

DoraRuntimeClass DateAndTimeView::classDateAndTimeView = {"DateAndTimeView", DateAndTimeView::CreateView, NULL};

 

DoraClassObject *DateAndTimeView::CreateView(QWidget *parent = 0)

{

return new DateAndTimeView(parent);

}

 

DoraClassObject *DateAndTimeView::CreateDocument()

{

return NULL;

}

 

Тук повече внимание ще отделя на функциите OnCreate и GetDocument, както и на обработката на съобщенията от двата бутона - buttonApply и buttonCancel.. И така, как изглежда функцията OnCreate, от клас DateAndTimeView ?

void DateAndTimeView::OnCreate(DoraClassObject *pObject, DoraBaseView *pView)

{

m_pObject = pObject;

m_pView = pView;

 

DoraRuntimeClass *runtimeCalendar = RUNTIME_CLASS(CalendarView);

DoraClassObject *objectCalendar = runtimeClaendar->CreateView(this);

calendarView = dynamic_cast<ClaendarView *>(objectCalendar);

calendarView->OnCreate(m_pObject, this);

 

Така след конструирането на обекта CalendarView, аз му предавам указател към клас DoraClassObject и указател към родителския му клас - DateAndTimeView.. След това повтарям същата манипулация с класа ClockView, както и с двата обекта на бутони. След това конструираме клас DateAndTimeDocument.

DoraRuntimeClass *runtimeDocument = RUNTIME_CLASS(DateAndTimeDocument);

DoraClassObject *objectDocument = runtimeDocument->CreateDocument();

DateAndTimeDocument *dateAndTimeDocument = dynbamic_cast<DateAndTimeDocument *>(objectDocument);

m_pDocument = dynamic_cast<DoraBaseDocument *>(dateAndTimeDocument);

 

Така в този момент аз вече имам валидни указатели към двата изгледни класа, към двата бутона и към документния клас. Сега нека направим функцията GetDocument.

DateAndTimeDocument *DateAndTimeView::GetDocument()

{

DateAndTimeDocument *dateAndTimeDocument;

dateAndTimeDocument = dynamic_cast<DateAndTimeDocument *>(m_pDocument);

return dateAndTimeDocument;

}

Сега нека да направим взаимодействието между отделните класове. На първо място нека да опишем функцията, която ще отговаря на бутона "Промени". Тя ще има задачата да прочете данните от класовете CalendarView и ClockView, след което да постави прочетените стойности в главния документен клас на приложението, за да могат те да се използват от всички други класове от приложението. За целта в базовият изгледен клас ще дефинираме и опишем две функции, едната от които е виртуална. Тези функции трябва да реагират на натискане на един от двата бутона - "Промени" или "Откажи" . Ето как ще изглеждат те:

Във файла с декларации на класа DoraBaseView:

void PostMessage(int id_message, int id_control);

virtual void PostPushButton(int id_control);

 

И във файла с имплементации:

void DoraBaseView::PostMessage(int id_message, int id_control)

{

switch(id_message)

{

case ID_PUSH_BUTTON:

PostPushButton(id_control);

break;

.

.

.

};

}

void DoraBaseView::PostPushButton(int id_control)

{

}

Сега ще трябва да предефинираме функцията PostPushButton, в класа DateAndTimeView. И във файла с деклрации на този клас функцията има следния вид:

void PostPushButton(int id_control);

И нейното описание във файла с имплементации:

void DateAndTimeView::PostPushButton(int id_control)

{

loadDateAndTime();

switch(id_control)

{

case ID_PUSH_APPLY_BUTTON:

PostApplyButton();

break;

case ID_PUSH_CANCEL_BUTTON:

PostCancelButton();

break;

}

}

Сега трябва да дефинирме и опишем още три функции loadDateAndTime, PostApplyButton и PostCancelButton. Функцията loadDateAndTime има задачата да снеме данните от двата изгледните класове CalendarView и ClockView, след което да ги постави в документния клас DateAndTimeDocument. За целта ще дефинираме четири променливи от тип CString в този документен клас - curentDate, curentTime, oldDate и oldTime. Ето как изглежда функцията loadDateAndTime:

void DateAndTimeView::loadDateAndTime()

{

DateAndTimeDocument *pDoc = GetDocument();

pDoc->oldDate = "";

pDoc->oldDate = pDoc->curentDate;

pDoc->oldTime = "";

pDoc->oldTime = pDoc->curentTime;

 

pDoc->curentDate = "";

pDoc->curentDate = calendarView->getData();

pDoc->curentTime = "";

pDoc->curentTime = clockView->getData();

}

Функцията PostApplyButton, ще снеме данните от доцументния клас - DateAndTimeDocument и ще ги постави в централния документен клас - DoraCentralDocument. Ето как ще направим това:

void DateAndTimeView::PostApplyButton()

{

DoraCentralDocument *centralDocument = mainApp->mainFrame->getDocument();

DataAndTimeDocument *pDoc = GetDocument();

 

centralDocument->curentDate = "";

centralDocument->curentData = pDoc->curentDate;

centralDocument->curentTime = "";

centralDocument->curentTime = pDoc->curentTime;

}

Накрая остава да опишем и функцията PostCancelButton. Ето как изглежда тя:

void DateAndTimeView::PostCancelButton()

{

DateAndTimeDocument *pDoc = GetDocument();

calendarView->setData(pDoc->oldDate);

clockView->setData(pDoc->oldTime);

}

Какво всъщтност направихме до момента - на първо място осигихме механизъм за прехвърляне на данни от един клас в друг с помощта на документните класове и освен това, принудихме нашите класове да си комуникират посредством съобщения. Така например функцията loadDateAndTime, се изпълнява при промяна на данните в класовете CalendarView и ClockView, след като се извика функцията PostMessage от базовия клас - DoraBaseView, която от своя страна извиква предефинираната функция PostButtonMessage от класа DateAndTimeView. Това стана възможно благодарение на това, че обектите на приложението се конструират динамично, по време на изпълнение на приложението. Освен това даже ако премахна, отново по време на изпълнение, класа DateAndTimeView, данните ще се запазят в централния документен клас, от където ще могат да се прочетат. Класовете CalendrView и ClockView, нямат функция GetDocument, което пък ми позволява, да ги използвам в други изгледни класове, без да е необходимо да внасям в тях някакви корекции. С което създадох две блокчета, които мога да използвам във всяко мое друго пиложение.

Вървейки по тази логика на разсъждение, неминуемо ще се доберем до идеята за COM, само че в рамките на едно приложение. Така ако погледнем по - широко на нещата, можем да третираме съвокупността от класовете DoraMainFrame, DocraCentralView и DoraCentralDocument, като едно контейрено приложение, а класовете DateAndTimeView, CalendarView, ClockView и DateAndTimeDocument, като вътрешен компонент на приложението. Това според Microsoft e "очевидно фалшиво обстоятелство", но аз не разбирам защо да е фалшиво и при мен си е съвсем нормална ситуация. Тук ще си позволя да изкажа едно мое лично мнение, в смисъл че една от причините, които правят Windows във всичките му модификации е толкова тромав, е имено COM и OLE2, освен това идеята да се изпълняват части или цели приложения в рамките на друго приложение си е отворена задна врата на къща, пълна с хубави неща и където няма никой. Толкова за Microsoft и нещото наречено Windows.

 

V. Какво се постига с този програмен модел.

 

1. Аз тук говоря за Qt, която е една от най - популярните графични библиотеки в Linux, но конкретно за такъв програмен модел според мен не е особено удачна. Този програмен модел е достатъчно мобилен за да може да се използува и с други графични библиотеки.

2.След като се създадат "блокчета", по този програмен модел аз мога да сглобявам достатъчно бързо приложения, при това ще са достатъчно стабилни, поради фактаа че са във вид на source code, което би ми позволило да достигна 100% съвместимост между компонентите на дадено приложение.

3.Прилагайки подхода на класическото писане на програми, аз печеля относителна независимост от хардуера на конкретната машина и от графичната среда с която рабитя. Това е така защото съм предоставил грижата за тези важни неща на самата графична библиотека, което не е маловажно.

4. По мое скромно мнение Qt като графична библиотека, достатъчно много прилича на MFC, така че не виждам защо да няма и механизъм подобен на архитектурата Document/View.

 

VI.Посока на развитие на този модел.

 

Тук много ми се искаше да кажа, че ще развивм модела в посока на COM или нещо подобно, но за да стане това имам усещането, че ще трябва да се усигури среда, която да се грижи за пренасянето на обектите от едно приложение към друго. Което директно ни отвежда към вече направената графична среда Windows, от която всеки от нас повече или по - малко е имал главоболия. Така че засега ще се въздържа.

Смятам да довърша базовите си класове, изградени според този модел, както и класове като например - диалогови панели, списъчни изгледи и т.н. Също така един такъв програмен модел би бил непълен, без да има класове за многонишково програмиране и обработка на данни, Така че ще работя в тази насока.

 

Михаил Петров - град Смолян

16.03.2002 година.


<< Кирилица + efax + html2ps + php HOWTO | Програмиране под Линукс. Среди за програмиране >>

Авторите на сайта, както и техните сътрудници запазват авторските права върху собствените си материали публикувани тук, но те са copyleft т.е. могат свободно да бъдат копирани и разпространявани с изискването изрично да се упоменава името на автора, както и да се публикува на видно място, че те са взети от оригиналния им URL-адрес на този сървър (http://www.linux-bg.org). Авторските права на преводните материали принадлежат на техните автори. Ако с публикуването тук на някакъв материал неволно са нарушени нечии права - след констатирането на този факт материалът ще бъде свален.

All trademarks, logos and copyrights mentioned on this site are the property of their respective owners.
Linux is copyright by Linus Torvalds.
© Линукс за българи ЕООД 2007
© Slavei Karadjov 1999 - 2006

All rights reserved.

Изпълнението отне: 1 wallclock secs ( 0.16 usr + 0.03 sys = 0.19 CPU)