Eseményvezérelt alkalmazások fejlesztése I. Tantárgyfelelős: Gregorics Tibor
Tantárgyról Cél: Objektum orientált módon felépített grafikus felületű, egy-, illetve többablakos, több rétegű, eseményvezérelt alkalmazások készítése Előadás (heti 1 óra) Konzultációs gyakorlat (heti 1 óra) Eszköz: Qt, C++, Linux, UML Előfeltétel: Objektum elvű alkalmazások fejlesztése Előismeretek: Objektumelvű programozás, tervezés (UML) C++, nyelvi könyvtárak (STL) 2
Eszközök Qt keretrendszer: o Qt 5.x keretrendszer, Qt Creator fejlesztőkörnyezet (https://www.qt.io/) apt-get install qt-sdk o Qt MySQL driver apt-get install libqt5sql5-mysql o MySql Server apt-get install mysql-server o MySql Workbench apt-get install mysql-workbench A számonkérés Linux környezetben zajlik. 3
Összevont számonkérés Kötelező házi feladatok egyenként 5 pontért: o 3 darab beadandó elkészítése: helyes (tesztelt) program + dokumentáció o Határidő: legfeljebb 3 hét késés, hetenként egy jegy pont levonás, legkésőbb a géptermi zárthelyi előtt o személyesen kell bemutatni Géptermi zárthelyi 5 pontért: o félév végi évfolyam zh (egyszer ismételhető) o Használható az előadás honlapja, bármely írott/nyomtatott anyag, és a félév során leadott beadandók Gyakorlati jegy: o a beadandók, és a duplán számított zárthelyi eredményének átlaga 4
Háttér anyagok http://people.inf.elte.hu/gt/eva1 Előadások diái Házi feladatok (követelmények leírása, feladatsor, minta) Egyéb segédanyagok Qt : https://doc.qt.io Jasmin Blanchette, Mark Summerfield: C++ GUI programming with Qt 4, Prentice Hall, 2006. Giachetta Roberto anyagai: http://people.inf.elte.hu/groberto/elte_eva1/ 5
Qt keretrendszer Néhány szó a grafikus felületről, a Qt programok fordításáról, és bevezetés az eseménykezelésbe
Qt keretrendszer A Qt egy platformfüggetlen alkalmazás-fejlesztési keretrendszer, amellyel nemcsak asztali, de akár mobil vagy beágyazott alkalmazások is fejleszthetők. Támogatást ad a grafikus felület, az adatbáziskezelés, a multimédia, a 3D grafika, a hálózati és webes kommunikáció készítéséhez. Rendelkezik nyílt forráskódú (LGPL) és kereskedelmi verzióval is. Fejlesztő nyelve elsősorban a C++, de más nyelvekre is elérhető, valamint rendelkezik saját leíró nyelvvel (Qt Quick). Elérhető a qt.io oldalról. 7
Hello Qt alkalmazás #include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); // alkalmazás példányosítása QLabel mylabel("hello Qt!"); // címke a felirattal mylabel.show(); // címke megjelenítése } return app.exec(); // alkalmazás futtatása 8
Grafikus felületű alkalmazás Grafikus felületű alkalmazásnak nevezzük azt a programot, amely 2D-s interaktív felhasználói felületen (GUI, Graphical User Interface) keresztül kommunikál a felhasználóval A konzol felületnél lényegesen változatosabb interakciós lehetőséget biztosít a programfutásba történő beavatkozáshoz. A felület grafikus megjelenítéssel is ellátott vezérlőkből (control/widget) áll (mint például nyomógombok, listák, menük, stb.). Megjelenhetnek önállóan ablakként (form/window), vagy egy másik vezérlő részeként. Mindig van egy aktív ablak, és egy aktív vezérlő (ezen van a fókusz). A működését a felhasználói interakcióra történő várakozás jellemzi. 9
Példák GUI alkalmazásokra 10
Grafikus felület felépítése A grafikus felület objektumorientáltan épül fel. Egy vezérlő egy osztálynak a példánya, amely (adattagokkal) adja meg a tulajdonságait (pl. pozíció, méret, betűtípus), és (metódusokkal) a viselkedését ( pl. kattintás esetén mi történjen). Mivel a különféle vezérlők sok hasonló tulajdonsággal bírnak, így osztályaik öröklődési hierarchiába szervezhetők. Az öröklődési lánc legtetején áll az általános vezérlő osztálya, amelyből a többi vezérlő osztálya származik. A Qt keretrendszer az általános vezérlőt és az abból származtatott nevezetes vezérlőket tartalmazza, csak a saját, egyedi vezérlőink osztályait kell nekünk származtatással definiálni. A vezérlők elhelyezkedési hierarchiába rendeződnek aszerint, hogy egy vezérlő önállóan, azaz ablakként, vagy egy másik vezérlő részeként jelenik meg. 11
Grafikus felület osztályhierarchiája QObject QApplication QWidget QLayout QFrame QComboBox QButton QBoxLayout QLabel QLineEdit QPushButton QCheckBox QHBoxLayout 12
Grafikus felület osztályai QObject + staticmetaobject :QMetaObject {readonly} # d_ptr :QScopedPointer<QObjectData> # staticqtmetaobject :QMetaObject {readonly} + QObject(QObject*) + ~QObject() + event(qevent*) :bool + eventfilter(qobject*, QEvent*) :bool + tr(char*, char*, int) :QString + trutf8(char*, char*, int) :QString + metaobject() :QMetaObject * {query} + tr(char*, char*, int) :QString + tr(char*, char*) :QString + trutf8(char*, char*, int) :QString + trutf8(char*, char*) :QString + objectname() :QString {query} + setobjectname(qstring&) :void + iswidgettype() :bool {query} + signalsblocked() :bool {query} + blocksignals(bool) :bool + thread() :QThread * {query} + movetothread(qthread*) :void + starttimer(int) :int + killtimer(int) :void + findchild(qstring&) :T {query} + findchildren(qstring&) :QList<T> {query} + findchildren(qregexp&) :QList<T> {query} + children() :QObjectList & {query} + setparent(qobject*) :void + installeventfilter(qobject*) :void + removeeventfilter(qobject*) :void + connect(qobject*, char*, QObject*, char*, Qt::ConnectionType) :bool + connect(qobject*, QMetaMethod&, QObject*, QMetaMethod&, Qt::ConnectionType) :bool + connect(qobject*, char*, char*, Qt::ConnectionType) :bool {query} + disconnect(qobject*, char*, QObject*, char*) :bool + disconnect(qobject*, QMetaMethod&, QObject*, QMetaMethod&) :bool + disconnect(char*, QObject*, char*) :bool + disconnect(qobject*, char*) :bool + dumpobjecttree() :void + dumpobjectinfo() :void + setproperty(char*, QVariant&) :bool + property(char*) :QVariant {query} + dynamicpropertynames() :QList<QByteArray> {query} + registeruserdata() :uint + setuserdata(uint, QObjectUserData*) :void + userdata(uint) :QObjectUserData* {query} + destroyed(qobject*) :void + parent() :QObject * {query} + inherits(char*) :bool {query} + deletelater() :void # sender() :QObject * {query} # sendersignalindex() :int {query} # receivers(char*) :int {query} # timerevent(qtimerevent*) :void # childevent(qchildevent*) :void # customevent(qevent*) :void # connectnotify(char*) :void # disconnectnotify(char*) :void # QObject(QObjectPrivate&, QObject*) QWidget + setenabled(bool) :void + setdisabled(bool) :void + setwindowmodified(bool) :void + framegeometry() :QRect {query} + geometry() :QRect & {query} + normalgeometry() :QRect {query} + x() :int {query} + y() :int {query} + pos() :QPoint {query} + framesize() :QSize {query} + size() :QSize {query} + width() :int {query} + height() :int {query} + rect() :QRect {query} + childrenrect() :QRect {query} + childrenregion() :QRegion {query} + minimumsize() :QSize {query} + maximumsize() :QSize {query} + minimumwidth() :int {query} + minimumheight() :int {query} + maximumwidth() :int {query} + maximumheight() :int {query} + setminimumsize(qsize&) :void + setminimumsize(int, int) :void + setmaximumsize(qsize&) :void + setmaximumsize(int, int) :void + setminimumwidth(int) :void + setminimumheight(int) :void + setmaximumwidth(int) :void + setmaximumheight(int) :void + setupui(qwidget*) :void + sizeincrement() :QSize {query} + setsizeincrement(qsize&) :void + setsizeincrement(int, int) :void + basesize() :QSize {query} + setbasesize(qsize&) :void + setbasesize(int, int) :void + setfixedsize(qsize&) :void + setfixedsize(int, int) :void + setfixedwidth(int) :void +...() QPaintDevice «enumeration» RenderFlag DrawWindowBackground = 0x1 DrawChildren = 0x2 IgnoreMask = 0x4 «enumeration» InsertPolicy NoInsert InsertAtTop InsertAtCurrent InsertAtBottom InsertAfterCurrent InsertBeforeCurrent InsertAlphabetically «enumeration» SizeAdjustPolicy AdjustToContents AdjustToContentsOnFirstShow AdjustToMinimumContentsLength AdjustToMinimumContentsLengthWithIcon QComboBox + QComboBox(QWidget*) + ~QComboBox() + maxvisibleitems() :int {query} + setmaxvisibleitems(int) :void + count() :int {query} + setmaxcount(int) :void + maxcount() :int {query} + autocompletion() :bool {query} + setautocompletion(bool) :void + autocompletioncasesensitivity() :Qt::CaseSensitivity {query} + setautocompletioncasesensitivity(qt::casesensitivity) :void + duplicatesenabled() :bool {query} + setduplicatesenabled(bool) :void + setframe(bool) :void + hasframe() :bool {query} + findtext(qstring&, Qt::MatchFlags) :int {query} + finddata(qvariant&, int, Qt::MatchFlags) :int {query} + insertpolicy() :InsertPolicy {query} + setinsertpolicy(insertpolicy) :void + sizeadjustpolicy() :SizeAdjustPolicy {query} + setsizeadjustpolicy(sizeadjustpolicy) :void + minimumcontentslength() :int {query} + setminimumcontentslength(int) :void + iconsize() :QSize {query} + seticonsize(qsize&) :void + iseditable() :bool {query} + seteditable(bool) :void + setlineedit(qlineedit*) :void + lineedit() :QLineEdit * {query} + setvalidator(qvalidator*) :void + validator() :QValidator * {query} + setcompleter(qcompleter*) :void + completer() :QCompleter * {query} + itemdelegate() :QAbstractItemDelegate * {query} + setitemdelegate(qabstractitemdelegate*) :void + model() :QAbstractItemModel * {query} + setmodel(qabstractitemmodel*) :void + rootmodelindex() :QModelIndex {query} + setrootmodelindex(qmodelindex&) :void + modelcolumn() :int {query} + setmodelcolumn(int) :void + currentindex() :int {query} + currenttext() :QString {query} + itemtext(int) :QString {query} + itemicon(int) :QIcon {query} + itemdata(int, int) :QVariant {query} + additem(qstring&, QVariant&) :void + additem(qicon&, QString&, QVariant&) :void + additems(qstringlist&) :void + insertitem(int, QString&, QVariant&) :void + insertitem(int, QIcon&, QString&, QVariant&) :void + insertitems(int, QStringList&) :void +...() 13
Qt osztálykönyvtár A Qt nyelvi könyvtár osztályai egy ősosztály (QObject) leszármazottjai Jelentős részét teszik ki a vezérlők osztályai. Számos segédosztállyal rendelkezik, pl.: adatszerkezetek (QVector, QStack, QLinkedList, ) fájl és fájlrendszer kezelés (QFile, QTextStream, QDir, ) párhuzamosság és aszinkron végrehajtás (QThread, QSemaphore, QFuture, ) 14
Fejlesztés nyelve és eszközei A fejlesztés C++/Qt nyelven történik. Elérhető a teljes C++ utasításkészlet, nyelvi könyvtár. Számos C++ nyelven megszokott osztálynak létezik Qt-s megfelelője (QString, QQueue, stb.) A C++ nyelven felül további makrókat, kiegészítéseket tartalmaz, amelyeket a Meta Object Compiler (MOC) fordít le ISO C++ kódra. Az alapértelmezett fejlesztőeszköz a Qt Creator, de más környezetekben is lehetőség van a Qt fejlesztésre (pl. Code::Blocks, Visual Studio). Külön tervezőprogram (Qt Designer) áll rendelkezésre a grafikus felület létrehozásához, amely XML nyelven írja la felület felépítését, amely aztán automatikusan C++/Qt kódra fordul. 15
Fordítás A fordítás projektszinten történik, az ehhez szükséges információk a projektfájlban (.pro) tárolódnak, amely tartalmazza a felhasznált modulokat, kapcsolókat, a forrásfájlok, erőforrások (pl. kép, szöveg,) listáját az eredmény paramétereket. A fordítás akár közvetlenül is elvégezhető a fordítóval: qmake project # könyvtár nevével azonos projektfájl létrehozása qmake # fordítófájl (makefile) előállítása a projektfájlból make # projekt fájlnak megfelelő fordítás és # szerkesztés végrehajtása 16
Fordítás lépései Qt fordító Qt felület leírás (XML) Qt XML fordító Qt kód (C++) Qt MOC fordító C++ kód C++ fordító gépi kód 17
Modulok A keretrendszer felépítése modularizált. A legfontosabb modulok: központi modul (QtCore) grafikus felület (QtGui), grafikus vezérlők (QtWidgets) adatbázis-kezelés (QtSQL) hálózat-kezelés (QtNetwork) A projektben használandó modulokat a projektfájlban kell megadnunk, pl.: QT += core gui widgets sql network Egy modul tartalmát egyszerre (pl. #include <QtGui>), vagy akár osztályonként is (pl. #include <QLabel>) betölthetjük az aktuális fájlba 18
Vezérlés Egy grafikus felületű alkalmazás vezérlését egy alkalmazás objektum (a Qt-ben a QApplication osztály példánya) látja el. Beállítja az alkalmazás szintű tulajdonságokat (megjelenés, elérési útvonal). Kezeli az alkalmazás vezérlő objektumait, azokon belül a grafikus felület elemeit. A programfutás során bekövetkező különféle történéseket, akciókat (pl. billentyűzet-, vagy egérhasználat, egy objektum üzenete, stb.) eseményként értelmezi. Gondoskodik arról, hogy az események eljussanak azokhoz a vezérlőkhöz, amelyek megfelelően reagálhatnak az eseményre. 19
Akció és Esemény Az eseményeket (event) a futó alkalmazás generálja az alkalmazás működésétől függetlenül bekövetkező akciók hatására. Pl. egy kattintás az egér jobbfülével egy nyomógomb felett egérgomb-lenyomás eseményként jelentkezik a nyomógombnál. Egy akció származhat a felhasználótól (érintőképernyő, billentyűzet, egér, stb.), vagy az alkalmazás egyik objektumától (pl. időzítő), vagy akár egy másik alkalmazástól is. Az események a QEvent osztályból származtatott objektumok. (pl: QMouseEvent) o A több, mint száz különféle esemény típust enum értékek azonosítják, amelyeket a QEvent::type() add meg. Pl: QEvent::KeyPress, QEvent::MouseButtonPress o Egy eseménynek lehetnek paraméterei (arguments). Pl. egérfül lenyomásakor: melyik fület nyomták le, mi az egérmutató pozíciója. 20
Eseménykezelés Egy esemény feldolgozását több eseménykezelő metódus végzi elosztott módon, amelyek megfelelő sorrendben való hívását az alkalmazás objektum kezdeményezi. Az alkalmazás objektum először a notify() metódusát futtatja. Ez gondoskodik arról, hogy az esemény a megfelelő (fókuszban levő vagy egér mutató alatti) vezérlőhöz eljusson. A vezérlők eseménykezeléséhez rendelhetünk az installeventfilter()metódussal megfigyelő (monitoring) objektumokat is (lehet több vezérlőnek egy közös megfigyelő objektuma, sőt maga az alkalmazás objektum is ilyen), és ezek eventfilter() metódusa végezhet előzetes eseményfeldolgozást. Egy vezérlő a hozzá eljutó eseményt először az event() metódusával dolgozza, majd az esemény fajtájától függően lefut a mousepressevent(), vagy keypressevent(), vagy paintevent() kezelő is. Ha ezek valamelyike szignált is kivált, akkor a szignálhoz rendelt tevékenység (slot) végrehajtására kerülhet sor. notify() eventfilter() event() mouseevent(), keyevent(), 21
Egyedi eseménykezelés Az eseményekre történő reagálás egyedivé alakítása történhet o származtatással: valamelyik létező QWidget leszármazottból származtatunk egy új osztályt, és abban felüldefiniáljuk az eseménykezelő metódusokat. A vezérlő osztályába fordítási időben égetjük be az eseménykezelés. o objektum befecskendezéssel: megadjuk a vezérlőnek azt a megfigyelő objektumot, amelyik eventfilter() metódusa végzi az esemény előzetes kezelését. Futási időben létrehozott kapcsolat. o szignálokkal: az eseményvizsgáló metódusok az esemény hatására egy szignált (signal) váltanak ki, amelyre futási időben feliratkozthatnak olyan metódusok (ezek akár más objektumok metódusai is lehetnek), amelyek a szignált kiváltó eseményt lekezelik. Futási időben létrehozott kapcsolat (nem objektum-orientált technológia). 22
Esemény és Szignál esemény: farokhúzás eseménykezelés: ebédidőt vagy munka végét jelent szignál: csak vijjog A szignál kiváltása rugalmasabbá teszi a vezérlő felhasználását, hiszen a vezérlő kódjának módosítása nélkül tudjuk megváltoztatni egy esemény kezelését. Ne keverjük össze az esemény és a szignál fogalmait. akció: zsinór rángatás Egy fókuszban levő nyomógomb esetén esemény az enter billentyű lenyomása vagy a gomb felületén történt egérkattintás, de mindkettő a clicked() szignált váltja ki. o Amikor alkalmazunk egy vezérlőt, akkor a szignáljaival foglalkozunk, és megmondjuk, hogy melyik szignálhoz milyen eseménykezelőt akarunk társítani. o Amikor tervezünk és implementálunk egy vezérlők, akkor annak szóba jöhető eseményeire koncentrálunk, és döntünk arról, hogy azokat milyen módon kezeljünk le (közvetlenül vagy szignállal). 23
Szignál kiváltása Egy szignál kiváltása (emit) hasonlít egy eljáráshíváshoz, amely azonban nem okoz hibát akkor sem, ha az eljárás hiányzik: ilyenkor a hívás üres programként viselkedik, a szignált kiváltó esemény kezeletlen marad. Egy szignál kiváltásakor megadhatunk aktuális paramétereket, amelyet a szignálhoz rendelt eseménykezelő eljárás formális paraméterváltozóival vesz át. A formális paraméterlistának illeszkedni-e kell az aktuális paraméterekhez (sorrendben, típusban, de darabszámban nem). Egy szignálhoz rendelt eseménykezelő eljárásban mindig lekérdezhető a szignált kiváltó vezérlő objektum: a szignál küldője (sender). 24
Eseménykezelő társítása Társításnak nevezzük azt, amikor egy vezérlő szignálját összekötjük egy eseménykezelővel, amely egy tetszőleges objektum speciális (slot) metódusa kell legyen. A társítás elvégezhető bármelyik QObject leszármazott típusban, vagy statikus metódus hivatkozással. Qt-ban ehhez a connect metódust használjuk, pl.: connect(&button, SIGNAL(clicked()), &app, SLOT(quit())); Ennek paraméterei: a szignált kiváltó küldő objektum (sender) a szignált (SIGNAL) a fogadó objektum (receiver) A fogadó metódusaként definiált eseménykezelő (SLOT). 25
Quit alkalmazás #include <QApplication> #include <QPushButton> int main(int argc, char *argv[]) { QApplication app(argc, argv); // alkalmazás QPushButton quit("quit"); // gomb quit.resize(75, 30); // méret quit.setfont(qfont("times", 18, QFont::Bold)); // betűtípus QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit())); quit.show(); // gomb megjelenítése } return app.exec(); 26