[Interest] Adding a C++ wrapper class renders my QML custom type unusable

Elvis Stansvik elvstone at gmail.com
Wed Aug 31 19:27:32 CEST 2016


2016-08-31 19:24 GMT+02:00 Elvis Stansvik <elvstone at gmail.com>:
> Hi Rob,
>
> Looks like a you did a pretty thorough analysis. If I understand you
> correctly, I can't really reproduce the problem with a test case I
> made.
>
> The test case I made consists of CustomButton.h, CustomButton.cpp,
> CustomButton.qml, main.qml and main.cpp as follows:

Attaching these in qmltest.tar.gz.

Elvis

>
> CustomButton.h:
>
> #pragma once
>
> #include <QQuickItem>
>
> class CustomButton : public QQuickItem
> {
>     Q_OBJECT
>
> public:
>     CustomButton(QQuickItem *parent = nullptr);
> };
>
> CustomButton.cpp:
>
> #include "CustomButton.h"
>
> CustomButton::CustomButton(QQuickItem *parent) : QQuickItem(parent)
> {
> }
>
> CustomButton.qml:
>
> import QtQuick 2.0
>
> import com.glob.myApp 1.0
>
> CustomButton {
>     signal clicked()
>
>     property string text;
>
>     function foo(arg) {
>         console.info("foo called from " + arg);
>     }
>
>     Rectangle {
>         anchors.fill: parent
>         color: "red"
>     }
>
>     MouseArea {
>         anchors.fill: parent
>         onClicked: parent.clicked()
>     }
>
>     Text {
>         anchors.centerIn: parent
>         text: parent.text
>     }
> }
>
> main.qml:
>
> import QtQuick 2.0
> import QtQuick.Window 2.2
>
> Window {
>     visible: true
>
>     CustomButton {
>         width: 100
>         height: 30
>         text: "Click me"
>         objectName: "button"
>
>         onClicked: console.info("button clicked (from QML)");
>
>         Component.onCompleted: {
>             console.log("component completed");
>             foo("QML");
>         }
>     }
> }
>
> main.cpp:
>
> #include "CustomButton.h"
>
> #include <QDebug>
> #include <QGuiApplication>
> #include <QMetaObject>
> #include <QQmlApplicationEngine>
> #include <QVariant>
>
> class MyClass : public QObject
> {
>     Q_OBJECT
>
> public slots:
>     void cppSlot() {
>         qDebug() << "button clicked (from C++)";
>     }
> };
>
> int main(int argc, char *argv[])
> {
>     QGuiApplication app(argc, argv);
>
>     qmlRegisterType<CustomButton>("com.glob.myApp", 1, 0, "CustomButton");
>
>     QQmlApplicationEngine engine("main.qml");
>
>     QObject *button = engine.rootObjects().first()->findChild<QObject
> *>("button");
>
>     // Call foo() on button
>     QVariant returnedValue;
>     QMetaObject::invokeMethod(button, "foo",
>             Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, "C++"));
>
>     // Listen to the clicked signal of button
>     MyClass myClass;
>     QObject::connect(button, SIGNAL(clicked()),
>                      &myClass, SLOT(cppSlot()));
>
>     return app.exec();
> }
>
> #include "moc_main.cpp"
>
> Now, if I run this test case, I get the following (if I click the button once):
>
> [estan at pyret qmltest]$ ./qmltest
> qml: component completed
> qml: foo called from QML
> qml: foo called from C++
> qml: button clicked (from QML)
> button clicked (from C++)
> [estan at pyret qmltest]$
>
> It would help to see some of your code to see where things go wrong.
>
> Elvis
>
> 2016-08-31 8:08 GMT+02:00 Rob Allan <rob_allan at trimble.com>:
>> I have a custom QML type, CustomButton, defined in CustomButton.qml.
>> Initially this was a fairly simple type, with a 'clicked' signal, and a few
>> JavaScript functions that set up the button content. I was able to use this
>> custom button from other QML files, and from C++ code (using
>> QMetaObject::invokeMethod() to invoke its methods), and it all worked pretty
>> well.
>>
>> As this button became more complex, I realised that I really needed a C++
>> wrapper class (or backing class?) to deal with some of the additional
>> complexity. I've added C++ wrapper classes to other QML types before, with
>> varying degrees of success, and I think I understand the basic steps
>> involved. I did the following:
>>
>> Created a minimal wrapper in CustomButton.h and CustomButton.cpp (I made
>> this as minimal as possible to begin with so as not to affect the existing
>> behavior - I thought!):
>>
>> #include <QQuickItem>
>>
>> class CustomButton : public QQuickItem
>> {
>>     Q_OBJECT
>>
>> public:
>>     CustomButton();
>> };
>>
>> Added the necessary qmlRegisterType() call to my application startup code:
>>
>> qmlRegisterType<CustomButton>("com.glob.myApp", 1, 0, "CustomButton");
>>
>> Added an import statement to my CustomButton.qml file:
>>
>> import com.glob.myApp 1.0
>>
>> Changed the root item in CustomButton.qml from 'Item' to 'CustomButton'.
>>
>> So far all appeared to be OK - the project compiled, and CustomButton.qml
>> appeared to be error-free in the QML editor. But then things started going
>> downhill.
>>
>> I noticed that, in another QML file that used CustomButton, it could no
>> longer 'see' the signals on my type - a reference to 'onClicked' was
>> red-underlined, and hovering over it showed 'Invalid property name'. At
>> runtime, it failed to create the referencing QML object due to this error.
>> It looks as if the introduction of the C++ wrapper class has 'hidden' the
>> signals defined in the original CustomButton.qml file.
>>
>> I found I could get past this error by deleting the signal definitions from
>> CustomButton.qml, and instead adding them to CustomButton.h:
>>
>> signals:
>>     void clicked(const QString text, int eventCode);
>>     etc...
>>
>> That allowed it to build and run. I'm not sure whether this was correct and
>> the signals would now have worked, because I then struck another problem -
>> my existing C++ code could no longer invoke the JavaScript functions defined
>> in CustomButton.qml. For example, when I tried to invoke a method
>> 'setContent' (which previously worked just fine) I now got this runtime
>> error:
>>
>> QMetaObject::invokeMethod: No such method
>> CustomButton::setContent(QVariant,QVariant)
>>
>> Again, it appears that adding a C++ wrapper class has 'hidden' the function
>> definitions in the QML file, that were previously available to the rest of
>> the system.
>>
>> OK, I thought, maybe I have to define these functions on the C++ class in
>> some way. I tried declaring an INVOKABLE function in the .h file that
>> matched my JavaScript function, and that failed with a link error - the
>> compiler wanted me to define an implementation for this function in my CPP
>> file - but I don't want to implement it in my CPP file, as the
>> implementation exists in the QML file! Just to see where it got me, I tried
>> adding an implementation to the CPP file, and inside this function,
>> attempted to "invoke" the JavaScript function. That failed, presumably
>> because I had now introduced a kind of circularity and was probably
>> attempting to invoke the same handler that I was already in!
>>
>> I also tried declaring these functions as 'slots' on the C++ class, but
>> fared no better - the compiler still wanted a CPP implementation, and I
>> wasn't sure if I was really meant to add one, and if I did, how I would then
>> invoke the JavaScript function from it.
>>
>> In short - adding a C++ wrapper class (with almost nothing in it) has broken
>> a previously functional QML type implementation, by apparently 'hiding'
>> signals and functions that exist in the QML.
>>
>> Can anyone advise what I'm doing wrong here? Or suggest some relevant
>> documentation that might give me the answers I need?
>>
>> Thanks,
>> Rob
>>
>>
>> _______________________________________________
>> Interest mailing list
>> Interest at qt-project.org
>> http://lists.qt-project.org/mailman/listinfo/interest
>>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: qmltest.tar.gz
Type: application/x-gzip
Size: 1268 bytes
Desc: not available
URL: <http://lists.qt-project.org/pipermail/interest/attachments/20160831/2d00dfa5/attachment.bin>


More information about the Interest mailing list