A QVariant I took from C++ contains a QJsonDocument, and I want to convert it to a js object, but I only found the use of QJsonDocument in C++. I couldn't find how to use QJsonDocument in qml.
For Example:
Qml:
function qmlUpdateObject( Object ){
console.log(Object);
}
// console content: qml: QVariant(QJsonDocument, QJsonDocument({"appDesc":{"description":"SomeDescription","message":"SomeMessage"},"appName":{"description":"Home","imp":["awesome","best","good"],"message":"Welcome"}}))
// I want to get a js object: {"appDesc":{"description":"SomeDescription","message":"SomeMessage"},"appName":{"description":"Home","imp":["awesome","best","good"],"message":"Welcome"}}
C++:
QObject::connect( this, SIGNAL( updateData(QVariant)),
viewItem, SLOT( qmlUpdateObject(QVariant)) );
void Controller::setData(QString name)
{
QFile file("data.json");
QJsonDocument d = QJsonDocument::fromJson(file.readAll());
QByteArray dataJson = d.toJson(QJsonDocument::Compact);
emit updateData(dataJson);
}
UPDATE
Add this line and pass back, qml can successfully parse:
QByteArray dataJson = d.toJson(QJsonDocument::Compact);
There is no direct QJsonDocument conversion to a js object. One possible solution is to convert the QJsonDocument to QByteArray and export it to QML, and in QML do parse using JSON.parse():
C++
QByteArray dataJson = doc.toJson(QJsonDocument::Compact);
// send dataJson to QML
QML
var json = JSON.parse(dataJson)
Note:
It seems that the OP does not know the good practice of not accessing QML objects from C++ but must create a QObject and export it to QML.
The example provided by the OP is not necessary to use QJsonDocument since JSON.parse() does the same as QJsonDocument d = QJsonDocument::fromJson(file.readAll());.
#include <QFile>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
class Controller: public QObject{
Q_OBJECT
public:
void setData(const QString & filename){
QFile file(filename);
if(file.open(QIODevice::ReadOnly))
Q_EMIT updateData(file.readAll());
}
Q_SIGNALS:
void updateData(const QByteArray & data);
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Controller controller;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("controller", &controller);
const QUrl url(QStringLiteral("qrc:/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);
controller.setData("data.json");
return app.exec();
}
#include "main.moc"
import QtQuick 2.14
import QtQuick.Window 2.14
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
function qmlUpdateObject( data ){
var obj = JSON.parse(data)
console.log(obj);
}
Connections{
target: controller
onUpdateData: qmlUpdateObject(data)
}
}
Related
I hope someone can help me with this. I have an external QML Module which accepts a QStringList as parameter. However, what I have is a simple String. My question is: Is there a way in QML to convert a list of Strings into a QStringList without any external c++ functions?
Thanks
I tried to pass a simple string but it is not accepted.
You can use a JavaScript array of strings or list<string> depending on your Qt version. Have a look here.
main.qml
import QtQuick
Rectangle {
id: root
width: 640
height: 480
property var jsArray: ["apple", "banana", "mango"]
property list<string> stringList: ["Oslo", "Berlin", "New York"]
Component.onCompleted: {
var arr = ["more", "strings", "here"]
applicationData.setSomething(arr)
applicationData.setSomething(root.stringList)
applicationData.setSomething(root.jsArray)
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlContext>
#include <QQuickView>
class ApplicationData : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE void setSomething(const QStringList &list) const
{
for (const auto &s : list)
qDebug() << s;
}
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
ApplicationData data;
view.rootContext()->setContextProperty("applicationData", &data);
view.setSource(QUrl(u"qrc:/75085103/main.qml"_qs));
view.show();
return app.exec();
}
#include "main.moc"
I have qtquick frontend and c++ backend. In Qml I have a checkbox with random initial value. I have a signal emitted onCheckedChanged which is never received. I believe, it is because the component is created before connect statements are made. When user interacts, I can catch those events but I miss the initial value. I cannot make the connections sooner because the QMl engine has to first create the components so I can have a reference to them to make the signal slot connection. So how to find out the initial value? Do I have to make a timer which will emit the value few seconds after startup? Is there a better way?
here is minimum example (when the initial value is true, the slot is never triggered)
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
signal checkboxStateChanged(enabled: bool)
CheckBox{
checked: Math.random() > 0.5
text: "value"
onCheckedChanged: checkboxStateChanged(checked)
}
}
backend.h
#include <QObject>
#include <QDebug>
class Backend: public QObject{
Q_OBJECT
public:
Backend(){}
public slots:
void logChecked(bool checked){
qDebug()<<checked;
}
};
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include "backend.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/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);
const auto root=engine.rootObjects();
QObject *window = root[0];
Backend b;
QObject::connect(window,SIGNAL(checkboxStateChanged(bool)),
&b,SLOT(logChecked(bool)));
return app.exec();
}
I am not sure about the approach you are trying but a cleaner method would be to bind the QML checked value to the c++ class. Then you can handle everything in your backend class which is a lot easier to debug and maintain. So using your example,
#include <QObject>
#include <QDebug>
class Backend: public QObject{
Q_OBJECT
// Add a Q_PROPERTY to bind in QML
Q_PROPERY(Qt::CheckState checked READ getChecked WRITE setChecked NOTIFY checkedChanged)
signals:
void checkedChanged();
public:
Backend() { m_checked = <random_value>;}
// implement the q_property methods
void setChecked(const Qt::CheckState value) {
if (m_checked != value) {
m_checked = value;
emit checkedChanged();
}
}
Qt::CheckState getChecked() const { return m_checked; }
public slots:
void logChecked(bool checked){
qDebug()<<checked;
}
private:
m_checked;
};
Then in QML:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQml 2.12 // Binding
import Backend 1.0
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
signal checkboxStateChanged(enabled: bool)
CheckBox{
id: qmlCheckBox
checked: backend.checked // C++ to QML binding
text: "value" // you can bind this too
}
// QML to C++ binding
Binding {
target: backend
property: "checked"
value: qmlCheckBox.checked
}
}
In main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include "backend.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// Register backend class for qml
qmlRegisterType<Backend>("Backend", 1, 0, "Backend");
engine.rootContext()->setContextProperty("backend", new Backend);
const QUrl url(QStringLiteral("qrc:/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 app.exec();
}
I don't want to expose a slot to QML, but is required to have a slot, because the slot is connected to internal signal.
I marked the slot as private, but the slots is accessible in QML e.g. Code completion/suggestion etc. My CPP Custom Class is registered in the main cpp.
h File:
#ifndef MYQMLTYPE_H
#define MYQMLTYPE_H
#include <QObject>
#include <QTimer>
class MyQMLType : public QObject
{
Q_OBJECT
Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)
public:
explicit MyQMLType(QObject *parent = nullptr);
public slots:
int increment(int value);
private slots:
void hideslot(void);
signals:
void messageChanged();
public:
QString message() const;
void setMessage(const QString& value);
private:
QString m_message;
QTimer *m_timer;
};
#endif // MYQMLTYPE_H
Cpp File:
#include "myqmltype.h"
MyQMLType::MyQMLType(QObject *parent) : QObject(parent)
{
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &MyQMLType::hideslot);
m_timer->start(1000);
}
int MyQMLType::increment(int value)
{
return value + 1;
}
void MyQMLType::hideslot()
{
// private slot
}
QString MyQMLType::message() const {
return m_message;
}
void MyQMLType::setMessage(const QString& value) {
if(m_message != value) {
m_message = value;
messageChanged(); // trigger signal of property change
}
}
Main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "MyQMLType.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<MyQMLType>("com.yourcompany.xyz", 1, 0, "MyQMLType");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/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 app.exec();
}
main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
import com.yourcompany.xyz 1.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
MyQMLType{
id: myqmltype
}
Rectangle{
id: rect
anchors.centerIn: parent
width: 100
height: 100
color: myqmltype.hideslot(); // This slot is avaiable in qml
}
}
Question:
Why is the private slot exposed to QML?
How to hide the slot?
Edit
Optimized Cpp File with private slot
Your premise is wrong
I don't want to expose a slot to QML, but is required to have a slot, because the slot is connected to internal signal.
Using the new Qt 5 connect syntax with pointer to member function you don't need a function to be a slot to be able to connect to it.
Just put your hideslot declaration in the private section of your class and you'll have what you want. It won't be exposed to QML and you would still be able to connect to it in C++.
As to why the private slot is exposed to QML, it is because all slots and Q_INVOKABLE functions are exposed to QML, regardless of their access.
I'm trying to update a MapCircle in QML from a signal in C++ and I'm veen having several issues with it all day.
In my class I have a Q_PROPERTY which is read only and holds the GPS positions of 4 UAVs in a QVariantList
class GCS: public QObject
{
Q_PROPERTY(QVariantList getUavPosition READ getUavPosition NOTIFY uavPositionSet)
public:
QVariantList getUavPosition() ;
signals:
void uavPositionSet();
public slots:
void setUavPosition();
void triggerPosition();
private:
QVariantList connected_uavs;
QVector<QGeoCoordinate> uav_positions;
};
I then define the functions as:
void GCS::setUavPosition()
{
double i = 0.0;
QGeoCoordinate uav_id;
uav_id.setLatitude(0.5);
uav_id.setLongitude(0.5 + i);
uav_id.setAltitude(5);
uav_positions.insert(0, uav_id);
connected_uavs.append( QVariant::fromValue(QGeoCoordinate(uav_positions[0].latitude(), uav_positions[0].longitude())));
i+=0.15;
emit uavPositionSet();
}
QVariantList GCS::getUavPosition()
{
return connected_uavs;
}
void GCS::triggerPosition()
{
setUavPosition();
ROS_INFO("Pos trig");
}
In my main function, I connect triggerPosition to a Timer so as to update the position periodically
int main(int argc, char *argv[])
{
ros::init(argc, argv, "planner");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
GCS gcs;
context->setContextProperty("planner", &gcs);
engine.load(QUrl(QStringLiteral("qrc:/planner.qml")));
QTimer *timer = new QTimer();
timer->setInterval(1000);
QObject::connect(&gcs, SIGNAL(uavPositionSet()), &gcs, SLOT(setUavPosition()));
QObject::connect(timer, SIGNAL(timeout()), &gcs, SLOT(triggerPosition()));
timer->start();
return app.exec();
}
However, when I run my program, there's a slight delay, my mouseArea becomes unusable and the program crashes. When I try to print the longitude to see if it updates, The initial value is printed out multiple times to the terminal but then the program crashes and there's is no MapCircle present on the map
The relevant part of My Qml file looks like this:
Map{
id: map
anchors.fill:parent
plugin: mapPlugin
center: QtPositioning.coordinate(0.5, 0.5)
zoomLevel:50
MapCircle{
id:uavPos
radius:2
color:'black'
border.width:3
}
Connections{
id:uavConnection
target: planner
onUavPositionSet:{
var data = planner.getUavPosition
uavPos.center = QtPositioning.coordinate(data[0].latitude, data[0].longitude)
console.log(data[0].longitude)
}
}
}
Can someone please kindly point me in the right direction here?
If you are going to handle information from several elements then it is better to use a model (together with a Repeater to create several elements), so it is only necessary to modify the role of an item:
gcs.h
#ifndef GCS_H
#define GCS_H
#include <QObject>
class QStandardItemModel;
class QAbstractItemModel;
class GCS: public QObject
{
Q_OBJECT
Q_PROPERTY(QObject* uavModel READ uavModel CONSTANT)
public:
enum UAVRoles {
PositionRole = Qt::UserRole + 1000
};
GCS(QObject *parent=nullptr);
QObject *uavModel() const;
public Q_SLOTS:
void triggerPosition();
private:
QStandardItemModel* m_uavModel;
};
#endif // GCS_H
gcs.cpp
#include "gcs.h"
#include <QGeoCoordinate>
#include <QStandardItemModel>
#include <random>
GCS::GCS(QObject *parent):
QObject(parent), m_uavModel(new QStandardItemModel(this))
{
m_uavModel->setItemRoleNames({{PositionRole, "position"}});
for(int i =0; i < 4; i++){
QStandardItem *item = new QStandardItem;
item->setData(QVariant::fromValue(QGeoCoordinate()), PositionRole);
m_uavModel->appendRow(item);
}
}
QObject *GCS::uavModel() const{
return m_uavModel;
}
void GCS::triggerPosition(){
std::mt19937 rng;
rng.seed(std::random_device()());
std::normal_distribution<> dist(-0.0001, +0.0001);
if(QStandardItem *item = m_uavModel->item(0)){
QGeoCoordinate uav_id;
uav_id.setLatitude(0.5 + dist(rng));
uav_id.setLongitude(0.5 + dist(rng));
uav_id.setAltitude(5);
item->setData(QVariant::fromValue(uav_id), PositionRole);
}
}
main.cpp
#include "gcs.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
GCS gcs;
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("planner", &gcs);
const QUrl url(QStringLiteral("qrc:/planner.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);
QTimer timer;
timer.setInterval(1000);
QObject::connect(&timer, &QTimer::timeout, &gcs, &GCS::triggerPosition);
timer.start();
return app.exec();
}
planner.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtLocation 5.14
import QtPositioning 5.14
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Plugin {
id: mapPlugin
name: "osm"
}
Map{
id: map
anchors.fill:parent
plugin: mapPlugin
center: QtPositioning.coordinate(0.5, 0.5)
zoomLevel:50
MapItemView{
model: planner.uavModel
delegate: MapCircle{
id:uavPos
radius: 2
color:'black'
border.width:3
center: QtPositioning.coordinate(model.position.latitude, model.position.longitude)
}
}
}
}
How do you call for example QFile::exists(path) inside a QML file in Qt 5.5?
MyFile.qml
import QtQuick 2.5
// These are some various things I've tried, with the error message they
// give related to the row where I call QFile::exists()
#include <QFile> // Expected token `{'
import QtQml 2.5 // Expected token `;'
import io.qt // Expected token `;'
import io.qt.QFile // Expected token `;'
Item {
id: root
property string imageSource
Image {
id: test
source: fileOrFallback(root.imageSource)
}
function fileOrFallback (source) {
return QFile::exists(source)
? preprocessor.getFilePath(source)
: theme.example + 'placeholder.png'
}
}
I've seen some examples on how to import your custom Qt functions, but how do you call built-in Qt functions in QML?
You cannot directly import a C ++ function, in these cases the approach is to create a QObject that exposes the method through a Q_INVOKABLE:
backend.h
#ifndef BACKEND_H
#define BACKEND_H
#include <QObject>
class Backend : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
Q_INVOKABLE bool exists(const QString &fileName);
};
#endif // BACKEND_H
backend.cpp
#include "backend.h"
#include <QFile>
bool Backend::exists(const QString &fileName){
return QFile::exists(fileName);
}
main.cpp
#include "backend.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
const QUrl url(QStringLiteral("qrc:/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 app.exec();
}
*.qml
// ...
function fileOrFallback (source) {
return backend.exists(source)
? preprocessor.getFilePath(source)
: theme.example + 'placeholder.png'
}
// ...