Can't invoke Qt c++ method in QML ListView - c++

I have a list of QObjects acting as a qml model for ListView. I can change their properties alright, but can't invoke any slots or Q_INVOKABLE methods. This is the minimal example for my issue (sad that it's still quite large).
Define a really simple class with a property and an invokable method
// DummyObject.h
class DummyElem : public QObject
{
Q_OBJECT
Q_PROPERTY(QString dummy READ getDummy CONSTANT)
public:
explicit DummyElem(QObject *parent = nullptr);
QString getDummy();
Q_INVOKABLE void notifyStuff();
};
Implement trivial methods of this simple class
// DummyObject.cpp
#include "DummyElem.h"
#include <QDebug>
DummyElem::DummyElem(QObject *parent) : QObject(parent) {}
QString DummyElem::getDummy() {return "lorem";}
void DummyElem::notifyStuff() {qDebug() << "ipsum";}
Start a qml app with the list as a root property. Exactly copy-pasted from a tutorial, the one they called q_incokable methods in.
// main.cpp
#include "DummyElem.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QList<QObject*> dataList;
dataList.append(new DummyElem);
dataList.append(new DummyElem);
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("dataModel", QVariant::fromValue(dataList));
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
Describe a qml layout with a ListView and a delegate that will invoke a c++ method when clicked.
// main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
visible: true
ListView {
anchors.fill: parent
model: dataModel
delegate: Component {
Text {
text: model.dummy
MouseArea {
anchors.fill: parent
onClicked: {model.notifyStuff()}
}
}
}
}
}
The issue is hard to debug as a c++ class model can't be json-strigified, nor can i get its javascript entries(). The error i get is "undefined is not a function" which is cool too.
I tried registering the Qt type in QML, but that didn't do anything either.
I'm using Qt libraries version 5.9.4, but the "minimal qt version required" box in QtCreator is set to "Qt 5.6".

You need to use modelData. I'm not entirely sure why, most probably because of the QVariantList. You can read a bit more on this page.
Window {
visible: true
ListView {
anchors.fill: parent
model: dataModel
delegate: Component {
Text {
text: modelData.dummy
MouseArea {
anchors.fill: parent
onClicked: modelData.notifyStuff();
}
}
}
}
}
Fun fact: this is the error I get on Qt 5.11.3:
TypeError: Property 'notifyStuff' of object QQmlDMObjectData(0x5585fe567650) is not a function
At least a bit more telling than undefined, but still not fully descriptive I'd say.

Related

Enum in Qt property

I have a code, which works with Qt 5.5 and doesn't with Qt 5.2. Problem is with this enum:
#include <QtCore/QMetaType>
enum Area
{
Area_A,
Area_B,
Area_C
};
Q_DECLARE_METATYPE(Area)
Then I have an object, which exposes this area property:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Area area READ area WRITE setArea NOTIFY areaChanged)
public:
explicit MyClass(QObject *parent = 0)
: QObject(parent), m_area(Area_A){}
Area area() const { return m_area; }
void setArea(Area area) {
m_area = area;
emit areaChanged(area);
}
signals:
void areaChanged(Area area);
private:
Area m_area;
};
And main.cpp:
#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>
#include <QtQml/QQmlContext>
#include <QtQml/QtQml>
#include "MyClass.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<MyClass>("GLib", 1, 0, "MyClass");
MyClass controller;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("controller", &controller);
engine.load("./main.qml");
controller.setArea(Area_B);
return app.exec();
}
It compiles, everything is OK. But when I tried to use area property in qml:
import QtQuick 2.0
import QtQuick.Window 2.0
import GLib 1.0
Window {
visible: true
id: root
property int area: controller.area
Text {
id: name
text: "Test"
x: area * 30
y: area * 30
}
}
I have run-time errors, if Qt 5.2 is used (Linux, x64):
QMetaProperty::read: Unable to handle unregistered datatype 'Area' for
property 'MyClass::area'
file:///home/yech844/devel/test_qml/main.qml:10:24: Unable to assign
[undefined] to int QMetaProperty::read: Unable to handle unregistered
datatype 'Area' for property 'MyClass::area'
file:///home/yech844/devel/test_qml/main.qml:10:24: Unable to assign
[undefined] to int
Is it a bug in Qt? Why I can't use Enum, which is declared out of Class scope?
Qt 5.5 introduced Q_ENUM macro which removed the need to use Q_DECLARE_METATYPE. Read more about it here: https://woboq.com/blog/q_enum.html
I don't know why the code works in Qt 5.5, but I know why it doesn't in Qt 5.2.
Q_DECLARE_METATYPE(...) only makes the type available in static (compiled) context. For example, if you want to use the type in QVariant::fromValue(...). Here, the type you pass to the function can be processed during compile time, and for this, Q_DECLARE_METATYPE is enough.
However, if you want to use a type in a pure runtime context, for example in a QML document, the Qt runtime doesn't know the type declared with Q_DECLARE_METATYPE. For this, a function call (evaluated during runtime) needs to be made, and qRegisterMetatype is the tool for this:
qRegisterMetaType<Area>("Area");
My guess for Qt 5.5 not needing that line is that qmlRegisterType might detect the use of the meta type in the property and automatically calls the above function for you.

Q_ENUMS are "undefined" in QML?

Enums are not working out for me.
I have registered them with Q_ENUMS()
I did not forget the Q_OBJECT macro
the type is registered using qmlRegisterType()
the module is imported in QML
In short, everything is "by-the-book" but for some reason I continue getting undefined for each and every enum in QML. Am I missing something?
class UI : public QQuickItem {
Q_OBJECT
Q_ENUMS(ObjectType)
public:
enum ObjectType {
_Root = 0,
_Block
};
...
};
...
qmlRegisterType<UI>("Nodes", 1, 0, "UI");
...
import Nodes 1.0
...
console.log(UI._Root) // undefined
EDIT: Also note that the registered enums are indeed available to use for the metasystem, for some reason they do not work in QML.
UPDATE: I just found this bug: https://bugreports.qt.io/browse/QTBUG-33248
But unlike that bug my root component is a bare UI not a custom element with UI as its root.
Turns out that it is actually possible to use enum values form QML in console.log(), the following code is actually working.
class A : public QObject {
Q_OBJECT
Q_ENUMS(EA)
public:
enum EA {
EA_NULL = 0,
EA_ONE
};
};
class B : public A {
Q_OBJECT
Q_ENUMS(EB)
public:
enum EB {
EA_TWO = 2,
EA_THREE
};
};
#include "main.moc"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<A>("test", 1, 0, "A");
qmlRegisterType<B>("test", 1, 0, "B");
QtQuick2ApplicationViewer viewer;
viewer.setMainQmlFile(QStringLiteral("qml/enums/main.qml"));
viewer.showExpanded();
return app.exec();
}
and...
Component.onCompleted: {
console.log(A.EA_NULL)
console.log(A.EA_ONE)
console.log(B.EA_NULL)
console.log(B.EA_ONE)
console.log(B.EA_TWO)
console.log(B.EA_THREE)
}
Output is:
0
1
0
1
2
3
So I guess there is another problem besides "you are not using it correctly"... It might have to do with the bug I mentioned above, and the fact that when I instantiate the UI element, I actually instantiate a QML component which is a tree of objects with the UI as the root. While this doesn't prove to be any problem for working with pointers from C++ with the full QML objects, it does seem to mess enums for some reason.
Your problem is not the exposure of the enum, but the fact that you have a leading underscore. Once you remove that, it will work.
You need to start the enum value with uppercase letter. Some rule is necessary to distinguish enums from attached properties from enums. Leading uppercase will refer to enums, and the rest for attached properties (or undefined if not set).
Admittedly, there is also a warning in Qt itself because if you try to assign that enum value to an int or var property, you are currently not getting a warning, and having discussed that issue a little bit with the current maintainer, it seems to be a bug which will be fixed later on.
See the working code below with the correspondigly proposed solution:
main.cpp
#include <QQuickView>
#include <QQuickItem>
#include <QGuiApplication>
#include <QUrl>
class UI : public QQuickItem {
Q_OBJECT
Q_ENUMS(ObjectType)
public:
enum ObjectType {
Root = 0,
_Block
};
};
#include "main.moc"
int main(int argc, char **argv)
{
QGuiApplication guiApplication(argc, argv);
qmlRegisterType<UI>("Nodes", 1, 0, "UI");
QQuickView *view = new QQuickView;
view->setSource(QUrl::fromLocalFile("main.qml"));
view->show();
return guiApplication.exec();
}
main.qml
import Nodes 1.0
import QtQuick 2.0
Rectangle {
id: button
width: 500; height: 500
MouseArea {
anchors.fill: parent
onClicked: console.log(UI.Root)
}
}
main.pro
TEMPLATE = app
TARGET = main
QT += quick
SOURCES += main.cpp
Build and Run
qmake && make && ./main
Output
0
I had the exact same problem, and thanks to these solutions I figured out my problem. However, in case anyone else is dealing with the same mixup, here it is a little clearer.
I was using a class from C++ in QML just like here, so I had something like this in main.cpp
qmlRegisterType<EnumClass>("Enums", 1, 0, "EnumClass");
and in the .qml
import Enums 1.0
EnumClass {
id: ec
}
and tried and tried to access ec.SomeEnum but kept getting undefined even though, the QtCreator autocomplete said ec.SomeEnum should exist.
This simply does not work, and to get this to work I had to use
EnumClass.SomeEnum
instead (just like they do here).

Can't call slot or Q_INVOKABLE from QML in subclass of QQmlPropertyMap

I'm trying to test drive the QQmlPropertyMap class. It seems like it might work well for what I want, if I can subclass it. The documentation here even gives some rudimentary instructions on what to do for subclassing it. Said documentation also indicates that this class derives from QObject.
For what it's worth, I'm using QtCreator 2.6.1 on Qt 5.0.0 with QtQuick 2.0.
My main.qml:
import QtQuick 2.0
Rectangle {
width: 360
height: 360
Text {
text: owner.field
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
owner.testFunc();
}
}
}
My main.cpp:
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include "TestMap.h"
#include <QQmlContext>
int main(int argc, char *argv[])
{
TestMap* map = new TestMap();
QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer viewer;
QQmlContext* ctxt = viewer.rootContext();
ctxt->setContextProperty("owner", map);
viewer.setMainQmlFile(QStringLiteral("qml/TestMap/main.qml"));
viewer.showExpanded();
return app.exec();
}
My TestMap.h
#ifndef TESTMAP_H
#define TESTMAP_H
#include <QObject>
#include <QQmlPropertyMap>
#include <QDebug>
class TestMap: public QQmlPropertyMap // QObject
{
Q_OBJECT
public:
TestMap(QObject* parent = 0): QQmlPropertyMap(this, parent) // QObject(parent)
{
insert("field", "value"); // Comment this out
}
TestMap(const TestMap& value) { }
virtual ~TestMap() {}
public slots:
void testFunc()
{
qDebug() << "Success!";
}
};
Q_DECLARE_METATYPE(TestMap)
#endif
When I run, I get a window saying "value", as I'd expect. But when I click on the window, I get a console output saying
TypeError: Property 'testFunc' of object TestMap(0xaaa0b8) is not a function
I've looked for similar problems, but all the search results are about people that forgot to include the Q_OBJECT macro. It must be something I'm doing wrong in the code, because if I make all the changes indicated in the comments of the TestMap file (and leave the main.cpp and main.qml exactly as is), I get the qDebug message I expect.
I'm not sure whether I'm supposed to Q_DECLARE_METATYPE or not (I think the 2-arg protected constructor is supposed to do it for me), but it doesn't work either way.
For the record, the only things I have to change to get it to work are:
1) Derive from QObject instead of QQmlPropertyMap.
2) Change the constructor to:
TestMap(QObject* parent = 0): QObject(parent) {}
And that's it. Since it works when I don't change anything about the main.cpp, main.qml, or the slot itself, I have to conclude it's nothing wrong with those. Can anyone tell me what I'm doing wrong?
This is now fixed in Qt 5.1.0 and onwards. See the following codereview url for details:
https://codereview.qt-project.org/#change,57418

How do I interact with QML from C++ in QtQuick 2.0

Let’s say that we have a very simple QML file, like this one:
import QtQuick 2.0
Rectangle {
width: 800
height: 600
color: '#000'
Text {
text: qsTr("Hi all")
anchors.centerIn: parent
}
}
The QML File is loaded with the QtQuick2ApplicationViewer helper class, like this:
QtQuick2ApplicationViewer viewer;
viewer.setMainQmlFile(QStringLiteral("qml/MyApp/Login/Window.qml"));
viewer.showFullScreen();
How should I proceed, if for example I would like to change the Rectangle’s color to white, from C++. My guess was:
QQuickItem *window = viewer.rootObject();
window->setProperty("color", "#fff");
But all that does is the following compiler error:
invalid use of incomplete type 'struct QQuickItem'
forward declaration of 'struct QQuickItem'
Then QQuickItem was forward declared somewhere in a header you included, but not fully qualified. Here more information.
QObject *rootObject = (QObject *)viewer.rootObject();
rootObject->setProperty("color", "red");

Qt push button event using Qt Designer not working: "No such slot QApplication"

I've read several articles about push button events in Qt but none seem to solve my problem. I have a simple GUI built with Qt Designer which only contains one button. The run-time error I get is the following:
Object::connect: No such slot QApplication::FullSizeR() in CameraWindow.h:25
Object::connect: (sender name: 'FullSize')
Object::connect: (receiver name: 'CameraViewer')
FullSizeR() is the function I want called when My button is pushed.
Here's' how main is defined:
int main(int argc, char *argv[]) {
// initialize resources, if needed
// Q_INIT_RESOURCE(resfile);
QApplication app(argc, argv);
CameraWindow cw;
cw.show();
//app.setActiveWindow(&cw);
//cw.getData(); // this paints the window
return app.exec();
}
And this is how CameraWindow is defined:
class CameraWindow : public QDialog{
Q_OBJECT
public:
bool serverConnected;
void getData();
CameraWindow()
{
widget.setupUi(this); //this calls Qt Designer code
//the function bellow produces a run-time error
//access the button via widget.FullSize
connect(widget.FullSize,SIGNAL(clicked()), qApp, SLOT(FullSizeR()));
}
QLabel *imgl;
virtual ~CameraWindow();
protected slots:
void FullSizeR();
private:
Ui::CameraWindow widget;
};
I've properly included QObject and my function definition under 'slots'
This is the definition of FullSizeR:
void CameraWindow::FullSizeR()
{
QMessageBox::information(this,"Button clicked!\n", "Warning");
}
The above doesn't seem to be hard to solve. I know its something simple if I only knew Qt a bit better :-/
Thanks All
connect(widget.FullSize,SIGNAL(clicked()), qApp, SLOT(FullSizeR()));
The error message says it all: qApp doesn't have the slot. You need this:
connect(widget.FullSize, SIGNAL(clicked()), this, SLOT(FullSizeR()));