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

Elvis Stansvik elvstone at gmail.com
Wed Aug 31 19:24:33 CEST 2016


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:

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
>



More information about the Interest mailing list