Señales QT / QML y Slots con C++

 C Programming >> Programación C >  >> Tags >> Qt
Señales QT / QML y Slots con C++

QT tiene un mecanismo de observador incorporado, al que llaman 'Señales y ranuras'. Permite que los objetos se comuniquen entre sí sin tener que tener conocimiento de los internos de ninguno de los dos. Al heredar de QObject y definiendo algunos Q_PROPERTY macro, el compilador de metaobjetos de QT (moc ) hace todo el trabajo duro por usted. Dentro de una clase de C++, todo esto funciona muy bien y es razonablemente fácil de seguir, pero cuando se usa QML requiere un poco más de trabajo. Este pequeño ejemplo le muestra cómo unir QML y C++ usando señales y ranuras, en QT 5.12.

A continuación una captura de pantalla de la aplicación. No es más que un simple contador que se incrementa con un botón o se configura a través de un campo de entrada de texto, pero es suficiente para comenzar.

Debido a que esta construcción de señal/ranura se basa principalmente en cadenas, no puede usar las herramientas de refactorización proporcionadas por su IDE. Si su método se basa en value y quieres cambiar value a, digamos, something , necesitas cambiar el Q_PROPERTY , el uso y los enlaces de QML y todo el código normal de C++. No es muy obvio si no está familiarizado con el funcionamiento de las señales QT y las ranuras y QML.

Por diversión, también compilé esta aplicación de ejemplo en Webassembly. Puede ejecutarlo aquí, o en la parte inferior de esta página está incrustado como un iframe .

Resumen

Debido a que este es un fragmento pequeño, carece de la explicación y la profundidad que normalmente obtienes de mis artículos. Se proporcionan algunos comentarios de código, pero se recomienda leer la documentación de QT en este caso:

  • Señales y tragamonedas
  • Interacción con QML y C++

Está explicado allí muy extenso. Esa es también la razón por la que escribí este resumen, debido a toda la documentación completa, es difícil comenzar con algo pequeño.

Mi código de ejemplo tiene una clase C++ llamada Counter , con un long long privado llamado m_Value .En el archivo QML, quiero usar esta clase y sus métodos, incluido QT Signal/Slot.

La clase debe heredar de QObject y debes colocar el Q_OBJECT macro en el encabezado:

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

Los métodos para establecer y obtener el valor son los esperados:

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

En el método anterior, verá el emit palabra clave. Esa es una definición en blanco, para mayor claridad. La función valueChanged() se llama. Este es nuestro signal , como en el archivo de encabezado:

signals:
    void valueChanged(long long newValue);

El setValue() el método es nuestro slot :

public slots:
        void setValue(long long value);

Estos son accesibles para QML por este Q_PROPERTY línea:

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

También puede conectarlos a cosas a través de QObject::connect() pero eso está fuera del alcance de este fragmento. Eso es para cuando usas la señalización dentro de C++.

Estas líneas en main.cpp también se requiere, agrega su clase a QML, por así decirlo:

    QQmlApplicationEngine engine;
    Counter myCounter;

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

Después de esto puedes acceder a MyCounter dentro de QML como si fuera una clase normal de C++. Por ejemplo, para llamar al Counter::value() método:

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

O el Counter::setValue() método:

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

Debido a la magia del moc y el código adicional que genera a través de Q_PROPERTY , cuando incrementa como en el ejemplo siguiente, sabe qué valor incrementar y ha generado sobrecargas de operador correctas para él:

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

También puede recibir la señal de C++ directamente en QT. Hemos definido valueChanged como la señal y a través de un Connection con onValueChanged (las mayúsculas importan aquí, prefije su método con on y cambie el primer carácter del nombre de su método a una mayúscula) puede hacer cosas en QML. Como a continuación, donde tengo una variable local que se incrementa cada vez que se recibe la señal:

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

Para ver un ejemplo de enlace bidireccional, mira el último TextInput en QML. Muestra el valor actual de la clase C++, se actualiza cuando se actualiza el valor y cuando ingresa un número, actualiza la clase C++.

Código de ejemplo

Cree una carpeta de proyecto y coloque todos los archivos allí con los nombres de archivo provistos.

El proyecto también está disponible en github, aquí.

qmlcppsignalexample.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>

principal.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();

}

contador.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

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

principal.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
            }
        }
    }
}

Construir/Hacer

Para crear el código anterior, primero cree una carpeta de compilación fuera del proyecto:

cd /tmp
mkdir build-qmlexample
cd build-qmlexample

Ejecute qmake , reemplaza la ruta (/home/remy/tmp/qt/qml_cpp_signal_example/ ) a la ruta de su proyecto:

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

Este ejemplo usa qmake , pero no debería haber problemas para usar cmake . No usar nada sofisticado aquí.

Cuando qmake ha terminado, puede ejecutar make para construir el proyecto:

make -j4

Después de un rato, el binario debería estar disponible:

$ 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

Ejecútelo desde la línea de comandos:

./qml_cpp_signal_example

Demostración de ensamblaje web QT

Por diversión, compilé la aplicación de ejemplo en webassembly. Ejecútelo aquí o, si se carga, un iframe a continuación: