Adding a C++ wrapper class renders my QML custom type unusable
-
wrote on 31 Aug 2016, 05:58 last edited by
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 -
Hi @glob
If I understood correctly, there seems to be a conflict between your 2 QML items.- Your custom
QQuickItem
based item registered usingqmlRegisterType()
. - And also you happen to have another Item with same name
CustomButton
since you have a file namedCustomButton.qml
So I think the problem is that the registered Item is being referenced and which probably doesnt have the signals defined?
- Your custom
-
wrote on 1 Sept 2016, 03:05 last edited by
Yes, I think you're on the right track here. Thanks to your suggestion, and others that I received on the qt 'interest' mailing list, I've been able to solve this.
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 mailing list members 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 one of the examples that was sent to me by a mailing list member, 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.
Rob
1/3