Sending a signal to a QML item from C++ (Qt5)

Einar picture Einar · Apr 9, 2014 · Viewed 10.2k times · Source

I have a QML file containing this:

Text {
    id: testData
    onTaskClicked:{
        testData.text = task.name
    }
}

The catch is this taskClicked signal. It is emitted by another widget (C++) and needs to be relayed to QML.

This is similar to this SO question, except that the solution posted there doesn't work (why is written below).

The C++ code:

ctxt->setContextProperty(QLatin1Literal("holiday"), m_model);
ctxt->setContextProperty(QLatin1Literal("bgcolor"), color);

view->setResizeMode(QQuickView::SizeRootObjectToView);

auto mainPath = QStandardPaths::locate(QStandardPaths::DataLocation,
                                           QLatin1Literal("taskview.qml"));

view->setSource(QUrl::fromLocalFile(mainPath));

ctxt->setContextProperty(QLatin1Literal("viewer"), m_view);

m_view is a QListView subclass that emits the taskClicked(HolidayTask* task) signal (from the .h file):

Q_SIGNALS:
    void taskClicked(HolidayTask* task);

color and m_model are registered in QML and are used elsewhere. The object from the signal is already registered in QML. view is my QQuickView.

First I tried the solution presented in the question above:

auto root = view->rootObject();
auto myElement = root->findChild<QObject*>(QLatin1Literal("testData");

connect(m_view, SIGNAL(taskClicked(HolidayTask* task), myElement,
        SLOT(taskClicked(HolidayTask* task);

However, myElement is always null (and I get a runtime warning about a non existent slot).

If I try to set the view (the QListView) pointer as a context property of the QML view, it still doesn't work.

In all cases, I get also:

QML Connections: Cannot assign to non-existent property "onTaskClicked"

What could I possibly be doing wrong here?

EDIT to clarify some details: HolidayTask is a custom QObject subclass, and the signal taskClicked is defined in C++ (in a QListView subclass)

EDIT2: We're getting close, but no cigar:

auto root = quickView->rootObject();
auto myElement = root->findChild<QObject*>(QLatin1Literal("testData"));

connect(m_view, SIGNAL(taskClicked(HolidayTask*)),
        myElement, SIGNAL(taskClicked(HolidayTask* task)));

and

Text {
    id: testData
    objectName: "testData"
    signal taskClicked(HolidayTask task)
    onTaskClicked: {
        testData.text = task.name
        console.log("CLICk!")
    }
}

yields

QObject::connect: No such signal QQuickText_QML_0::taskClicked(HolidayTask* task) in /home/lb/Coding/cpp/holiday-planner/src/mainwindow.cpp:178
QObject::connect:  (receiver name: 'testData')

More details: HolidayTask, my custom QObject subclass, is registered in the code as

qmlRegisterType<HolidayTask>("HolidayPlanner", 1, 0, "HolidayTask");

Minimal QML with the data:

 import QtQuick 2.0
 import QtQml 2.2

import HolidayPlanner 1.0

Rectangle {
    id: container
    objectName: "container"
    color: bgcolor


   Text {
        id: testData
        objectName: "testData"
        signal taskClicked(HolidayTask task)
        onTaskClicked: {
            testData.text = task.name
            console.log("CLICK")
        }
   }

}

EDIT3: The final, working code is (see answers on why)

 connect(m_view, SIGNAL(taskClicked(HolidayPlanner::HolidayTask*)),
        myElement, SIGNAL(taskClicked(HolidayPlanner::HolidayTask*)));

This worked only through the use of objects with full namespaces. Otherwise the signature will not match in QML.

Answer

lpapp picture lpapp · Apr 9, 2014

However, myElement is always null (and I get a runtime warning about a non existent slot).

You are trying to find the child based on id, whereas it is based on the objectName property. You would need to set the objectName property to the desired to actually find it.

Also, you do not seem to declare the signal in your QML Text item. I am not sure if it is a custom C++ item or a built-in one. You have not shared enough code unfortunately to understand that bit. Either way, declare your signal as per documentation.

Therefore, try this code out:

Text {
    id: testData
    objectName: "testData"
    // ^^^^^^^^^^^^^^^^^^^
    signal taskClicked (HolidayTask task)
    // ^^^^^^^^^^^^^^^^^^^
    onTaskClicked:{
        testData.text = task.name
    }
}

Once that is done, you are almost ready. You need to have your HolidayTask registered to QML for sure, and you also need to change the connect syntax in your main.cpp as follows:

connect(m_view, SIGNAL(taskClicked(HolidayTask* task), myElement, SIGNAL(taskClicked(HolidayTask* task)));

In short, you need to trigger your QML signal handler this way, and not via SLOT.

Also, note that your connect syntax is broken as it is missing the closing brackets at the end. That needs to be fixed.

I would even consider removing the pointer assignment and use value or reference based passing for this.