Q_INVOKABLE oder wie kann ich Qt Funktionen in Qml benutzen?

Wir möchten in unserem Beispiel die Qt Funktion currentDateTime() in unserem Qml File benutzen um die aktuelle Zeit anzuzeigen.

Dazu fügen wir in unser myclass.h File die Beschreibung Q_INVOKABLE ein, was soviel heist wie die Funktion in Qml aufrufbar machen.

class MyClass : public QObject
{
    Q_OBJECT 
public:
    MyClass();
    ~MyClass();

    Q_INVOKABLE QDateTime getCurrentDateTime() //<--- unser Name für die in qml Aufrufbare Funktion 
     const {
        return  QDateTime::currentDateTime(); //Eigentliche Qt-Funktion
     }

signals:
    void setlabeltext(QString text);
public slots:
    void cppSlot(QString msg);
};

Jetzt müssen wir nur noch die Funktion irgendwie in unserem Qml file Aufrufen,

doch zuerst fügen wir in unser main.qml main_window über den Designer ein Rechteck und ein neues Label für Datum und Uhrzeit ein.

datum_uhrzeit

Diesen Label Text wollen wir nun jede Sekunde aktualisieren dazu verwenden wir die Vorher gelernte Möglichkeit C++ Signale mit Qml zu verbinden. (Den TextNamen text1 ändern wir noch auf datumzeit.)
Anschließend erstellen wir uns einen Timer und ein Signal sowie einen Slot in unserer myclass.h

#include <QObject>
#include <QDateTime>
#include <QDebug>
#include <QTimer>

class MyClass : public QObject
{
    Q_OBJECT

public:
    MyClass();
    ~MyClass();
    QTimer * SecondsTimer;
    Q_INVOKABLE QDateTime getCurrentDateTime()
     const {
        return  QDateTime::currentDateTime();
     }
signals:
    void setlabeltext(QString text);
    void emit_timer_second();

public slots:
    void cppSlot(QString msg);
    void emit_timer();
};

Unsere main.cpp erweitern wir um den Slot emit_timer() und füllen den Konstruktor MyClass mit etwas Leben das einen Timer erzeugt,verbindet und startet.

#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include "myclass.h"
#include <QQmlContext>
#include <QDateTime>
#include <QtCore>
#include <QDebug>
#include <QQuickItem>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QtQuick2ApplicationViewer viewer;

    MyClass data;
    viewer.rootContext()->setContextProperty("myclassData", &data);
    viewer.setMainQmlFile(QStringLiteral("qml/qtquick_uebung/main.qml"));

    //Verbindung von C++ über Signal Slots nach QML
    QString msg = "Dieser Text kommt von c++";
    emit data.setlabeltext(msg);

    //Verbindung von QML über Signal Slots nach C++
    QObject* viewerobject = viewer.rootObject();
    QObject::connect(viewerobject,SIGNAL(qmlSignal(QString)),&data,SLOT(cppSlot(QString)));

    MyClass(); //<-- hier wird myclass Aufgerufen
    viewer.showExpanded();
    return app.exec();
}

MyClass::MyClass()
{
    SecondsTimer = new QTimer(this);
    QObject::connect(SecondsTimer,SIGNAL(timeout()),this,SLOT(emit_timer()));
    SecondsTimer->start(1000);
}

MyClass::~MyClass()
{ 
}

void MyClass::cppSlot(QString msg)
{
  qDebug() << QString("Called the C++ slot with message:%1").arg(msg);
}

void MyClass::emit_timer()
{
    emit emit_timer_second();
}

Der Slot emit_timer() wird nun jede Sekunde aufgerufen und emittiert das Signal emit_timer_second(), dieses Signal fangen wir nun noch in Qml auf und setzen unsere Q_INVOKABLE Funktion ein um das DatumZeit Label auf die aktuelle Systemzeit zu setzen. Das Target für unser Signal ist immer noch myclassData daher fügen wir nun auch diesen Slot in Connections ein (Großbuchstabe beachten)

  Connections {
                   target: myclassData
                   onSetlabeltext: {
                     labeltext.text=text
                   }

                   onEmit_timer_second: {
                     datumzeit.text=Qt.formatDateTime(myclassData.getCurrentDateTime(),"dd.MM.yyyy:hh.mm.ss") //<--datumzeit ist unser neues label das wir nun setzen 
                   }
}

  Rectangle {           //die neuen Elemente Hellgruenes Rechteck und Text datumzeit
        id: rectangle1
        x: 0
        y: 0
        width: 360
        height: 27
        color: "#4dc635"

        Text {
            id: datumzeit
            x: 0
            y: 0
            width: 360
            height: 27
            text: Qt.formatDateTime(myclassData.getCurrentDateTime(),"dd.MM.yyyy:hh.mm.ss") //wenn wir von anfang an die aktuelle Zeit sehen wollen müssen wir den Text vorher setzen
            horizontalAlignment: Text.AlignRight
            font.pixelSize: 21
        }
    }

und voila wir bekommen das aktuelle Datum und aktuelle Zeit in unserem Label angezeigt

q_invokable

QML Signal nach C++ Slot und wieder zurück

Wir wollen über Signal und Slot mit unserer QML Datei kommunizieren wie wir es von unseren normalen Qt UI Anwendungen gewohnt sind.

Dazu legen wir erst einmal ein Signal in unserer main.qml Datei an

Rectangle {
    id: main_window
    property int index: 0
    width: 360
    height: 360
    color: "#1e864b"
    radius: 0
    transformOrigin: Item.Center
    signal qmlSignal(string msg) //Hier wird ein signal angelegt das einen Qstring als Parameter verwendet
 ...

Anschließend müssen wir dieses Signal noch in unserer qml Datei emitieren.
Hier wurde der emit in dem onClicked: Teil unseres Buttons eingefügt

    onClicked:{
            index++
            if (index>=3)index=0;
            if (index==0)main_window.state=""
            if (index==1)main_window.state="State1"
            if (index==2)main_window.state="State2"
            main_window.qmlSignal("Hello from QML") // Hier wird das signal qmlSignal emittiert es gehört zur id main_window

        }

So nun Benötigen wir noch einen Slot und einen connect in unserer C++ Datei.

Dazu erweitern wir unser Programm um  myclass.h . Dort erzeugen wir einen Slot. Zusätzlich erzeugen wir noch einen Konstrucktor und einen Destrucktor für MyClass.

 
#include <QObject>

class MyClass : public QObject
{
Q_OBJECT

public:
MyClass(); // Konstruktor
~MyClass(); //Destruktor

signals:

public slots:
void cppSlot(QString msg); // Slot wie gehabt

};

in main Cpp müssen wir nun unser QML-Signal mit einem C++-Slot verbinden.
Dazu erstellen wir uns ein RootObject von unserem QtQuick2ApllicationViewer und verbinden dieses Object mit unserer Classe MyClass zusätzlich erstellen wir den Konstrucktor und Destrucktor von MyClass sowie eine Testfunktion für den cppSlot. Das Ganze siet dann so aus main.cpp:

#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include "myclass.h"
#include <QQmlContext>
#include <QtCore>
#include <QDebug>
#include <QQuickItem> //wichtig da man sonst den  Fehler:cannot convert 'QQuickItem*' to 'QObject*' in initialization
                     // QObject* viewerobject = viewer.rootObject(); erhält (Fehlt auch in den meisten Dokumentationen die nicht qt5 bzw qtquick2.0 verwenden)
 ^

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QtQuick2ApplicationViewer viewer;

    MyClass data;
    viewer.rootContext()->setContextProperty("myclassData", &data); //<--hier wird der Bezug zu unserem Qml File hergestellt
    viewer.setMainQmlFile(QStringLiteral("qml/qtquick_uebung/main.qml"));
    //Verbindung von QML über Signal Slots nach C++
    QObject* viewerobject = viewer.rootObject(); //unser viewer Root Object
    QObject::connect(viewerobject,SIGNAL(qmlSignal(QString)),&data,SLOT(cppSlot(QString))); //der Connect mit &data was unserer Classe MyClass entspricht
    viewer.showExpanded();
    return app.exec();
}

MyClass::MyClass()
{
}

MyClass::~MyClass()
{
}

void MyClass::cppSlot(QString msg)
{
  qDebug() << QString("Called the C++ slot with message:%1").arg(msg); //Wir geben auf der Debugkonsole den von QML gesendeten Text aus
}

Wenn wir jetzt auf unseren Button klicken erscheint jedesmal in der Konsole unser von QML per Signal gesendeter Text.
ausgabe qml signalslot

States und Transitions

Nun wollen wir etwas Bewegung in unser Programm bringen dazu verwenden wir die Möglichkeit von Qt Quick States zu erstellen.

Ein State ist ein Zustand bei dem sich Irgend ein vorhandenes Objekt verändert z.B in seiner Größe , Position oder sonstigen Eigenschaften.

Um einen State zu erstellen  gehen wir in den Designer und klicken oben auf das Plus wir erstellen für unser Beispiel 2 zusätzliche States in denen wir unseren Button an eine andere Position setzen.

states

Wir können beliebig viele States erstellen.  Im qml-Code wird eine Liste erstellt die die Einzelnen States und ihre Veränderung anzeigt.

    states: [
        State {
            name: "State1"

            PropertyChanges {
                target: button1
                x: 0
                y: 260
            }
        },
        State {
            name: "State2"
            PropertyChanges {
                target: button1
                x: 260
                y: 260
            }
        }
    ]

Eine Liste ist immer durch die [ ] gekennzeichnet.

So jetzt müssen wir nur noch den Zustand Irgendwie ändern aber wie ?

Wir wollen das bei jedem klick auf den Button in den nächsten Zustand gewechselt wird. Dazu müssen wir eine Variable Index einführen

Variablen heissen in qml property. Diese fügen wir in die geschweiften Klammern von Rectangle zu Beginn ein.

 property int index: 0

Diese Property können wir nun bei jedem klick erhöhen und sie als Indentifikator für unseren Zustand verwenden dazu fügen wir folgenden Code in die onClicked: Eigenschaft des Buttons.

  onClicked:{
                    index++
                    labeltext.text=index

                    if (index>=3)index=0;
                    if (index==0)main_window.state=""
                    if (index==1)main_window.state="State1"
                    if (index==2)main_window.state="State2"

              }

Der Grundzustand wird wieder erreicht durch die „“ was keinem Zustand also dem Normal Zustand entspricht. main_window ist die id unseres Rectangles die wir noch vergeben müssen da wir bis dato keine id dafür haben. Dort wo Hallo World steht wird der aktuelle Index angezeigt. Starten wir nun das Programm können wir die Zustände durch klicken auf unseren Knopf durchschreiten.

Ein bisschen lahm sieht das ganze schon aus daher fügen wir eine Animation hinzu,die in qml Transition genannt wird.

 transitions: Transition {
           NumberAnimation { properties: "x,y"; easing.type: Easing.InBounce }
       }

Hier eine NumberAnimation der x,y Koordinaten unseres Buttons, der easing.type gibt die Bewegung an. Klicken wir mit rechter Maustaste auf das easing.type argument können wir mit Qt Quick Werkzeugleiste anzeigen die gewünschte Bewegungsanimation einstellen. (Dies ist immer möglich wenn eine kleine Glühbirne im Editor erscheint diese erscheint wenn wir z.b unseren Cursor hinter die Number Animation setzen )

qt_quick_tool

Mit der Werkzeugleiste können wir uns die Animation durch klicken auf den Play Pfeil anschauen  und beliebig verändern.

Nun haben wir eine erste Anwendung mit Bewegung hier nochmal der komplette Code wie er bissher bei mir aussieht:

import QtQuick 2.0
import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.0

Rectangle {
    id: main_window
    property int index: 0
    width: 360
    height: 360
    color: "#1e864b"
    radius: 0
    transformOrigin: Item.Center
    Text {
        id: labeltext
        text: qsTr("Hello World")
        opacity: 1
        smooth: true
        anchors.centerIn: parent
    }
    MouseArea {
        id: mousearea1
        x: 0
        y: 0
        anchors.rightMargin: 0
        anchors.bottomMargin: 0
        anchors.leftMargin: 0
        anchors.topMargin: 0
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }

                Button {
                    id: button1
                    x: 130
                    y: 40
                    width: 100
                    height: 100

                    style: ButtonStyle {
                            background: Rectangle {
                                implicitWidth: 100
                                implicitHeight:100
                                border.width: control.activeFocus ? 2 : 1
                                border.color: "green"

                                radius: width/2
                                gradient: Gradient {
                                    GradientStop { position: 0 ; color: control.pressed ? "lightgreen" : "blue" }
                                    GradientStop { position: 1 ; color: control.pressed ? "darkgreen" : "darkblue" }
                                }
                            }
                        }

                    text: "Button"
                    tooltip: "Das ist ein Button"

                    onClicked:{
                                index++
                                labeltext.text=index

                                if (index>=3)index=0;
                                if (index==0)main_window.state=""
                                if (index==1)main_window.state="State1"
                                if (index==2)main_window.state="State2"

                              }

                     //hier wird der Text beim clicken geändert
                }

    }
    states: [
        State {
            name: "State1"

            PropertyChanges {
                target: button1
                x: 0
                y: 260
            }
        },
        State {
            name: "State2"
            PropertyChanges {
                target: button1
                x: 260
                y: 260
            }

        }
    ]

    transitions: Transition {
           NumberAnimation { properties: "x,y"; easing.type: Easing.InBounce }
       }

}

Custom Styles für Qt Quick Controls

Hmm ich möchte meinem Knopf eine andere Farbe und Form geben aber es gibt im QtQD keine Farbauswahl für mein Button Objekt.  Also wie stelle ich das an?

Die Lösung heist:

import QtQuick.Controls.Styles 1.0

hiermit lässt sich der Style in unserem fall der Buttonstyle anpassen.

Dazu müssen wir bei unserem Button folgenden Code einfügen:

 Button {
          id: button1
          x: 130
          y: 38
          width: 100
          height: 100
          style: ButtonStyle 
                {
                 background: Rectangle 
                      {
                        color: control.pressed ? "green" : "blue"  //ist der Knopf gedrückt wenn nein blau wenn ja grün
                        implicitWidth: 100                         //wird keine Größe angegeben werden die impliciten größen verwendet 
                        implicitHeight:100
                        border.width: control.activeFocus ? 2 : 1 //wird mit tab auf activer focus geschaltet verändert sich der Button Rand
                        border.color: "#999"
                        radius: width/2 // die Größe halbiert ergibt einen Runden Button
                      }
                 }
          }

Der Ternary Operator bedeutet folgendes: condition ? true-statement : false-statement

qml_button_style

Schon haben wir einen bunten Knopf der grün wird wenn wir ihn drücken.
Ein bisschen hässlich sieht er ja schon aus unser Knopf um ihn zu verschönern belegen wir ihn mit einem Gradienten

also in die geschweifte Klammer von Style->Rectangle  folgendes einfügen:

  gradient: Gradient {
                        GradientStop { position: 0 ; color: control.pressed ? "lightgreen" : "blue" }
                        GradientStop { position: 1 ; color: control.pressed ? "darkgreen" : "darkblue" }
                     }

Es wird ein Farbübergang von position 0 auf position 1 gemacht (man kann beliebig viele Positionen einfügen) in unserem Fall wenn der Knopf nicht gedrückt ist von blue->darkblue und wenn er gedrückt ist von lightgreen nach darkgreen hier kann man auch jede Beliebige Farbe wählen von #fffff bis #00000. Der Gradient überschreibt die eigenschaft color daher can die Zeile color: control.pressed ? „green“ : „blue“ gelöscht werden

qml_button_style_2

Jetzt schaut unser Knopf schon ansehnlicher aus das Problem  im QtQD wird der Runde Button manchmal nicht sofort angezeigt dort sieht man nach wie vor einen Eckigen Button mit den Standard styles.

Nach einem Neustart vom QtCreator sieht man aber auch hier den runden Entwurf.

Qt Quick Controls unser erster Button

Hallo wir wollen nun einen Button einfügen der z.b unseren Hallo Welt text beim clicken ändert.

Dazu importieren wir in unserem main.qml

import QtQuick.Controls 1.0

Gehen wir dann in unseren QtQD erhalten wir weitere Optionen in unserer QML-Typen auswahl Es gibt diverse controls die wir schon aus der Widget Programmiereung her kennen.

Buttons,Checkboxen Slider…. etc. Wir wählen nun einen Button aus und ziehen ihn auf unser Rectangle.

qml_designer_controls

Diesen Button Können wir Rechts unter Button zusätzlich bearbeiten zb können wir ihn noch mit einem Tooltip versehen oder ein Bild für den Button auswählen.

Starten wir das Programm sehen wir den Button wie gewünsch jedoch wenn wir draufklicken geschieht nichts?

Da wir unseren Hallo Welt Text ändern wollen verpassen wir ihm eine id: damit lässt er sich direkt im qml code ansprechen. Daher ist es wichtig die id’s immer sorgfältig zu wählen damit man später genau weis was sich darunter verbirgt.

Das Label anklicken und oben rechts die ID eintragen:

qml_designer_id

Wir nennen unseren „Hallo Welt“ Text labeltext.

Wir können auch alternativ im Code in die Geschweiften Klammern von Text die id händisch einfügen:

 Text {
        id: labeltext
        text: qsTr("Hello World")
        opacity: 1
        smooth: true
        anchors.centerIn: parent
    }

So jetzt verbinden wir noch den Button mit der Aktion Text ändern mit einem onClicked: 

der ganze Code sieht dann wie folgt aus:

import QtQuick 2.0
import QtQuick.Controls 1.0

Rectangle {
    width: 360
    height: 360
    color: "#1e864b"
    radius: 0
    transformOrigin: Item.Center
    Text {
        id: labeltext
        text: qsTr("Hello World")
        opacity: 1
        smooth: true
        anchors.centerIn: parent
    }
    MouseArea {
        x: 0
        y: 0
        anchors.rightMargin: 0
        anchors.bottomMargin: 0
        anchors.leftMargin: 0
        anchors.topMargin: 0
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }

        Button {
            id: button1
            x: 143
            y: 68
            text: "Button"
            opacity: 1
            scale: 1
            rotation: 0
            onClicked:{labeltext.text="Button Clicked"} //hier wird der Text beim clicken geändert
        }
    }
}

Starten wir nun das Programm wird das label beim drücken des Knopfes geändert.
bei erneutem drücken geschieht jedoch nichts mehr wir können die onClicked: eigenschaft mit einem if statement erweitern so z.B  :

 onClicked:{
                        if (labeltext.text=="Button Clicked")
                        {
                            labeltext.text="Hello World"
                        }
                        else
                        {
                            labeltext.text="Button Clicked"
                        }
                      }

Nun ändert sich der Text bei jedem click.

qml_button_clicked

Möchte man den Button direkt mit C++ Code Verknüpfen ist das etwas komplizierter.

Wir wollen zuerst die Spezialitäten von qml beleuchten und zur Verknüpfung mit C++-Code kommen wir später…

Qt Quick Project erstellen

Über Datei->Neu->Anwendungen hier gibt es prinzipiell 4 Möglichkeiten eine Qtquick 2 Anwendung zu erstellen:

  • Qt Quick 2 Anwendung  (eingebauten Elemente)
  • Qt Quick2 -Anwendung (aus existierender QML-Datei)
  • Qt Quick 2 UI
  • Qt Quick 2 UI mit Controls

Wir betrachten in diesem Tutorial die Qt Quick Anwendung mit eingebauten Elementen hier kann man qml mit c++ Code leicht mischen und hat somit alle erdenklichen Möglichkeiten.

dateiname_qtquick

Zuerst den Namen und Pfad für das neue Project auswählen.

kit auswahl

Dann das Verwendete Kit Auswählen in unserem Fall ist nur Desktop MinGW 32Bit verfügbar.

Anschließend kommt noch eine Zusammenfassung und durch klicken auf weiter erhalten wir unser erstes Qtquick Hello World Programm

Betrachten wir die Datei main.cpp

#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"

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

    QtQuick2ApplicationViewer viewer;
    viewer.setMainQmlFile(QStringLiteral("qml/qtquick_uebung/main.qml"));
    viewer.showExpanded();

    return app.exec();
}

Das kennen wir schon soweit von unserem ersten Tutorial nur gibt es hier einen sogenannten QtQuick2ApplicationViewer der die qml Dateien für uns nach dem Compilvorgang anzeigt.Hier wird main.qml geöffnet. Die Main Cpp kann wie gehabt editiert und erweitert werden.

Neu ist jedoch die main.qml die sich im Projectexplorer unter dem Ordner Qml befindet.

project qtquick

import QtQuick 2.0

Rectangle {
    width: 360
    height: 360
    Text {
        text: qsTr("Hello World")
        anchors.centerIn: parent
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }
}

Wichtig ist immer der import von QtQuick 2.0 damit der Creator die Datein auch richtig interpretieren kann ,dieser import steht immer am Anfang jeder .qml Datei.
mit rectangle wird ein Quadrat angelegt dessen Breite und Höhe angegeben wird.

In diesem Quadrat befindet sich ein Text der mit anchors:centerIn: parent in die Mitte des Quadrates gelegt wird.
Zusätzlich wird eine MouseArea die das gesamte Quadrat(anchors.fill:parent) abdeckt.
wird daurauf geklickt wird eine Aktion hier Qt.quit() ausgeführt.

Starten wird das Programm haben wir schon unser erstes Hello World Ergebnis.  🙂

hello world

Die Anwendung schließt sich wenn wir in das Rechteck klicken.

Wenn wir unsere main.qml im Projectexplorer auswählen und den designer öffnen, öffnet sich der QtQuick- Designer (so nenn ich ihn jetzt mal QtQD)

Hier kann man oben links im Navigator die enthaltenen Elemente sehen:

Es gibt Rectangle und darunter einen Text und eine Mouse Area nun können wir zum Beispiel die Eigenschaften des Rectangles ändern indem wir Rechts im Bild auf  Rectangle und dann den Farbeditor aufklappen so können wir z.B das Rechteck beliebig einfärben.

designer_qtquick

in unserem code erscheint bei den Eigenschaften für Rectangle dann ein Kürzel für color z.B.:

color: "#1e864b"

Nochmal ausführen und voila das Rechteck ist bunt.

hello world green

Spielen sie ruhig ein wenig mit dem QtQD herum um zusehen was alles machbar ist. Im nächsten Schritt werden wir einige Qt Quick Controls einführen.