QT / QML Signale und Slots mit C++

 C Programming >> C-Programmierung >  >> Tags >> Qt
QT / QML Signale und Slots mit C++

QT hat einen eingebauten Observer-Mechanismus, den sie „Signals and Slots“ nennen. Es ermöglicht Objekten, miteinander zu kommunizieren, ohne die Interna eines der beiden kennen zu müssen. Durch Erben von QObject und einige wenige Q_PROPERTY definieren Makros, der QT Meta Object Compiler (moc ) erledigt die ganze harte Arbeit für Sie. Innerhalb einer C++-Klasse funktioniert das alles praktisch und ist relativ einfach zu befolgen, aber wenn Sie QML verwenden, erfordert es etwas mehr Arbeit. Dieses kleine Beispiel zeigt Ihnen, wie Sie QML und C++ mithilfe von Signalen und Slots in QT 5.12 verbinden.

Unten ein Screenshot der Anwendung. Es ist nichts weiter als ein einfacher Zähler, der per Knopfdruck hochgezählt oder über ein Texteingabefeld gesetzt wird, aber für den Anfang reicht es aus.

Da diese Signal-/Slot-Konstruktion hauptsächlich auf Zeichenfolgen basiert, können Sie die von Ihrer IDE bereitgestellten Refactoring-Tools nicht verwenden. Wenn Ihre Methode auf value basiert und Sie value ändern möchten zu, sagen wir, something , müssen Sie den Q_PROPERTY ändern , die QML-Nutzung und -Bindungen und den gesamten regulären C++-Code. Nicht sehr offensichtlich, wenn Sie nicht damit vertraut sind, wie QT-Signale und -Slots und QML funktionieren.

Zum Spaß habe ich diese Beispielanwendung auch für Webassembly kompiliert. Sie können es hier ausführen oder es ist unten auf dieser Seite als iframe eingebettet .

Zusammenfassung

Da dies ein kleiner Ausschnitt ist, fehlt ihm die Erklärung und Tiefe, die Sie normalerweise von meinen Artikeln erhalten. Einige Codekommentare werden bereitgestellt, aber in diesem Fall wird empfohlen, die QT-Dokumentation zu lesen:

  • Signale und Slots
  • Interaktion mit QML und C++

Da ist es sehr ausführlich erklärt. Das ist auch der Grund, warum ich diese Zusammenfassung geschrieben habe, bei all der umfangreichen Dokumentation ist es schwer, mit etwas Kleinem anzufangen.

Mein Beispielcode hat eine C++-Klasse namens Counter , mit einem privaten long long mit dem Namen m_Value .In der QML-Datei möchte ich diese Klasse und ihre Methoden verwenden, einschließlich des QT-Signals/Slots.

Die Klasse muss von QObject erben und Sie müssen den Q_OBJECT platzieren Makro im Header:

class Counter : public QObject
    {
        Q_OBJECT
        [...]

Die Methoden zum Festlegen und Abrufen des Werts sind wie erwartet:

long long value() const { return m_Value; };
[...]
void Counter::setValue(long long value) {
        if (value == m_Value)
            return;
        m_Value = value;
        emit valueChanged(value);
    }

In der obigen Methode sehen Sie den emit Stichwort. Das ist aus Gründen der Übersichtlichkeit eine leere Definition. Die Funktion valueChanged() wird genannt. Das ist unser signal , wie in der Header-Datei:

signals:
    void valueChanged(long long newValue);

Die setValue() Methode ist unser slot :

public slots:
        void setValue(long long value);

Diese sind für QML über diesen Q_PROPERTY zugänglich Zeile:

Q_PROPERTY(long long value READ value WRITE setValue NOTIFY valueChanged)

Sie können diese auch über QObject::connect() mit Dingen verbinden aber das ist außerhalb des Bereichs dieses Snippets. Das gilt, wenn Sie die Signalisierung in C++ verwenden.

Diese Zeilen in main.cpp ist ebenfalls erforderlich, es fügt sozusagen Ihre Klasse zu QML hinzu:

    QQmlApplicationEngine engine;
    Counter myCounter;

    QQmlContext *context = engine.rootContext();
    context->setContextProperty("MyCounter", &myCounter);

Danach können Sie auf MyCounter zugreifen innerhalb von QML, als wäre es eine reguläre C++-Klasse. Zum Beispiel, um Counter::value() anzurufen Methode:

    Text {
        text: "Counter: " + MyCounter.value + "."
    }    

Oder die Counter::setValue() Methode:

        Button {
            text: qsTr("Set counter to 10")
            // C++ method Counter::setValue(long long), bound via Q_PROPERTY
            onClicked: MyCounter.setValue(10)
        }

Aufgrund der Magie der moc und den zusätzlichen Code, den es über Q_PROPERTY generiert , wenn Sie wie im folgenden Beispiel inkrementieren, weiß es, welcher Wert zu inkrementieren ist, und hat dafür korrekte Operatorüberladungen generiert:

   Button {
        text: qsTr("Increase Counter")
        onClicked: ++MyCounter.value
    }

Sie können das C++-Signal auch direkt in QT empfangen. Wir haben valueChanged definiert als das Signal und über einen Connection mit onValueChanged (Hier kommt es auf Großbuchstaben an, stellen Sie Ihrer Methode on voran und ändern Sie das erste Zeichen Ihres Methodennamens in einen Großbuchstaben), können Sie Dinge in QML tun. Wie unten, wo ich eine lokale Variable habe, die jedes Mal erhöht wird, wenn das Signal empfangen wird:

Text {
    property int changeCount: 0
    id: labelChanged
    text: "Count has changed " + changeCount + " times."
    // Receive the valueChanged NOTIFY
    Connections {
        target: MyCounter
        onValueChanged: {
            ++labelChanged.changeCount
        }
    }
}

Ein Beispiel für eine bidirektionale Bindung finden Sie im letzten TextInput in QML. Es zeigt den aktuellen Wert der C++-Klasse an, wird aktualisiert, wenn der Wert aktualisiert wird, und wenn Sie eine Zahl eingeben, aktualisiert es die C++-Klasse.

Beispielcode

Erstellen Sie einen Projektordner und legen Sie dort alle Dateien unter den angegebenen Dateinamen ab.

Das Projekt ist auch hier auf Github verfügbar.

qmlcppsignalebeispiel.pro

QT += quick

CONFIG += c++11

SOURCES += \
        counter.cpp \
        main.cpp

RESOURCES += qml.qrc

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

# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

HEADERS += \
    counter.h

qml.qrc

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

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include <QQmlContext>
#include "counter.h"

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

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    Counter myCounter;

    QQmlContext *context = engine.rootContext();
    /* Below line makes myCounter object and methods available in QML as "MyCounter" */
    context->setContextProperty("MyCounter", &myCounter);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);

    engine.load(url);
    return app.exec();

}

counter.h

#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>

class Counter : public QObject
{
    Q_OBJECT
    Q_PROPERTY(long long value READ value WRITE setValue NOTIFY valueChanged)
public:
    explicit Counter(QObject *parent = nullptr);
    long long value() const { return m_Value; };

public slots:
    void setValue(long long value);

signals:
    void valueChanged(long long newValue);

private:
    long long m_Value {0} ;
};

#endif // COUNTER_H

counter.cpp

#include "counter.h"

Counter::Counter(QObject* parent) : QObject(parent)
{
}

void Counter::setValue(long long value) {
    if (value == m_Value)
        return;
    m_Value = value;
    emit valueChanged(value);
}

main.qml

import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.11

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("QML Signals and slots example - Raymii.org")

    MenuBar {
        width: parent.width
        Menu {
            title: qsTr("File")
            MenuItem {
                text: qsTr("Exit")
                onTriggered: Qt.quit();
            }
        }
    }

    Column {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
        spacing: 20

        Text {
            id: info
            width: parent.width * 0.9
            wrapMode: Text.WordWrap
            text: "QML / C++ binding via signals and slots example program, by Raymii.org. License: GNU GPLv3"
        }


        Text {
            id: labelCount
            // C++ method Counter::value(). Bound via Q_PROPERTY, updates automatically on change
            text: "Counter: " + MyCounter.value + "."
        }

        Text {
            property int changeCount: 0
            id: labelChanged
            text: "Count has changed " + changeCount + " times."
            // Receive the valueChanged NOTIFY
            Connections {
                target: MyCounter
                onValueChanged: {
                    ++labelChanged.changeCount
                }
            }
        }

        Row {
            spacing: 20
            Button {
                text: qsTr("Increase Counter")
                onClicked: ++MyCounter.value
            }

            Button {
                text: qsTr("Set counter to 10")
                // C++ method Counter::setValue(long long), bound via Q_PROPERTY
                onClicked: MyCounter.setValue(10)
            }

            Button {
                text: qsTr("Reset")
                onClicked: {
                    // C++ method Counter::setValue(long long), bound via Q_PROPERTY
                    MyCounter.setValue(0)
                }
            }
        }

        Row {
            spacing: 20

            Text {
                id: setText
                text: qsTr("Enter counter value: ")
            }
            Rectangle {
                width: setText.width
                height: setText.height
                border.width: 1
                border.color: "black"

                TextInput {
                    id: counterInput
                    focus: true
                    text: MyCounter.value
                }
            }
            // Bi-directional binding, entering a number in the textarea updates the
            // C++ class, if the C++ class is updated, the textarea is updated as well.
            Binding {
                target: MyCounter
                property: "value"
                value: counterInput.text
            }
        }
    }
}

Bauen / Machen

Um den obigen Code zu erstellen, erstellen Sie zuerst einen Build-Ordner außerhalb des Projekts:

cd /tmp
mkdir build-qmlexample
cd build-qmlexample

Führen Sie qmake aus , ersetzen Sie den Pfad (/home/remy/tmp/qt/qml_cpp_signal_example/ ) zu Ihrem Projektpfad:

qmake /home/remy/tmp/qt/qml_cpp_signal_example/qmlcppsignalexample.pro -spec linux-g++ CONFIG+=release && make qmake_all

Dieses Beispiel verwendet qmake , aber es sollte keine Probleme geben, cmake zu verwenden . Hier wird nichts Besonderes verwendet.

Wenn qmake beendet ist, können Sie make ausführen um das Projekt zu erstellen:

make -j4

Nach kurzer Zeit sollte die Binärdatei verfügbar sein:

$ file qml_cpp_signal_example 
qml_cpp_signal_example: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f884f57b90ebf05b51551d42cef5ca3ee52037b4, for GNU/Linux 3.2.0, with debug_info, not stripped

Führen Sie es von der Befehlszeile aus:

./qml_cpp_signal_example

QT-Webassembly-Demo

Zum Spaß habe ich die Beispielanwendung zu Webassembly kompiliert. Führen Sie es hier aus oder, wenn es geladen wird, ein iframe unten: