Calling C++ method from QML (Qt Quick application) - c++

So, I've done some searching and none of the similar questions I've read have had advice that worked.
I'm using Qt Creator (and I'm not too familiar with Qt) so I'm not sure what voodoo it's done in the background. However, I'm using a standard Qt Quick Application project.
Essentially, I want to call a C++ function from QML that returns a string that replaces some text in the layout, periodically.
Here is main.cpp:
#include <QtGui/QApplication>
#include "qmlapplicationviewer.h"
#include <QDeclarativeContext>
class testClass : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE QString gimmeText() {
return QString("new text");
}
};
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
QmlApplicationViewer viewer;
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/Picenter2/main.qml"));
testClass t;
viewer.rootContext()->setContextProperty("testOb", &t);
viewer.showFullScreen();
return app->exec();
}
And here is a snippet of the layout (as most of it is obviously irrelevant):
Text {
id: text1
x: 105
y: 156
color: "#ffffff"
text: qsTr("text")
font.pixelSize: 12
Timer {
interval: 1000; running: true; repeat: false
onTriggered: text1.text = testOb.gimmeText();
}
The errors given are:
invalid use of incomplete type 'struct QDeclarativeContext' main.cpp (28)
forward declaration of 'struct QDeclarativeContext' qdeclarativeview.h (60)
EDIT: with QDeclarativeContext included, the above disappear, giving these errors:
(.text.startup+0x3e):-1: error: undefined reference to `vtable for testClass'
(.text.startup+0xcf):-1: error: undefined reference to `vtable for testClass'
(.text.startup+0x12b):-1: error: undefined reference to `vtable for testClass'
:-1: error: collect2: ld returned 1 exit status
I haven't done much C++ programming, so I'm not entirely familiar with what that means. Following advice for essentially identical problems has only given me vtable errors or more incomprehensible things.
What really confuses me is that, looking at the header file, the QmlApplicationViewer is derived from QDeclarativeView, which is exactly what the Qt documentation uses here to do almost exactly what I want. Thanks for any suggestions anyone has.

You have to register your class for using it with QML. You can do this in the main function. You have to import it in the QML code too. Your code should look like this :
main.cpp :
#include <QtGui/QApplication>
#include "qmlapplicationviewer.h"
#include <QDeclarativeContext>
#include <QtDeclarative> // Required for registration
class testClass : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE QString gimmeText() {
return QString("new text");
}
};
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
qmlRegisterType<testClass>("MyCustomQMLObjects", 2, 35, "testClassNameInQML");
QmlApplicationViewer viewer;
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/Picenter2/main.qml"));
testClass t;
viewer.rootContext()->setContextProperty("testOb", &t);
viewer.showFullScreen();
return app->exec();
}
QML code :
// ...
import MyCustomQMLObjects 2.35
// ...
property testClassNameInQML testOb
// ...
Text {
id: text1
x: 105
y: 156
color: "#ffffff"
text: qsTr("text")
font.pixelSize: 12
Timer {
interval: 1000; running: true; repeat: false
onTriggered: text1.text = testOb.gimmeText();
}
// ...

I have no experience with qt and I cant see what in the code triggers the error. However when such errors occur, it is because a class (struct QDeclarativeContext) has been forward declared, but is used as if the entire definition is known (access member, declare variable of this type, etc). To fix this you need to include the header which has this type's definition.

Related

ERROR that looks so main.cpp:(.text.startup+0xd6): undefined reference to `vtable for Counter'?

I have naturally trivial question as I mean: we press button --> counter increases, counter increases --> QLabel's value is renewed. I caught strange error and don't want to do. I'm not dummy in C++ but in QT I am. It's my first and most trivial application in it.
Some answers there (on Stack Overflow) advised to add virtual constructor. It has no effect.
I tried to rewrite signals and slots to new qt5 style but there were another problems, I was too lazy to fix them, was it (rewriting, not laziness :) ) a good way, maybe problem is really with versions?
I just haven't tried to reinstall QT or install Qt4, maybe problem is in it?
about versions:
$ qmake --version
responds:
QMake version 3.0
Using Qt version 5.5.1 in /usr/lib/x86_64-linux-gnu
conn.pro:
TEMPLATE = app
QT += core gui
TARGET = conn
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
SOURCES += main.cpp
main.cpp:
#include <QApplication>
#include <QLabel>
#include <QPushButton>
#include <QObject>
class Counter : public QObject {
Q_OBJECT
private:
double i_;
public:
virtual ~Counter()
{
}
Counter() : QObject(), i_(0)
{
}
public slots:
void slot_incCounter();
signals:
void goodbye(){}
void counterChanged(double){}
};
void Counter::slot_incCounter() {
emit counterChanged(++i_);
if (i_ == 5) {
emit goodbye();
}
}
int main(int argc, char* argv[]) {
QApplication my_app(argc, argv);
QLabel label1("label i created");
label1.show();
QPushButton button1("press me");
button1.show();
Counter counter1;
QObject::connect(&button1, SIGNAL(clicked()),
&counter1, SLOT(slot_incCounter()));
QObject::connect(&counter1, SIGNAL(counterChanged(double a)),
&label1, SLOT(setNum(double a)));
QObject::connect(&counter1, SIGNAL(goodbye()),
&my_app, SLOT(quit()));
return my_app.exec();
}
Try to run it:
qmake && make && ./conn
So I see in console:
g++ -m64 -Wl,-O1 -o conn main.o -L/usr/X11R6/lib64 -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
main.o: In function `main':
main.cpp:(.text.startup+0xd6): undefined reference to `vtable for Counter'
collect2: error: ld returned 1 exit status
Makefile:144: recipe for target 'conn' failed
make`:` *** [conn] Error 1
What should I do?
Qt uses the meta object compiler (moc) to enable e.g. signal and slots. By default it works perfectly if the Q_OBJECT macro is in a header file. So the easiest would be you put Counter into it's own header/implementation file, rerun qmake and make. (That's by the way good practice...)
If you want to stick with a single main.cpp file you need to tell the moc explicitly that this file contains macros moc needs to parse. You do this with the following line at the very end of main.cpp:
#include "main.moc"
Then also rerun qmake and make.
Please keep in mind that the manually including a moc-include directive is not the best choice. So better split your C++ classes into separate files right from the beginning...
Thank you very much! Your answer was full, useful and making all more obvious.
Solution was:
1. Move class Counter to Counter.h
Since this moment the message about vtable disappeared. Appeared messages that goodbye() and Counter::counterChanged(double) have multiple definition. The first definition was mine in Counter.cpp (WRONG WAY). The second was in moc_Counter.cpp, generated by MOC utility. So:
2. Remove definitions (my empty definitions) of signal functions, because moc makes its own in file moc_Counter.cpp:
// SIGNAL 0
void Counter::goodbye()
{
QMetaObject::activate(this, &staticMetaObject, 0, Q_NULLPTR);
}
// SIGNAL 1
void Counter::counterChanged(double _t1)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
and they cause a problem of multiple definition.
Summing it up, working code:
main.cpp:
#include <QApplication>
#include "Counter.h"
int main(int argc, char* argv[]) {
QApplication my_app(argc, argv);
QLabel label1("1");
label1.show();
QPushButton button1("press me");
button1.show();
Counter counter1;
QObject::connect(&button1, SIGNAL(clicked()),
&counter1, SLOT(slot_incCounter()));
QObject::connect(&counter1, SIGNAL(counterChanged(double)),
&label1, SLOT(setNum(double)));
QObject::connect(&counter1, SIGNAL(goodbye()),
&my_app, SLOT(quit()));
return my_app.exec();
}
void Counter::slot_incCounter() {
emit counterChanged(++i_);
if (i_ == 5) {
emit goodbye();
}
}
Counter.h:
#ifndef COUNTER_H
#define COUNTER_H
#include <QLabel>
#include <QPushButton>
#include <QObject>
class Counter : public QObject {
Q_OBJECT
private:
double i_;
public:
virtual ~Counter()
{
}
Counter() : QObject()
{
}
public slots:
void slot_incCounter();
signals:
void goodbye();
void counterChanged(double);
};
#endif // COUNTER_H
Counter.cpp:
#include "Counter.h"
Thank you, you're great!

Define signals and slots inside main.cpp

I wrote a little program with a my own class within the main.cpp. Here the code:
#include <QApplication>
#include <QPushButton>
#include <QLabel>
class MyWidget : public QWidget {
//Q_OBJECT
public:
MyWidget(QWidget* parent = 0);
QLabel* label;
QString string;
signals:
public slots:
void setTextLabel();
};
void MyWidget::setTextLabel() {
label->setText("Test");
}
MyWidget::MyWidget(QWidget* parent)
: QWidget(parent) {
}
int main(int argc, char** argv) {
QApplication app(argc, argv);
MyWidget widget;
widget.show();
return app.exec();
}
it seems work but not "completely". My slot doens't work. I suppose i have to put Q_OBJECT. BUT, doing so, I got a list of errors, like this:
undefined reference to `vtable for MyWidget'
........................................
collect2: error: ld returned 1 exit status
make: *** [mywidget] Error 1
I can I manage that? Where the problem?
Signals and slots in Qt are managed through the moc: meta object compiler. Basically, the moc generates additional C++ code for each class containing the Q_OBJECT macro in order to implement effectively the signals and slots mechanisms. The additional code is then linked to the original class declaration.
The problem here is that your class is declared in main.cpp: this conflicts with how the moc is working with your code. You should declare your class in a separate header.
More about the moc
Edit: as hyde pointed, an alternative is to include in your cpp the file generated by the moc: Why is important to include “.moc” file at end of a Qt Source code file?
just append the line #include"main.moc" to your cpp source file should be enough.
More information:
Why is important to include ".moc" file at end of a Qt Source code file?

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