Discussion:
Adding a C++ wrapper class renders my QML custom type unusable
(too old to reply)
Rob Allan
2016-08-31 06:08:36 UTC
Permalink
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:

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


1. Added the necessary qmlRegisterType() call to my application startup
code:

qmlRegisterType<CustomButton>("com.glob.myApp", 1, 0, "CustomButton");


1. Added an import statement to my CustomButton.qml file:

import com.glob.myApp 1.0

1. 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
Michael R Nelson
2016-08-31 11:49:21 UTC
Permalink
Guessing here, but I wonder if the problem is related to fact both qml file and derived class name are the same, i.e., “CustomButton”. You might try renaming one or the other.

Mike

From: Interest [mailto:interest-bounces+mnelson=***@qt-project.org] On Behalf Of Rob Allan
Sent: Wednesday, August 31, 2016 2:09 AM
To: ***@qt-project.org
Subject: [Interest] Adding a C++ wrapper class renders my QML custom type unusable


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:

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

};

1. Added the necessary qmlRegisterType() call to my application startup code:

qmlRegisterType<CustomButton>("com.glob.myApp", 1, 0, "CustomButton");

1. Added an import statement to my CustomButton.qml file:

import com.glob.myApp 1.0

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

________________________________
Please be advised that this email may contain confidential information. If you are not the intended recipient, please notify us by email by replying to the sender and delete this message. The sender disclaims that the content of this email constitutes an offer to enter into, or the acceptance of, any agreement; provided that the foregoing does not invalidate the binding effect of any digital or other electronic reproduction of a manual signature that is included in any attachment.
Elvis Stansvik
2016-08-31 17:24:33 UTC
Permalink
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):

[***@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++)
[***@pyret qmltest]$

It would help to see some of your code to see where things go wrong.

Elvis
Post by Rob Allan
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
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
#include <QQuickItem>
class CustomButton : public QQuickItem
{
Q_OBJECT
CustomButton();
};
qmlRegisterType<CustomButton>("com.glob.myApp", 1, 0, "CustomButton");
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
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
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
http://lists.qt-project.org/mailman/listinfo/interest
Elvis Stansvik
2016-08-31 17:27:32 UTC
Permalink
Post by Elvis Stansvik
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,
Attaching these in qmltest.tar.gz.

Elvis
Post by Elvis Stansvik
#pragma once
#include <QQuickItem>
class CustomButton : public QQuickItem
{
Q_OBJECT
CustomButton(QQuickItem *parent = nullptr);
};
#include "CustomButton.h"
CustomButton::CustomButton(QQuickItem *parent) : QQuickItem(parent)
{
}
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
}
}
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");
}
}
}
#include "CustomButton.h"
#include <QDebug>
#include <QGuiApplication>
#include <QMetaObject>
#include <QQmlApplicationEngine>
#include <QVariant>
class MyClass : public QObject
{
Q_OBJECT
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"
qml: component completed
qml: foo called from QML
qml: foo called from C++
qml: button clicked (from QML)
button clicked (from C++)
It would help to see some of your code to see where things go wrong.
Elvis
Post by Rob Allan
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
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
#include <QQuickItem>
class CustomButton : public QQuickItem
{
Q_OBJECT
CustomButton();
};
qmlRegisterType<CustomButton>("com.glob.myApp", 1, 0, "CustomButton");
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
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
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
http://lists.qt-project.org/mailman/listinfo/interest
Sina Dogru
2016-08-31 21:46:17 UTC
Permalink
Hello Rob,

As I understand, you would like to define a QML object type
<http://doc.qt.io/qt-5/qtqml-documents-definetypes.html> which root object
is a C++ object, add a
signal to that custom QML type in QML document or defined in C++ and call
an inline JavaScript
function from C++. I am not sure why is your program is not working but
those all are supposed to
be working.

But you can not declare a Q_INVOKABLE function and implement it in QML or
JavaScript file. Because
even if you don't call those functions, MOC generated files will. So the
compiler gives an error. Maybe
you can call your JavaScript implementations from those wrappers but you
must define those functions.

Here is a little example:

// custombutton.h
#include <QQuickItem>


class CustomButton : public QQuickItem
{
Q_OBJECT
public:
CustomButton()
{
setAcceptedMouseButtons(Qt::AllButtons);
}

signals:
void clicked(const QString& text, int eventCode);

protected:
void mousePressEvent(QMouseEvent* event) override
{
QQuickItem::mousePressEvent(event);

emit clicked("emitted from CustomButton::mousePressEvent",
event->type());

qDebug("CustomButton::mousePressEvent calls this.foo");
// method foo is an inline JavaScript function.
QMetaObject::invokeMethod(this, "foo");
}
};

// CustomButton.qml
import QtQuick 2.7
import QtQuick.Controls 2.0

import MyApp 1.0 as Cpp


Cpp.CustomButton {
id: root

signal qmlclick(string text);

function foo() {
qmlclick("emitted from CustomButton.foo");
}

Rectangle {
anchors.fill: parent
color: "lightblue"

Label {
anchors.fill: parent

text: "Custom Button"

verticalAlignment: Label.AlignVCenter
horizontalAlignment: Label.AlignHCenter
wrapMode: Label.WordWrap
elide: Label.ElideMiddle
}
}
}

// main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 2.0


Window {
visible: true
width: 200
height: 200

title: "MyApp"

Column {
anchors.centerIn: parent

CustomButton {
id: customButton

width: 80
height: 50

onClicked: console.log("custom button click signal " + text);
onQmlclick: console.log("custom button qmlclick signal " +
text);
}

Button {
id: qqcButton

width: 80
height: 50

text: "qqcButton"

onClicked: customButton.clicked("emitted from
qqcButton.onClicked", 2);
}
}
}

// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "custombutton.h"


int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);

qmlRegisterType<CustomButton>("MyApp", 1, 0, "CustomButton");

QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

return app.exec();
}

Hope it helps,
Sina
Post by Rob Allan
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
1. 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
#include <QQuickItem>
class CustomButton : public QQuickItem
{
Q_OBJECT
CustomButton();
};
1. Added the necessary qmlRegisterType() call to my application
qmlRegisterType<CustomButton>("com.glob.myApp", 1, 0, "CustomButton");
import com.glob.myApp 1.0
1. 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
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
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
http://lists.qt-project.org/mailman/listinfo/interest
Rob Allan
2016-09-01 02:56:46 UTC
Permalink
Many thanks to the people who replied with suggestions regarding this issue
- with your help I've been able to solve it.

A few people suggested that the problem might be related to using the same
name 'CustomButton' in multiple places (for the C++ class name, the QML
file name, and the name of the root element type in the QML file). This
was, at least in part, correct (more details below). Two other list members
(Elvis and Sina) provided simple examples that were similar to mine but
worked OK, which helped me to understand why mine didn't work.

Following up on the suggestion that name clashes might be a problem, I
tried (separately) renaming the C++ class, renaming the QML file, and
renaming the root element type in the QML file. None of these solved the
problem.

It turned out that problem was indeed name related, but was not really a
problem with CustomButton itself, but with the way I was using it from
another QML file. I have several QML types in my project that I've
implemented in a similar way to CustomButton - that is, with a QML file
implementation, and an accompanying C++ base class. One example is a custom
MainWindow class, implemented via MainWindow.qml, MainWindow.h and
MainWindow.cpp. MainWindow.qml includes a reference to
CustomButton. Because I have a C++ base class for MainWindow, I had to
include an 'import' statement at the top of it:

import QtQuick 2.5
import QtQuick.Controls 2.0

import com.glob.myApp 1.0

MainWindow {
// ... other stuff
CustomButton {
// ...etc
}
}

While this 'import' statement was necessary in order for MainWindow.qml to
be correct, it had an unforeseen consequence - it meant that the reference
to CustomButton was now regarded as a reference to the CustomButton C++
class, and not the CustomButton QML file! That's why the QML in this file
could now no longer 'see' any of the signals or functions defined in
CustomButton.qml.

The solution for this was to include an 'as' qualifier on the import, and
to use this as a prefix on the root element. Now that I think about it, I
have seen this convention used in the system QML files provided with Qt. I
also got this idea from Sina's example, where he uses 'Cpp' as the
qualifier. I like this convention, as it makes it clear that we are now
referring to the C++ base class definition:

import QtQuick 2.5
import QtQuick.Controls 2.0

import com.glob.myApp 1.0 as Cpp

Cpp.MainWindow {
// ... other stuff
CustomButton {
// ...etc
}
}

So now our reference to MainWindow is correctly interpreted as the C++
MainWindow class, while the reference to CustomButton resolves (I think) to
CustomButton.qml (which, behind the scenes, happens to have an associated
C++ base class - but that is an implementation detail that is invisible at
this level).

I have to say this seems like quite a subtle and potentially confusing
aspect of Qt Quick. When creating a custom type such as 'CustomButton',
there are potentially three places where its name can appear:

- As the name of a C++ class
- As the name of a QML file, e.g. CustomButton.qml
- As the name of the root object type in CustomButton.qml

When other parts of the project refer to CustomButton, it is not
necessarily obvious which of these you are referring to. In a sense they
are all one and the same thing, but in another sense they are not, and - as
I've discovered - the use of 'import' statements and 'as' qualifiers can
affect which of the manifestations of CustomButton is actually targeted.

I wonder if there is any way this situation can be clarified, either in the
way this is implemented in Qt Quick, or if nothing else, in the
documentation.

Again, many thanks to those who contributed answers and helped me to solve
this!

Rob
Post by Rob Allan
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
1. 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
#include <QQuickItem>
class CustomButton : public QQuickItem
{
Q_OBJECT
CustomButton();
};
1. Added the necessary qmlRegisterType() call to my application
qmlRegisterType<CustomButton>("com.glob.myApp", 1, 0, "CustomButton");
import com.glob.myApp 1.0
1. 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
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
QMetaObject::invokeMethod: No such method CustomButton::setContent(QVari
ant,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
Mitch Curtis
2016-09-01 13:11:45 UTC
Permalink
Can you provide a small, reproducible example?

From: Interest [mailto:interest-bounces+mitch.curtis=***@qt-project.org] On Behalf Of Rob Allan
Sent: Wednesday, 31 August 2016 8:09 AM
To: ***@qt-project.org
Subject: [Interest] Adding a C++ wrapper class renders my QML custom type unusable


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:

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

};

1. Added the necessary qmlRegisterType() call to my application startup code:

qmlRegisterType<CustomButton>("com.glob.myApp", 1, 0, "CustomButton");

1. Added an import statement to my CustomButton.qml file:

import com.glob.myApp 1.0

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

Loading...