|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +codename: AlkFejlHF34 |
| 4 | +title: jeti123 csapat tanulságai |
| 5 | +tags: alkfejl afhf skipfromindex |
| 6 | +authors: Csőke Lóránt, Frank János, Werkmann Virág |
| 7 | +--- |
| 8 | + |
| 9 | +# A jeti123 csapat tanulságai az Alkalmazásfejlesztés házi feladattal kapcsolatban. |
| 10 | + |
| 11 | +## .c .h fileok |
| 12 | + |
| 13 | +Mindenképpen javasolt szépen struktúrált .cpp és .h fileokat készíteni, melyeknek beszédes neveik vannak. Ez jelentősen megkönnyíti a projekt átláthatóságát, valamint debuggolásnál is egyértelmű lesz miért és hova ugrálunk. |
| 14 | + |
| 15 | +## Osztályok, öröklés |
| 16 | + |
| 17 | +Az előbb leírtak itt is érvényesek, beszédes osztályneveket ajánlott kitalálni. Ne féljünk, ha esetleg hosszúra sikerül, inkább legyen átlátható, a code completion úgy is megkönnyíti a gépelést. Kiemelném, hogy az öröklési mechanizmus egy rendkívül hasznos funkciója a c++ -nak. Ne sajnáljuk az időt attól, hogy egy korrekt ősosztályt alkossunk meg, melyből örökölt példányok könnyebben átláthatóak. |
| 18 | + |
| 19 | +Esetünkben a `` WindowEventHandling `` osztályból származnak a szimulátor és a monitor ablakkezelői, rendre `` MonitorWindowEventHandling `` és `` SimulatorWindowEventHandling `` . Az ősosztály tartalmazza a QML oldali elemek keresésére szolgáló `` FindItemByName `` függvényeket, protected öröklési mechanizmussal. A konstruktora átveszi a `` QQmlContext `` objektumot, mely tovább öröklődik. Szintén itt kerül deklarálásra az ablakkezelők signal-slot mechanizmusához szükséges virtual öröklődésű `` ConnectQmlSignals `` függvénye, mely az alosztályokban az override kulcsszóval felülírható mint pl.: `` void ConnectQmlSignals(QObject *rootObject) override; `` Ennek segítségével az örököltetett osztályok saját módon írhatják felül az adott függvényt. |
| 20 | + |
| 21 | +## c++ és QML környezet közötti változó átadás |
| 22 | + |
| 23 | +Ez talán a grafikus felület egyik alapvető működési feltétele. Ha nem vagyunk képesek átadni a cpp oldali változóinkat a grafikus felületet alkotó QML-nek, szinte semmi értelme GUI-t készíteni. Ez persze visszafelés is igaz, hiszen olykor pl. egy csúszkával beállított értéket kell a cpp oldalon megvalósított funkciónknak átadni. |
| 24 | + |
| 25 | +A probléma megoldása a következő: |
| 26 | + |
| 27 | +#### cpp -> QML: "QString" |
| 28 | +* Az átadni kívánt változó létrehozása: |
| 29 | +```cpp |
| 30 | +QString testResult = (testOK == true) ? "OK" : "NOK"; |
| 31 | +``` |
| 32 | +* Átadás a QML környezetnek "testResultText" néven: |
| 33 | +```cpp |
| 34 | +qmlContext.setContextProperty(QStringLiteral("testResultText"), QVariant::fromValue(testResult)); |
| 35 | +``` |
| 36 | +* Ezzel létrejött egy `` testResultText `` nevű változó a QML oldalon, melyet tetszőleges módon használhatunk a QML környezetben. |
| 37 | + |
| 38 | +#### cpp -> QML: "QVector" |
| 39 | +* Az átadni kívánt vektor: |
| 40 | +```cpp |
| 41 | +QVector<int> activeAlarmZones; |
| 42 | +``` |
| 43 | +* Az átadáshoz deklarálni kell egy `` QVarianList `` nevű változót, melyet fel kell tölteni az átadni kívánt tömb elemeivel: |
| 44 | +```cpp |
| 45 | +QVarianList alarmZonesQML; |
| 46 | +``` |
| 47 | +* A feltöltés: |
| 48 | +```cpp |
| 49 | +QVariantList alarmzone = QVariantList{}; //Átmeneti változó |
| 50 | +for(int i = 0; i < activeAlarmZones.length(); i++) |
| 51 | +{ |
| 52 | + int zone = activeAlarmZones[i]; |
| 53 | + alarmzone.append(QVariant::fromValue(zone)); |
| 54 | +} |
| 55 | +alarmZonesQML = alarmzone; |
| 56 | +``` |
| 57 | +* Átadás a QML környezetnek `` alarmZones `` néven: |
| 58 | +```cpp |
| 59 | +qmlContext.setContextProperty(QStringLiteral("alarmZones"), QVariant::fromValue(alarmZonesQML)); |
| 60 | +``` |
| 61 | + |
| 62 | +#### cpp -> QML: "2D vektor" |
| 63 | +* A probléma a következő: |
| 64 | +A szimulátorunk egy házat modellez, melyben több szoba van. A szobák hőmérséklete különbözik, melyet szeretnénk átadni a QML környezetnek. Az ábrázolhatóság érdekében azonban szeretnénk az utolsó N értéket megjeleníteni ezzel minden szobához N hőmérséklet tartozik. Az így keletkezett 2D tömb pl. a következőképpen nézhet ki: |
| 65 | +Szoba1: | T1,1 | T1,2 | T1,3 | ... | T1,N | |
| 66 | +Szoba2: | T2,1 | T2,2 | T2,3 | ... | T2,N | |
| 67 | +Szoba3: | T3,1 | T3,2 | T3,3 | ... | T3,N | |
| 68 | +Szoba4: | T4,1 | T4,2 | T4,3 | ... | T4,N | |
| 69 | +* Az átadáshoz deklarált változó: |
| 70 | +```cpp |
| 71 | +QVariantList roomTemperaturesQML; |
| 72 | +``` |
| 73 | +* Az adatok másolása és a 2D tömb létrehozása: |
| 74 | +A "container" eltárolja a szobák állapotát minden új állapot érkezésekor, azaz ebben megtalálható az utolsó N állapot. |
| 75 | +```cpp |
| 76 | +for (int i = 0; i < container[0].get()->rooms.length(); i++)//Ciklus végig a szobákon |
| 77 | +{ |
| 78 | + auto index = container.size() - N; //Megjelenítendő utolsó N adat első elemének helye |
| 79 | + |
| 80 | + QVariantList ll = QVariantList{};//Átmeneti QVariantList típusú változó |
| 81 | + for(; index != container.size(); index++) //Ciklus végig az utolsó N bejegyzésen |
| 82 | + { |
| 83 | + float temp = container[index].get()->rooms[i].temperature; //Egy érték kinyerése |
| 84 | + ll.append(QVariant::fromValue(temp)); //ll lista feltöltése 1. dimenzió |
| 85 | + } |
| 86 | + l << QVariant::fromValue(QVariantList(ll)); //Az ll lista hozzáfűzése az l listához: 2. dimenzió |
| 87 | +} |
| 88 | +roomTemperaturesQML = l; // a 2D lista másolása az átadni kívánt változóba |
| 89 | +``` |
| 90 | +* Átadás a QML környezetnek `` roomTemperatures `` néven: |
| 91 | +```cpp |
| 92 | +qmlContext.setContextProperty(QStringLiteral("roomTemperatures"), QVariant::fromValue(roomTemperaturesQML)); |
| 93 | +``` |
| 94 | +* A QML oldalon az ismert módon érjük el a tömb elemeit: |
| 95 | +```cpp |
| 96 | +var T = graphTemperatures[i][j]; |
| 97 | +``` |
| 98 | + |
| 99 | +## Kommunikáció a QML oldalról a CPP oldalnak |
| 100 | +* Ezt könnyen megtehetjük a "signal & slot" mechanizmussal. A cpp oldalon definiáljuk a QML oldalról meghívandó slotot pl.: |
| 101 | +```cpp |
| 102 | +public slots: |
| 103 | + void setHeatingSetPoint(int value); |
| 104 | +``` |
| 105 | +* A QML oldalon létrehozunk egy signalt: |
| 106 | +```cpp |
| 107 | +signal setHeatingSetPointCpp(int value); |
| 108 | +``` |
| 109 | +* A QML ojektumunk, melyet majd meg kell keressünk a CPP oldalról `` <objectName> `` néven: |
| 110 | +```cpp |
| 111 | +Item |
| 112 | +{ |
| 113 | + objectName: "controlPanel" |
| 114 | + ... |
| 115 | + //Ezen objektumon belül található majd az a slider pl. mely az adott értéket változtatni akarja. |
| 116 | +} |
| 117 | +``` |
| 118 | +* Összekötés a cpp slottal: |
| 119 | +```cpp |
| 120 | +QQuickItem *controlPanel = FindItemByName(rootObject,QString("controlPanel")); //Megkeressük a QML "controlPanel" objektumát |
| 121 | +if (controlPanel) //Ha megtaláltuk összekapcsoljuk a QML signalt a cpp slottal: |
| 122 | + QObject::connect(controlPanel, SIGNAL(setHeatingSetPointCpp(int)), this, SLOT(setHeatingSetPoint(int))); |
| 123 | +else //Ha nem, sírunk... |
| 124 | + qDebug() << "HIBA: Nem találom a controlPanel objektumot a QML környezetben."; |
| 125 | +``` |
| 126 | +* A "slider" elem blokkján belül létrehozunk egy JS függvényt, mely az érték megváltozásakor meghívja a cpp oldalon regisztrált signalt: |
| 127 | +```cpp |
| 128 | +Slider |
| 129 | +{ |
| 130 | + function setHeating() {setHeatingSetPointCpp(heatingSlider.value)} //JS függvény |
| 131 | + id:heatingSlider |
| 132 | + onValueChanged: setHeating() //JS függvény meghívása, mely a CPP signalt aktiválja |
| 133 | +} |
| 134 | +``` |
| 135 | +* Az így meghívódott cpp oldali `` setHeatingSetPoint `` függvényünk tetszőlegesen használhatja az átadott értéket. |
| 136 | + |
| 137 | +## Grafikonok rajzolása qml oldalon |
| 138 | +Ha nagyon nincs ötlete az embernek, hogyan lehet qml-ből grafikont rajzolni, akkor érdemes szétnézni a példák között. A Qt Creator Welcome felületéről is elérhetőek gyorsan, az Examples gomb alatt. Ezek közül mi a Qml Weather és Qml F1 Legends példa alapján készítettük el a saját grafikonunkat. Ebben a snippet-ben a ChartView általunk felfedezett lehetőségeit szeretnénk bemutatni. |
| 139 | + |
| 140 | +#### ChartView |
| 141 | +Egyszerűen lehet grafikont rajzoltatni vele, mi a hőmérséklet kijelzésére használtuk. Az idő előre haladtával a grafikon is változott, mindig az utolsó pár értéket jelenítette meg. |
| 142 | +```cpp |
| 143 | +ChartView |
| 144 | +{ |
| 145 | + id: chartView |
| 146 | + animationOptions: ChartView.SeriesAnimations |
| 147 | + antialiasing: true |
| 148 | + |
| 149 | + ValueAxis{ |
| 150 | + id: valueAxisY |
| 151 | + min: 10 |
| 152 | + max: 40 |
| 153 | + titleText: "Temperature [°C]" |
| 154 | + } |
| 155 | + |
| 156 | + LineSeries { |
| 157 | + id: tempNappali |
| 158 | + axisX: valueAxisX |
| 159 | + axisY: valueAxisY |
| 160 | + name: "Nappali" |
| 161 | + } |
| 162 | +// ... |
| 163 | +``` |
| 164 | +* `` ValueAxis `` segítségével megadhatjuk, hogy mi legyen a tengelyek neve, a tengelyen mi legyen a minimális és maximálisan kijelzett érték. A minimum és a maximum menet közben dinamikusan változtatható. Példáil: `` chartView.axisY().max = 30 `` |
| 165 | +* `` LineSeries `` segítségével fogjuk kezelni a kirajzolandó adatokat. Az `` id `` azonosítja a vonalat, a `` name `` pedig legendnél jelenik meg. |
| 166 | +* `` tempNappali.append(currentIndex,i1); `` sorral tudunk értéket hozzáfűzni a grafikonunkhoz. A `` currentIndex `` mondja meg, hogy az x tengely melyik értékéhez tartozzon az `` i1 `` változóban megadott érték. |
| 167 | +* A grafikon mozgatását úgy oldottuk meg, hogy az x tengelyhez tartozó minimum és maximum értékeket változtattuk, így mindig csak az utolsó 45 érték volt megjelenítve. |
| 168 | +```cpp |
| 169 | +if (currentIndex > 43) { |
| 170 | + chartView.axisX().max = currentIndex + 1; |
| 171 | + chartView.axisX().min = chartView.axisX().max - 45; |
| 172 | + } else { |
| 173 | + chartView.axisX().max = 45; |
| 174 | + chartView.axisX().min = 0; |
| 175 | + } |
| 176 | +``` |
| 177 | + |
| 178 | +## Befejezésül |
| 179 | +A házi feladattal rendkívül sokat tanultunk, nem csak QML és CPP programozás terén, de abban is jelentős tapasztalatra |
| 180 | +tettünk szert, hogyan kell megfelelően struktúrált kódot készíteni és hogyan kell azt átláthatóan ledokumentálni. Reméljük a leírtakkal segíteni tudtunk mindazoknak, akik még az első lépéseket teszik a házi feladat megoldása során. |
0 commit comments