I'm trying to create a simple Task class that will be exposed to QML to do asynchronous operations in C++. To do this, I want to avoid subclassing QObject and instead I'm using the Q_GADGET macro as follows:
#pragma once
#include <QFuture>
// Workaround to use a templated class with Q_GADGET
class TaskBase {
Q_GADGET
};
template <typename T>
class Task : public TaskBase
{
public:
explicit Task() {}
// Creates a task from a QFuture
explicit Task(QFuture<T> future) : m_future(future)
{
auto watcher = new QFutureWatcher<T>();
QObject::connect(watcher, &QFutureWatcher<T>::finished, [this, watcher](){
qDebug () << "Finished, result is" << result();
watcher->deleteLater();
});
watcher->setFuture(future);
}
// Returns the result of the task
Q_INVOKABLE T result() { return m_future.result(); }
private:
QFuture<T> m_future;
};
// Register classes to QML
Q_DECLARE_METATYPE(Task<qint64>)
Q_DECLARE_METATYPE(Task<QString>)
Q_DECLARE_METATYPE(Task<bool>)
Then I'm using the class as follows:
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtConcurrent>
#include "task.h"
class HeavyComputer : public QObject
{
Q_OBJECT
public:
HeavyComputer(QObject *parent = nullptr) : QObject(parent) {}
Q_INVOKABLE Task<qint64> compute() const
{
return Task<qint64>(QtConcurrent::run([](){
qint64 sum = 0;
for(int i = 0; i < 100000; ++i){
sum += i;
}
return sum;
}));
}
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qRegisterMetaType<Task<qint64>>();
// Create a QML engine.
QQmlApplicationEngine engine;
// Register the HeavyComputer class
qmlRegisterType<HeavyComputer>("HeavyComputer", 1, 0, "HeavyComputer");
// Load the QML file.
const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl)
{
if (!obj && url == objUrl)
QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.load(url);
// Return the application's exit code.s
return app.exec();
}
#include "main.moc"
Using it from QML:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import HeavyComputer 1.0
Window {
id: window
visible: true
width: 640
height: 480
title: qsTr("Hello World")
HeavyComputer {
id: computer
}
Button {
anchors.bottom: parent.bottom
text: "Compute"
onClicked: {
computer.compute()
}
}
}
Now unfortunately, if you run the example and click on the button multiple times the application crashes. The debugger points out that the problem is in this line due a write access violation:
qDebug () << "Finished, result is" << result();
The stack information says this:
1 std::_Atomic_storage<QMutexData *,8>::compare_exchange_strong atomic 737 0x7ffb1d1ddec2
2 std::atomic<QMutexData *>::compare_exchange_strong atomic 1600 0x7ffb1d1ddfcc
3 QAtomicOps<QMutexData *>::testAndSetAcquire<QMutexData *> qatomic_cxx11.h 308 0x7ffb1d1d985a
4 QBasicAtomicPointer<QMutexData>::testAndSetAcquire qbasicatomic.h 273 0x7ffb1d1e8354
5 QBasicMutex::fastTryLock qmutex.h 109 0x7ffb1d1df761
6 QMutex::lock qmutex.cpp 230 0x7ffb1d21fb28
7 QMutexLocker::QMutexLocker qmutex.h 234 0x7ffb1d1da5bd
8 QFutureInterfaceBase::waitForResult qfutureinterface.cpp 317 0x7ffb1d236099
9 QFuture<__int64>::result qfuture.h 209 0x7ff7aba58421
10 Task<__int64>::result task.h 27 0x7ff7aba58467
11 <lambda_37427196d3c8e94283745470a735a1b5>::operator() task.h 20 0x7ff7aba56ce1
12 QtPrivate::FunctorCall<QtPrivate::IndexesList<>,QtPrivate::List<>,void,<lambda_37427196d3c8e94283745470a735a1b5>>::call qobjectdefs_impl.h 146 0x7ff7aba575d9
13 QtPrivate::Functor<<lambda_37427196d3c8e94283745470a735a1b5>,0>::call<QtPrivate::List<>,void> qobjectdefs_impl.h 257 0x7ff7aba53e43
14 QtPrivate::QFunctorSlotObject<<lambda_37427196d3c8e94283745470a735a1b5>,0,QtPrivate::List<>,void>::impl qobjectdefs_impl.h 449 0x7ff7aba57d4e
15 QtPrivate::QSlotObjectBase::call qobjectdefs_impl.h 398 0x7ffb1d64e9d2
16 doActivate<0> qobject.cpp 3886 0x7ffb1d6a6bb8
17 QMetaObject::activate qobject.cpp 3947 0x7ffb1d6936d7
18 QFutureWatcherBase::finished moc_qfuturewatcher.cpp 274 0x7ffb1d23a5f3
19 QFutureWatcherBasePrivate::sendCallOutEvent qfuturewatcher.cpp 442 0x7ffb1d23af0e
20 QFutureWatcherBase::event qfuturewatcher.cpp 336 0x7ffb1d23a581
... <More>
EDIT: the problem also only occurs if HeavyComputer::compute() is called from QML. Doing this in C++:
HeavyComputer computer;
computer.compute();
works just fine with no crashes.
Related
I need to parse a QML tree and get ids of all QML objects along the way which have it. I noticed that ids don't behave like normal properties (see the example below) – value returned from obj->property call is an invalid QVariant.
My question is – is there a way to retrieve object's id, even in some hacky (but reproductible) way?
Simplified example:
main.qml:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
Item {
id: howToGetThis
objectName: "item"
}
}
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QTimer>
#include <QDebug>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QTimer::singleShot(1000, [&]() {
auto item = engine.rootObjects()[0]->findChild<QObject*>("item");
qDebug() << item->property("objectName");
qDebug() << item->property("id");
});
return app.exec();
}
Output:
QVariant(QString, "item")
QVariant(Invalid)
I think what you need is:
QString QQmlContext::nameForObject(QObject *object)
You can find the description here:
https://doc.qt.io/qt-5/qqmlcontext.html#nameForObject
Returns the name of object in this context, or an empty string if object is not named in the context. Objects are named by setContextProperty(), or by ids in the case of QML created contexts.
Based on comments received, a common pitfall is to call nameForObject using the wrong QQmlContext. (When that happens, you just the empty string.) To help with that, here is a more complete example:
QQuickItem* const focus_item = my_QQuickWindow->activeFocusItem();
if (!focus_item) {
fprintf(stderr, "no item has focus");
} else {
// There are many contexts in a hierarchy. You have to get the right one:
QQmlContext* const context = qmlContext(focus_item);
if (!context) {
// Unsure if this branch of code is even reachable:
fprintf(stderr, "item is not in any context?");
} else {
const QString focus_item_id = context->nameForObject(focus_item);
fprintf(stderr, "focus item: %s\n", focus_item_id.toStdString().c_str());
}
}
In my C++ class I have
struct trackPoint {
QString lat;
QString lon;
QString elevation;
};
QVector<trackPoint> trackPoints;
In QML I want to access this as a multi-dimensional array of lon,lat pairs
[[0,1],[1,1],[2,1]]
Is this possible using the Q_Property mechanism? As I am pretty sure that structs cannot be exposed to QML?
I've tied:-
Q_PROPERTY(QVector<trackPoint> trackPoints READ gpx)
With a method:-
QVector<trackPoint> GPXFileIO::gpx() const {
return trackPoints;
}
But this gives me the error:-
QMetaProperty::read: Unable to handle unregistered datatype 'QVector<trackPoint>' for property 'GPXFileIO::trackPoints'
A simple way to expose a struct to QML is using Q_GADGET with Q_PROPERTY so we can get each element of the structure, they will not be part of an array. On the other hand QVector is supporting a number of elements with QString, int, QUrl, etc. but not for new types, in which case QVariantList should be used.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QVector>
struct TrackPoint {
Q_GADGET
Q_PROPERTY(qreal lat MEMBER lat)
Q_PROPERTY(qreal lon MEMBER lon)
Q_PROPERTY(qreal elevation MEMBER elevation)
public:
qreal lat;
qreal lon;
qreal elevation;
};
class TrackClass: public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList trackpoints READ gpx)
public:
TrackClass(QObject *parent=nullptr):QObject(parent){
trackPoints << TrackPoint{10, 10, 10} << TrackPoint{11, 11, 11};
}
QVariantList gpx() const{
QVariantList l;
for(const TrackPoint & p: trackPoints){
l << QVariant::fromValue(p);
}
return l;
}
private:
QVector<TrackPoint> trackPoints;
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
TrackClass track;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("track", &track);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component.onCompleted: {
for( var i in track.trackpoints){
var p = track.trackpoints[i];
console.log("lat: ", p.lat, "lon: ", p.lon, "elevation: ", p.elevation)
}
}
}
Output:
qml: lat: 10 lon: 10 elevation: 10
qml: lat: 11 lon: 11 elevation: 11
Without adding the complexity of gadgets, I find pretty straight forward the usage of QVariantList of QVariantMap, as Qvariant.
This is how I do it:
Q_PROPERTY(QVariant trackpoints READ gpx NOTIFY gpxChanged)
QVariant TrackClass::gpx() const
{
QVariantList itemsList;
for(const TrackPoint &p : trackPoints)
{
QVariantMap itemMap;
itemMap.insert("lat", p.lat);
itemMap.insert("lon", p.lon);
itemMap.insert("elevation", p.elevation);
itemsList.append(itemMap);
}
return QVariant::fromValue(itemsList);
}
Then in QML you can use trackpoints as model and access item fields by name.
It is a good practice to also add a NOTIFY signal, to be called when your QVector changes.
I am filling QLineSeries data on c++ side and QML chart is supposed to consume them (and update as they change). Data producer is connected to newData which are added to the series data, and that should trigger repaint of the chart.
Previously, the LineSeries were manipulated in QML but now I don't know how to make the c++ QLineSeries instances accessible to QML.
// ...
#include<QtCharts/QLineSeries>
using namespace QtCharts;
/* class holding all data to be displayed as properties */
class UiData: public QObject{
Q_OBJECT
Q_PROPERTY(QLineSeries *xy READ getXy NOTIFY xyChanged);
QLineSeries* getXy(){return &xy; }
signals:
void xyChanged();
public slots:
void newData(float x, float y){
xy.append(x,y);
emit xyChanged();
}
private:
QLineSeries xy;
}
int main(int argc, char* argv[]){
QApplication app(argc,argv);
QQmlApplicationEngine engine;
UiData uiData;
/* make the instance accessiblt from QML */
engine.rootContext()->setContextProperty("uiData",&uiData);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
// ...
return qApp->exec();
}
main.qml:
ChartView{
ValueAxis{
id: _axisX;
}
ValueAxis{
id: _axisY;
}
/*
BEFORE:
data in QML: easy, plus had JS slot calling _xySeries.append(...)
*/
LineSeries{
id: _xySeries;
axisX: _axisX;
axisY: _axisY;
}
/*
NOW:
how to use an already existing LineSeries instance (uiData.xy),
and only change a few attributes, like axisX and axisY?
Something like this:
LineSeries(uiData.xy) {
axisX: _axisX;
axisY: _axisY;
}
or like this:
component.onCompleted:{
// where is something like ChartView.addSeries?
// (there is ChartView.removeSeries already)
uiData.xy.axisX=_axisX;
uiData.xy.axisY=_axisY;
addSeries(uiData.xy);
}
*/
};
Is there a good solution to this?
In an extreme case, I can also create the whole ChartView in c++ (but then again, how to insert it into the QML?).
It can be said that LineSeries is a QLineSeries + other functionalities, so ChartView uses those other functionalities.
What you must do is access the LineSeries in UiData, for this you must use the memory position.
class UiData: public QObject{
Q_OBJECT
Q_PROPERTY(QLineSeries* xy READ xy WRITE setXy NOTIFY xyChanged)
public:
...
QLineSeries *xy() const
{
return mXy;
}
void setXy(QLineSeries *xy)
{
if(mXy == xy) return;
if(mXy)
if(mXy->parent() == this) // is owner
delete mXy;
mXy = xy;
emit xyChanged();
}
public slots:
void newData(qreal x, qreal y){
if(mXy){
mXy->append(x, y);
}
}
...
signals:
void xyChanged();
...
private:
QLineSeries *mXy;
};
*.qml
ChartView{
ValueAxis{
id: _axisX;
}
ValueAxis{
id: _axisY;
}
LineSeries{
id: _xySeries;
axisX: _axisX;
axisY: _axisY;
}
}
Component.onCompleted: uiData.xy = _xySeries // <----
As you can see, a QLineSeries is not being created in C ++, but the pointer serves only to reference LineSeries.
The complete example can be found in the following link.
I'm trying to pass a 2d QList as a Q_PROPERTY into QML, however, inside QML and i am unable to actually access any of the information.
some code:
c++:
the q_property get populated by a q_invokable function in the constructor:
void Class::createNewGameArray(){
QList<QList<QString>> testArray;
for( int i = 0; i < _intervals.size(); ++i) {
QList<QString> innerArray;
testArray.append(innerArray);
testArray[i].append(_intervals[i]);
testArray[i].append("Audio");
}
for( int i = 0; i < _intervals.size(); ++i) {
QList<QString> innerArray;
testArray.append(innerArray);
testArray[i+12].append(_intervals[i]);
testArray[i+12].append("Text");
}
std::random_shuffle(testArray.begin(),testArray.end());
Class::setGameArray(testArray);
emit gameArrayChanged(_newGameArray);
which returns this:
(("M7", "Text"), ("M3", "Text"), ("m3", "Text"), ("M6", "Audio"), ("TT", "Audio"), ("P4", "Text"), ("m7", "Audio"), ("m2", "Text"), ("m6", "Audio"), ("m6", "Text"), ("M7", "Audio"), ("P5", "Text"), ("P4", "Audio"), ("m2", "Audio"), ("M2", "Audio"), ("M3", "Audio"), ("P5", "Audio"), ("m3", "Audio"), ("M6", "Text"), ("TT", "Text"), ("m7", "Text"), ("Oct", "Audio"), ("Oct", "Text"), ("M2", "Text"))
exactly what i want.
i set the rootContext like so in main.cpp:
Class object;
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("object", &object);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
however, inside qml i only get
qml: QVariant(QList<QList<QString> >)
and am unable to actually do anything with it.
My goal, ideally, would be to be able to access the 2d qlist from qml in this manner:
object.gameArray[0][1]
// return "Text"
I'm able to do this with regular QLists (without the 2d). Any help would be greatly appreciated!
QML does not inherently understand QLists, so in general it is not possible to pass in a QList of any type T and have QML able to access the items inside the list.
However, the QML engine does have built in support for a few specific types of QList:
QList<QObject *>
QList<QVariant>
QStringList - (not QList<QString>!!!)
Therefore if you can construct your list of lists using any combination of the 3 types above, then you can have a working solution. In your use case I would suggest the following construction:
QList<QVariant(QStringList)>
A final note before we try it... Just because this will work, it does not necessarily mean that it is a good idea. The QList contents are copied to Javascript arrays at runtime, and therefore any minor updates to any of the lists from the C++ will cause the entire list to be reconstructed as a new Javascript array, which could be expensive.
Now, let's try it...
myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QStringList>
#include <QVariant>
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QVariant> variantList READ variantList NOTIFY variantListChanged)
public:
explicit MyClass(QObject *parent = nullptr) : QObject(parent),
m_variantList({
QStringList({ "apple", "banana", "coconut" }),
QStringList({ "alice", "bob", "charlie" }),
QStringList({ "alpha", "beta", "gamma" })
}) { }
QList<QVariant> variantList() const { return m_variantList; }
signals:
void variantListChanged();
public slots:
private:
QList<QVariant> m_variantList;
};
#endif // MYCLASS_H
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
Column {
id: column
// will add the strings here from the handler below
}
Component.onCompleted: {
console.log("variantList length %1".arg(myClass.variantList.length))
for (var i = 0; i < myClass.variantList.length; i++) {
console.log("stringList %1 length %2".arg(i).arg(myClass.variantList[i].length))
for (var j = 0; j < myClass.variantList[i].length; j++) {
// print strings to the console
console.log("variantList i(%1), j(%2) = %3".arg(i).arg(j).arg(myClass.variantList[i][j]))
// add the strings to a visual list so we can see them in the user interface
Qt.createQmlObject('import QtQuick 2.7; Text { text: "i(%1), j(%2) = %3" }'.arg(i).arg(j).arg(myClass.variantList[i][j]), column)
}
}
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "myclass.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
MyClass myClass;
engine.rootContext()->setContextProperty("myClass", &myClass);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
Runtime output
qml: variantList length 3
qml: stringList 0 length 3
qml: variantList i(0), j(0) = apple
qml: variantList i(0), j(1) = banana
qml: variantList i(0), j(2) = coconut
qml: stringList 1 length 3
qml: variantList i(1), j(0) = alice
qml: variantList i(1), j(1) = bob
qml: variantList i(1), j(2) = charlie
qml: stringList 2 length 3
qml: variantList i(2), j(0) = alpha
qml: variantList i(2), j(1) = beta
qml: variantList i(2), j(2) = gamma
... and it works :)
Automatic conversions would only work for several specific types of containers, and that's it. Just because conversion A works, and conversion B works, doesn't mean that conversion A will work too.
You can pretty much forget about using the [] operator in all the cases where automatic conversion doesn't work.
A variant list of variant lists however might just work. I haven't tested it myself but there is a slim hope. However, you will have to manually do the conversion before you pass that stuff to QML.
An approach that will most definitely work is to create accessor functions, like for example QString Class::get(int row, int col) or you can have separate accessors to select a row, and then pass that result to another function to select a column to give you the string.
I'm a noob - not good with QML or C++ yet, but getting there. I seem to have hit a stumbling block I can't get past. I'm receiving the following error in my attempt to run a build and I'm not sure what I missed in my code...
**QObject::connect: Cannot connect (null)::buttonClicked_enable() to TLA_Funcs::sys_enable()
**
I've looked through the other versions of the question here and it seems that I have my code correct, but I still get the error. Can someone take a peek? Here's the relevant sample of my code (the rest is too long and I left out the guts of the function - too long).
QML Code:
Rectangle {
id: main
width: 800
height: 600
color: "#abcfe9"
border.width: 4
border.color: "#000000"
signal buttonClicked_enable()
Button {
id: enable
x: 628
y: 55
text: "ENABLE"
onClicked:buttonClicked_enable()
}
//....
}
My class header:
#ifndef TLA_FUNCS_H
#define TLA_FUNCS_H
#include <QObject>
class TLA_Funcs : public QObject
{
Q_OBJECT
public:
explicit TLA_Funcs(QObject *parent = 0);
signals:
public slots:
Q_INVOKABLE void sys_enable(){return ;}
private:
};
#endif
And in my main.cpp file:
#include "TLA_Funcs.h"
TLA_Funcs::TLA_Funcs(QObject *parent) :
QObject(parent)
{
}
int main (int argc, char*argv[]) {
QGuiApplication app(argc, argv);
QQuickView *view = new QQuickView(QUrl("main.qml"));
view->show();
QQuickItem *item = view->rootObject();
TLA_Funcs *funcs = new TLA_Funcs();
QObject::connect(item, SIGNAL(buttonClicked_enable()), funcs, SLOT(sys_enable()));
}
I've defined the signals in the parent rectangle, and in the button code I've tried using:
onClicked:main.buttonClicked_enable()
as well as:
onClicked: {
buttonClicked_enable()
TLA_Funcs.sys_enable()
}
That didn't help either. I also tried defining the functions under "signal" in the class, but that made more of a mess. Can someone at least point me in the right direction, and keep in mind, I'm still a noob... Thanks All!!!
Problem Solved: there was an error in the .pro file. I copied the code into a new project and it worked correctly 100%. Thanks all for the help!