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

C++ Signal nach QML-Slot

So jetzt das ganze noch umgekehrt also von einer C++ Funktion informationen per Signal zu unserem Qml Ui senden.

Dazu erstellen wir in unserer neu angelegten myclass.h ein signal:

#include <QObject>

class MyClass : public QObject
{
    Q_OBJECT

public:
    MyClass();
    ~MyClass();

signals:

    void setlabeltext(QString text);

public slots:
    void cppSlot(QString msg);

};

Anschließend emittieren wir das Signal von unserer C++ Datei aus.

#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++"; //Unser C++ Text
    emit data.setlabeltext(msg); //<-- Hier wird das Signal setlabeltext von MyClass gesendet 

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

    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);
}

Nun benötigen wir noch einen Slot diesmal auf der Qml Seite:

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
    signal qmlSignal(string msg)

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

    Connections {                        // Mittels Connections verbinden wir das Signal mit dem Qml "Slot"
                   target: myclassData   // myclassdata wurde als ContextProperty in main.cpp definiert
                   onSetlabeltext: {     // ganz wichtig !!! hier muss ein Großbuchstabe stehen sonst funktionierts nicht
                     labeltext.text=text // das Label wo Hallo World stand enthält nun den Text von C++
                   }
    }
    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();
        }
...

Wichtig ist das die aktion die das Signal empfängt mit on[Großbuchstabe]Signalname aufgerufen werden muss. Komischerweise funktioniert diese Konstelation nicht wenn der Signalname mit einem Großbuchstaben beginnt (hat mich einiges an Zeit gekostet das rauszufinden o_o ) also empfiehlt es sich signalnamen immer klein zu schreiben um hier probleme zu vermeiden.

So jetzt haben wir einen per Signal gesendeten C++ Text in unserem Qml file und können in z.b in ein Label füllen:

Signal_slot qml

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