5

I have what I'm calling a C++ "service" who's interface I want to expose to QML. I'm trying to use QQmlContext's setContextProperty to link the object into the QML and connect to it from a QML Connections block.

QML isn't complaining with a reference error as it did previously when I hadn't registered the service in the QML context:

qrc:/main.qml:13: ReferenceError: service is not defined

So, QML seems to find the service object now, however the QML slot javascript function is not getting invoked. I see this in Qt Creator:

Debugging starts
QML debugging is enabled. Only use this in a safe environment.
QML Debugger: Waiting for connection on port 62301...
Calling the clbk signal
Debugging has finished

There should be an In onClbk message per console.log("In onClbk"); I know that I can use QMetaObject::invokeMethod to invoke a QML object's function directly, but I am trying to have a little looser coupling through the use of signals and slots.

I would like to avoid creating a QQuickItem and instantiating the service in the QML, if at all possible.

Unfortunately, the boilerplate code is legion and this is my SSCCE.

Here is a zip file of all the project directory as created through Qt Creator 5.4.

main.cpp

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    auto rc = engine.rootContext();
    auto service = new Service();
    engine.rootContext()->setContextProperty(QStringLiteral("service"), service);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    // invoke the trigger arbitrarily
    QTimer *timer = new QTimer();
    timer->setSingleShot(true);
    QObject::connect(timer, SIGNAL(timeout()), service, SLOT(trigger_clbk()));
    timer->start(4000);
    return app.exec();
}

service.h

class Service : public QQuickItem {
    Q_OBJECT

public:
    virtual ~Service(){}
signals:
    void clbk();
public slots:
    void trigger_clbk() {
        qDebug()<<"Calling the clbk signal";
        clbk();
    }
};

main.qml

import QtQuick 2.4
import QtQuick.Window 2.2

Window {
    visible: true
    MainForm {
        anchors.fill: parent
        mouseArea.onClicked: {
            Qt.quit();
        }
        // subscribe to a signal
        Connections {
            target: service
            onClbk: function(){
                console.log("In onClbk");
            }
        }
    }
}

Main.ui.qml

import QtQuick 2.3

Rectangle {
    property alias mouseArea: mouseArea

    width: 360
    height: 360

    MouseArea {
        id: mouseArea
        anchors.fill: parent
    }

    Text {
        anchors.centerIn: parent
        text: "Hello World"
    }
}
Ross Rogers
  • 23,523
  • 27
  • 108
  • 164
  • FYI, I have read, and recently fixed the dead links, on the accepted answer to this [similar question](http://stackoverflow.com/questions/8834147/c-signal-to-qml-slot-in-qt). – Ross Rogers May 01 '15 at 21:21

2 Answers2

7

You're trying to assign a JS function to the cblk signal handler, that's not going to work as the signal handler is the function that handles the signal. So the Connections block should read:

Connections {
    target: service
    onClbk: {
        console.log("In onClbk");
    }
}
cmannett85
  • 21,725
  • 8
  • 76
  • 119
  • 1
    Ooh.. so what I was doing was creating a function that would be invoked to provide the real function that would be the slot? – Ross Rogers May 01 '15 at 21:24
  • Can I bother you to elaborate on how I could make the `onClbk` property/slot accept parameters? – Ross Rogers May 01 '15 at 21:25
  • 3
    No, JS functions are first class objects so you were creating a function object that was immediately destroyed as the signal handler went out of scope. Signal handlers receive whatever arguments they are sent, so if you defined the signal as `void clbk( double hello )`, the signal handler would have a `hello` var available within it's scope _but the handler definition would not change_. – cmannett85 May 01 '15 at 21:31
  • Ah. The parameters are magically injected into scope. This QML has a bit of a learning curve... Thanks C. Mannett. – Ross Rogers May 01 '15 at 21:33
-2
  1. You should explicitly use the type of service, like Service or QObject (not auto). It's good for you to ensure what you want the object do.

  2. No need define additional function in SLOT of your QML, because it self is a function. In the case if you want to use parameter, do like this:

    signals:
    void clbk(QString signalString);
    
    Connections {
    target: service
    onClbk: {
        console.log(signalString);
    }
    }
    

Notice that you must use exact name of parameters, and the type of params must be register using qRegisterMetaType.

Ken
  • 1,303
  • 13
  • 15
  • Thanks for the info. I respectfully disagree with your suggestion to not use `auto`. [Type inference](https://en.wikipedia.org/wiki/Type_inference) is awesome and you do not forgo static typing/compile-time checking by using it. You do however reduce the Signal to Noise Ratio in your code by avoiding having type syntax specifications everywhere. – Ross Rogers May 17 '16 at 14:53