QtQmlDemo

Ha a Qt Creator indításakor Qt Quick Applicationt választunk, akkor rögtön kaptunk egy kis alkalmazás keretet, amivel QML-es alkalmazást tudunk készíteni. (A QtQuick az az osztálykönyvtár, aminek a felhasználó felület leíró nyelve a QML.)

A kiindulási alap így néz ki:

Elindítva az alkalmazást az alábbi ablakot kapjuk:

A File/Exit kilép, a többi művelet csak kiírja, hogy megtörtént az esemény.

Nézzük most végig, hogy miből áll ez a kis példaprogram.

main.cpp

A main.cpp tartalma igen tömör:

#include <QApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

Létrehoz egy QApplication objektumot, aminek az app.exec() hívásával indul el az ablakkezelő rendszer eseménykezelő főciklusa. Vagyis a főprogram összerakja a felhasználói felületet, majd amíg ki nem lépünk, eseményekre vár és minden eseménynél végrehajtja a megfelelő eseménykezelőt.

Ezen kívül létrejön egy QQmlApplicationEngine objektum is, ami pedig a QML felület betöltéséért, felépítéséért és üzemeltetéséért felelős.

(A QStringLiteral majdnem ugyanaz, mintha QString()-et írnánk, csak fordítási időben létre tud jönni egy read-only QString objektum, így futási időben sokkal gyorsabb.)

Ami itt igazán érdekes, az a QML fájl és az elérési útja (URI-je): a QRC fájl egy un. resource leíró fájl. Ezt a bal oldali könyvtárszerkezetben is láthatjuk. A lényege, hogy az ebben a qml.qrc fájlban szereplő fájlok belefordulnak az exe fájlba, így a program viszi őket magával, nem kell külön fájlként odamásolni őket a telepítéskor. Ilyen erőforrások tipikusan a QML fájlok és például képek szoktak lenni.

qml.qrc

A QRC fájl tartalma az alábbi (jobb klikk, Open in Text Editor):

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
        <file>MainForm.ui.qml</file>
    </qresource>
</RCC>

A lényege, hogy az itt felsorolt - egyébként a projektben létező - fájlok bekerülnek a fordításkor az exe fájlba. Hivatkozni rájuk úgy lehet, ahogy a main() függvényben is láttuk, pl. “qrc:/main.qml”. (A “/” prefixet már a QRC fájlban adtuk meg.)

pro fájl

Még mielőtt a QML részbe belemennénk, nézzük meg a QtQmlDemo.pro projekt fájlt is.

TEMPLATE = app

Ez jelenti azt, hogy egy futtatható alkalmazást szeretnénk. (És nem például libraryt.)

QT += qml quick widgets

Szükségünk lesz a QT keretrendszerből a qml, quick és widgets részekre. Widgeteket most nem használunk, viszont a QApplication osztály használatához is ez kell. A háttérben ezek a beállítások példul bizonyos include és library fájl csoportok elérési útjait veszik hozzá a projektünkhöz.

SOURCES += main.cpp

Forrás fájlunk csak egy van, a main.cpp.

RESOURCES += qml.qrc

Resource leíró fájlunk is csak egy van, a qml.qrc (ami egyébként több fájlra hivatkozik, de ez más kérdés.)

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =

Ha külső QML fájlokat is szeretnénk használni, itt adhatnánk meg a helyüket. Péládul ha egy nagyon szép grafikon rajzoló QML komponenst letöltünk.

# Default rules for deployment.
include(deployment.pri)

Itt egy másik projekt fájlt includeolunk. A kiterjesztése azért pri és nem pro, hogy ez is jelezze, hogy ezt includeolásra szánták.

A deployment.pri tartalmába most nem megyünk bele. Sok esetben nem is kell hozzányúlni. Elsősorban akkor van rá szükség, ha a program telepítése valamiért speciális, például mert Android alatt processzor architektúra függő, hogy mi hol van a fájlrendszerben.

MainForm.ui.qml

Ennek a fájlnak a tartalmát egyrészt megnézhetjük a QML designerben is, vagy szövegszerkesztőben is. (A bal oldali eszköztárban a “Design” vagy “Edit” gombokat kiválasztva.)

Ez a fájl egy összetett felhasználó felület elemet ír le: azt, ami a menü kivételével az alkalmazás ablakában megjelenik. Van rajta sorba rendezve 3 nyomógomb.

import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1

Itt különböző osztály csoportokat importálunk, hogy használhassuk őket. A nevük után a verziószámot is meg kell adni. Ezek a sorok kb. be is importálnak mindent, amire egy nem túl bonyolult felület összerakásához szükségünk van.

Item {

Ez egy új felhasználói felület elem lesz. Mellesleg összetett, mert benne további elemek vannak.

    width: 640
    height: 480

Minden elemnek vannak tulajdonságai, propertyjei. Ezeket itt simán, számszerűen is beállíthatjuk, de majd később látni fogjuk, hogy akár JavaScript kódot is használhatunk a meghatározásukhoz, vagy az adatkötések segítségével megadhatjuk, hogy az értéküket mindig valahonnan máshonnan vegyék.

    property alias button3: button3
    property alias button2: button2
    property alias button1: button1

A három Button control elég mélyen van a hierarchiában, így a könnyebb elérhetőség érdekében itt egy aliast hozunk létre nekik: kintről lehet majd úgy hivatkozni rájuk, mintha ezen a szinten lennének, közvetlenül az Item alatt, button1, button2 és button3 néven.

    RowLayout {

A layout egy elrendezést irányító elem. Ami benne van, azokat úgy helyezi el, hogy sorban legyenek.

        anchors.centerIn: parent

Maga a RowLayout is rendelkezik tulajdonságokkal. Ilyen az anchors. Itt lehet megadni, hogy a RowLayout elem határai hova igazodjanak. Itt most bárhogy is mozog vagy méreteződik az ablak, ő a szülőjének a közepén lesz. (Itt lehet például olyanokat is megadni, hogy minden irányba töltse ki a szülőt, ami jelen esetben az eggyel kintebb definiált Item.)

        Button {

Most egy Button osztályú elem jön.

            id: button1

Ha konkrét azonosítót is szeretnénk a nyomógombnak adni, hogy tudjunk rá hivatkozni máshonnan, azt az “id” tulajdonság beállításával tudjuk megtenni. Eddig erre nem volt szükség, mert nem akarunk például magára a RowLayoutra hivatkozni. De most kelleni fog.

            text: qsTr("Press Me 1")

A Buttonnak van egy text tulajdonsága, amit egy konkrét szövegre állítunk be. A qsTr() makrónak annyi feladata van, hogy jelzi, hogy ezt a stringet ha nemzetközi programot fejlesztünk, le kell fordítani, mert megjelenik a felhasználói felületen. Erre is van a Qt-nek támogatása, de ebbe most nem megyünk bele.

A további két Button hasonló az elsőhöz.

        }

        Button {
            id: button2
            text: qsTr("Press Me 2")
        }

        Button {
            id: button3
            text: qsTr("Press Me 3")
        }
    }
}

Mint látható, itt a felhasználói felület felépítését adtuk meg, de a viselkedésről semmit nem mondtunk. Ez azért van, mert ez egy általánosabban használható felhasználói felület elem (bár most nyilván csak a main.qml-ből fogjuk egyszer példányosítani).

A lényeg, hogy a viselkedést majd ott adjuk meg, ahol felhasználjuk ezt a felület elemet.

main.qml

A main.qml a felhasználói felületünk gyökéreleme. Definiál egy ablakot, benne egy menüt, valamint példányosítja a MainForm elemet (ami a MainForm.ui.qml fájlban van leírva) és beállítja az eseménykezelőket.

import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2

ApplicationWindow {
    title: qsTr("Hello World")
    width: 640
    height: 480
    visible: true

Az ApplicationWindow az alkalmazás főablaka. Beállítjuk a címét, méretét és láthatóságát.

    menuBar: MenuBar {
        Menu {
            title: qsTr("&File")
            MenuItem {
                text: qsTr("&Open")
                onTriggered: messageDialog.show(qsTr("Open action triggered"));
            }
            MenuItem {
                text: qsTr("E&xit")
                onTriggered: Qt.quit();
            }
        }
    }

A menüsor egy MenuBar osztályú elem. A “menuBar : MenuBar” azt jeleti, hogy az ApplicationWindow menuBar tulajdonságának adunk értéket úgy, hogy az egy MenuBar osztályú elem lesz, aminek itt helyben adjuk meg az értékét. A MenuBar tartalmaz egy Menu elemet (ez lesz a File menü), aminek van címe, valamint benne vannak MenuItem-ek (Open és Exit). A címekben az “&” jel határozza meg a billentyűparancsokat: az Alt-F, Alt-O és Alt-x az egyes menüpontokat fogják aktiválni.

Ezeknek az elemeknek már eseményeik (signaljaik) is vannak. QML alatt az eseményeknek megfelelő signalokat JavaScript kódrészletekhez tudjuk kötni. A signal neve “Triggered”, amihez kódrészletet az “onTriggered”-nek értéket adva tudunk rendelni. A fenti esetben az Open triggerére meghívjuk a messageDialog objektum show() metódusát, az Exit esetében pedig a Qt.quit() hívással leállítjuk a programot.

    MainForm {
        anchors.fill: parent
        button1.onClicked: messageDialog.show(qsTr("Button 1 pressed"))
        button2.onClicked: messageDialog.show(qsTr("Button 2 pressed"))
        button3.onClicked: messageDialog.show(qsTr("Button 3 pressed"))
    }

Itt példányosítjuk a MainForm elemet. Az “anchors.fill” tulajdonságnak megadhatjuk, hogy az elem mely elemet töltse ki teljesen. Most a szülőt, ami az ApplicationWindow. Ezen kívül beállítunk eseménykezelőket is, miközben kihasználjuk, hogy a nyomógombok bár a MainForm mélyén vannak (egy RowLayout alatt), aliasokon keresztül könnyen el tudjuk őket érni.

    MessageDialog {
        id: messageDialog
        title: qsTr("May I have your attention, please?")

        function show(caption) {
            messageDialog.text = caption;
            messageDialog.open();
        }
    }

Végül létrehozunk még egy MessageDialog objektumot is az ablakon belül, ami egy felugró üzenet ablakot jelent. Adunk neki “id”-t, mert így tudunk rá hivatkozni az eseménykezelőkben. Beállítjuk a title tulajdonságot, valamint definiálunk benne egy JavaScript függvényt. Olyan ez, mintha egy MessageDialog osztályból származtattunk volna egy sajátot, és kiegészítjük egy újabb metódussal. A függvény pedig nem tesz mást, mint a paraméterül kapott szövegre állítja a text tulajdonságot, majd feldobja a dialógus ablakot. (A JavaScript gyengén típusos nyelv, a paraméter típusát nem kell megadni. Lehet bármi, amit a változót használó kódrészek le tudnak majd kezelni.)

}

Összefoglalás, a qrc_qml.cpp

Ezzel a végére is értünk a Qt QML-es példa keretprogramjának. A fordításkor megnézve a konzolt egy érdekes részre lehetünk figyelmesek:

C:\Qt\5.4\mingw491_32\bin\rcc.exe -name qml ..\QtQmlDemo\qml.qrc -o debug\qrc_qml.cpp

g++ -c -pipe -fno-keep-inline-dllexport -g -frtti -Wall -Wextra -fexceptions -mthreads -DUNICODE -DQT_QML_DEBUG -DQT_DECLARATIVE_DEBUG -DQT_QUICK_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I..\QtQmlDemo -I"C:\Qt\5.4\mingw491_32\include" -I"C:\Qt\5.4\mingw491_32\include\QtQuick" -I"C:\Qt\5.4\mingw491_32\include\QtWidgets" -I"C:\Qt\5.4\mingw491_32\include\QtGui" -I"C:\Qt\5.4\mingw491_32\include\QtQml" -I"C:\Qt\5.4\mingw491_32\include\QtNetwork" -I"C:\Qt\5.4\mingw491_32\include\QtCore" -I"debug" -I"." -I"C:\Qt\5.4\mingw491_32\mkspecs\win32-g++" -o debug\qrc_qml.o debug\qrc_qml.cpp

Az első sor egy rcc.exe program segítségével a qml.qrc fájlból készít egy qrc_qml.cpp fájlt, a második pedig hozzáfordítja azt a projekthez.

Ennek a generált cpp fájlnak igen tanulságos, bár mérsékelten olvasható a tartalma.

[...]

static const unsigned char qt_resource_data[] = {
  // E:/Projektek/alkalmazasfejlesztes/QtQmlDemo/MainForm.ui.qml
  0x0,0x0,0x2,0x42,
  0x69,
  0x6d,0x70,0x6f,0x72,0x74,0x20,0x51,0x74,0x51,0x75,0x69,0x63,0x6b,0x20,0x32,0x2e,
  0x34,0xd,0xa,0x69,0x6d,0x70,0x6f,0x72,0x74,0x20,0x51,0x74,0x51,0x75,0x69,0x63,
  0x6b,0x2e,0x43,0x6f,0x6e,0x74,0x72,0x6f,0x6c,0x73,0x20,0x31,0x2e,0x33,0xd,0xa,
  0x69,0x6d,0x70,0x6f,0x72,0x74,0x20,0x51,0x74,0x51,0x75,0x69,0x63,0x6b,0x2e,0x4c


[...]

static const unsigned char qt_resource_name[] = {
  // MainForm.ui.qml
  0x0,0xf,
  0x5,0xe3,0xf8,0x3c,
  0x0,0x4d,
  0x0,0x61,0x0,0x69,0x0,0x6e,0x0,0x46,0x0,0x6f,0x0,0x72,0x0,0x6d,0x0,0x2e,0x0,0x75,0x0,0x69,0x0,0x2e,0x0,0x71,0x0,0x6d,0x0,0x6c,

[...]

A fájl binárisan tartalmazza az erőforrás fájlok tartalmát és neveit. Az első blokk a tényleges adattartalom, a második pedig egy részlet a fájlok neveiből. A nevek UTF-16 kódolással szerepelnek, vagyis minden karakter 2 bájt. A “0x0, 0x4d” péládul az M betű a MainForm elejéről.

Szerzők, verziók: Csorba Kristóf