Control OSM map in QML qt - c++

I am trying to control the map in Qt however I keep ending up with the following error:
QGeoTileRequestManager: Failed to fetch tile (291271,152514,19) 5 times, giving up. Last error message was: 'Permission denied'
I have functions in C++ that parse the messages and calculates the position:
Map.qml
import QtQuick 2.12
import QtQuick.Window 2.14
import QtQuick.Controls 2.15
import QtLocation 5.6
import QtPositioning 5.6
import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.12
import Qt.labs.location 1.0
Page {
id: mainWindow
visible: true
function addMarker(latitude, longitude)
{
var Component = Qt.createComponent("qrc:/Marker.qml")
var item = Component.createObject(window, {
coordinate: QtPositioning.coordinate(latitude, longitude)
})
map.addMapItem(item)
}
Map {
id: map
width: mainWindow.width
height: mainWindow.height
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75)
Component.onCompleted: addMarker(59.91, 10.75)
zoomLevel: 60
}
Plugin {
id: mapPlugin
name: "osm" // "mapboxgl", "esri", ...
// specify plugin parameters if necessary
PluginParameter {
name: "osm.mapping.providersrepository.disabled"
value: "true"
}
PluginParameter {
name: "osm.mapping.providersrepository.address"
value: "http://maps-redirect.qt.io/osm/5.6/"
}
}
}
Setting the coordinates is done through Q_PROPERTY:
#include <QObject>
class Data : public QObject
{
Q_OBJECT;
Q_PROPERTY(double gnss_log READ gnss_long WRITE set_gnss_long NOTIFY gnss_long_changed);
Q_PROPERTY(double gnss_lat READ gnss_lat WRITE set_gnss_lat NOTIFY gnss_lat_changed);
public: signals:
void gnss_long_changed();
void gnss_lat_changed();
public slots:
void set_gnss_long(double);
void set_gnss_lat(double);
public:
Data();
double gnss_long();
double gnss_lat();
private:
double m_gnss_long;
double m_gnss_lat;
};
void Data::set_gnss_long(double curr_long)
{
// Checks whether updated baud rate changed
if (curr_long == m_gnss_long)
return;
m_gnss_long = curr_long;
qDebug() << m_gnss_long;
//Emits signal indicating change
emit gnss_long_changed();
}
double Data::gnss_long()
{
return m_gnss_long;
}
I when I run this, I get a blank screen with a bunch of the errors mentioned above.

Since the OP has not provided an MRE my answer will only show a demo. The logic is to create a QProperty that exposes the position, then a binding must be done:
#include <QGeoCoordinate>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
#include <random>
class Data: public QObject{
Q_OBJECT
Q_PROPERTY(QGeoCoordinate gnssPosition READ gnssPosition WRITE setGnssPosition NOTIFY gnssPositionChanged)
public:
const QGeoCoordinate &gnssPosition() const{
return m_gnssPosition;
}
void setGnssLatitude(qreal latitude){
QGeoCoordinate coordinate(latitude, m_gnssPosition.longitude());
setGnssPosition(coordinate);
}
void setGnssLongitude(qreal longitude){
QGeoCoordinate coordinate(m_gnssPosition.latitude(), longitude);
setGnssPosition(coordinate);
}
void setGnssPosition(const QGeoCoordinate &newGnssPosition){
if (m_gnssPosition == newGnssPosition)
return;
m_gnssPosition = newGnssPosition;
emit gnssPositionChanged();
}
signals:
void gnssPositionChanged();
private:
QGeoCoordinate m_gnssPosition;
};
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
Data data_out;
data_out.setGnssPosition(QGeoCoordinate(59.91273, 10.74609));
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("data_out", &data_out);
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);
QTimer timer;
timer.setInterval(1000);
QObject::connect(&timer, &QTimer::timeout, &data_out, [&data_out](){
std::random_device rd;
std::mt19937 e2(rd());
std::uniform_real_distribution<> dist(-.05, .05);
QGeoCoordinate coord = data_out.gnssPosition();
coord.setLatitude(coord.latitude() + dist(e2));
coord.setLongitude(coord.longitude() + dist(e2));
data_out.setGnssPosition(coord);
// qDebug() << data_out.gnssPosition();
});
timer.start();
return app.exec();
}
#include "main.moc"
import QtQuick 2.15
import QtQuick.Window 2.15
import QtLocation 5.15
import QtPositioning 5.15
Window {
id: mainWindow
width: 640
height: 480
visible: true
title: qsTr("Hello World")
property Component markerProvider: MapQuickItem {
anchorPoint.x: rect.width / 2
anchorPoint.y: rect.height / 2
sourceItem: Rectangle{
id: rect
width: 40
height: 40
color: "salmon"
}
}
function addMarker(coordinate){
var marker = markerProvider.createObject()
console.log(marker)
marker.coordinate = coordinate
map.addMapItem(marker)
}
Map {
id: map
width: mainWindow.width
height: mainWindow.height
plugin: mapPlugin
center: data_out.gnssPosition
zoomLevel: 12
}
Plugin {
id: mapPlugin
name: "osm" // "mapboxgl", "esri", ...
// specify plugin parameters if necessary
PluginParameter {
name: "osm.mapping.providersrepository.disabled"
value: "true"
}
PluginParameter {
name: "osm.mapping.providersrepository.address"
value: "http://maps-redirect.qt.io/osm/5.6/"
}
}
Connections{
target: data_out
function onGnssPositionChanged(){
addMarker(data_out.gnssPosition)
}
}
}

Related

Adding new places to the QML map is not working

I am currently creating a program that can mark places on the map.
map.qml:
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtLocation 5.6
import QtPositioning 5.6
Rectangle {
ListModel {
id: locationModel
}
Plugin {
id: mapPlugin
name: "esri"
}
Map {
id: place
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(51.5, 0.1)
zoomLevel: 7
MapItemView
{
model: locationModel
delegate: mapcomponent
}
}
Component {
id: mapcomponent
MapQuickItem {
id: marker
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
coordinate: QtPositioning.coordinate(lat, lon)
sourceItem: Image {
id: image
width: 100
height: 50
source: "qrc:/rec/marker.png"
}
}
}
function addPlace(lat: double, lon: double){
locationModel.append({"lat": lat, "lon": lon})
console.log("Done")
}
}
mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->quickWidget->setSource(QUrl("qrc:/rec/map.qml"));
QQmlComponent component(ui->quickWidget->engine(), ui->quickWidget->source());
object = component.create();
}
/*Some not interesting code.*/
void MainWindow::on_pushButton_4_clicked()
{
double lat = 50.00;
double lon = 20.00;
QMetaObject::invokeMethod(object, "addDevice",
Q_ARG(double, lat),
Q_ARG(double, lon));
}
object is defined in mainwindow.h like this: QObject *object;.
The add place function is called from a c ++ file when a certain button is clicked. Unfortunately, for some reason, this function does not work properly. After invoking it, the message "Done" appears in the console and the location of the point to be marked on the map is added to the ListModel. Unfortunately, the marker does not appear on the map. What am I doing wrong? I will be grateful for any advice.
The problem is that you are creating a new component with a new map and you are adding the markers to that new invisible map.
Instead of exposing QML objects to C++ it is better to do the opposite:
#ifndef PLACEHELPER_H
#define PLACEHELPER_H
#include <QObject>
class PlaceHelper : public QObject
{
Q_OBJECT
public:
explicit PlaceHelper(QObject *parent = nullptr);
void addPlace(double latitude, double longitude);
Q_SIGNALS:
void add(double latitude, double longitude);
};
#endif // PLACEHELPER_H
#include "placehelper.h"
PlaceHelper::PlaceHelper(QObject *parent) : QObject(parent)
{
}
void PlaceHelper::addPlace(double latitude, double longitude)
{
Q_EMIT add(latitude, longitude);
}
*.h
// ..
private:
Ui::MainWindow *ui;
PlaceHelper placeHelper;
};
*.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QQmlContext>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->quickWidget->rootContext()->setContextProperty("placeHelper", &placeHelper);
ui->quickWidget->setSource(QUrl("qrc:/rec/map.qml"));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_4_clicked()
{
double lat = 50.00;
double lon = 0.10;
placeHelper.addPlace(lat, lon);
}
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtLocation 5.6
import QtPositioning 5.6
Rectangle {
ListModel {
id: locationModel
}
Plugin {
id: mapPlugin
name: "esri"
}
Map {
id: place
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(51.5, 0.1)
zoomLevel: 7
MapItemView
{
model: locationModel
delegate: mapcomponent
}
}
Component {
id: mapcomponent
MapQuickItem {
id: marker
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
coordinate: QtPositioning.coordinate(lat, lon)
sourceItem: Image {
id: image
width: 100
height: 50
source: "qrc:/rec/marker.png"
}
}
}
Connections{
target: placeHelper
function onAdd(latitude, longitude){
locationModel.append({"lat": latitude, "lon": longitude})
}
}
}
Another solution is for the model to be implemented in C ++ and exported to QML:
#ifndef PLACEMODEL_H
#define PLACEMODEL_H
#include <QStandardItemModel>
class PlaceModel: public QStandardItemModel
{
Q_OBJECT
public:
enum PlaceRoles {
LatitudeRole = Qt::UserRole + 1,
LongitudeRole
};
PlaceModel(QObject *parent=nullptr);
Q_INVOKABLE void addPlace(double longitude, double latitude);
};
#endif // PLACEMODEL_H
#include "placemodel.h"
PlaceModel::PlaceModel(QObject *parent):
QStandardItemModel(parent)
{
setItemRoleNames({{LatitudeRole, "lat"},
{LongitudeRole, "lon"}});
}
void PlaceModel::addPlace(double latitude, double longitude)
{
QStandardItem *item = new QStandardItem;
item->setData(latitude, LatitudeRole);
item->setData(longitude, LongitudeRole);
appendRow(item);
}
*.h
private:
Ui::MainWindow *ui;
PlaceModel placeModel;
*.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QQmlContext>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->quickWidget->rootContext()->setContextProperty("placeModel", &placeModel);
ui->quickWidget->setSource(QUrl("qrc:/rec/map.qml"));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_4_clicked()
{
double lat = 50.00;
double lon = 0.10;
placeModel.addPlace(lat, lon);
}
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtLocation 5.6
import QtPositioning 5.6
Rectangle {
Plugin {
id: mapPlugin
name: "esri"
}
Map {
id: place
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(51.5, 0.1)
zoomLevel: 7
MapItemView
{
id: mapView
model: placeModel
delegate: mapcomponent
}
}
Component {
id: mapcomponent
MapQuickItem {
id: marker
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
coordinate: QtPositioning.coordinate(lat, lon)
sourceItem: Image {
id: image
width: 100
height: 50
source: "qrc:/rec/marker.png"
}
}
}
}

Access an QObject class from another QObject class by reference and parallel using in qml [duplicate]

I tried to operate a part of a qt project in Qt\Examples\Qt-5.9\quick\views, I am new to qml and I am trying to open each time a different QDialog window depending on qml pathview component that has been clicked. First of all, I started with creating a class (interfacageQML) which will serve to interface the qml Mainform and the QDialog (qtinterface), the necessary files are included among which interfacageqml.h.
here is the main.cpp :
#include "interfacageqml.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<interfacageQML>("Interfacage", 1, 0,"Component:MouseArea");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
And here's interfacageqml.h :
#ifndef INTERFACAGEQML_H
#define INTERFACAGEQML_H
#include <QObject>
#include "qtinterface.h"
class interfacageQML : public QObject
{
Q_OBJECT
public:
interfacageQML(QObject *parent);
~interfacageQML();
Q_INVOKABLE void mouseClick();
signals:
void clicked();
};
#endif // INTERFACAGEQML_H
interfacageqml.cpp :
#include "interfacageqml.h"
#include <QDebug>
#include <QApplication>
interfacageQML::interfacageQML(QObject *parent)
: QObject(parent)
{
}
interfacageQML::~interfacageQML()
{
}
void interfacageQML::mouseClick()
{
qDebug() << "qmlinterface::mouseClick()";
emit clicked();
}
My project is organised this way :
the qmlinterface.qrc file contains these paths:
main.qml :
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MainForm{
anchors.fill: parent
}
}
MainForm.qml :
import QtQuick 2.6
import QtQuick.Controls 2.0 as QQC2
import Interfacage 1.0
Rectangle {
width: 800
height: 800
color: "white"
ListModel {
id: appModel
ListElement {
name: "Contacts"
icon: "pics/Resources/AddressBook_48.png"
}
ListElement {
name: "Music"
icon: "pics/Resources/AudioPlayer_48.png"
}
ListElement {
name: "Movies"
icon: "pics/Resources/VideoPlayer_48.png"
}
ListElement {
name: "Camera"
icon: "pics/Resources/Camera_48.png"
}
ListElement {
name: "Calendar"
icon: "pics/Resources/DateBook_48.png"
}
ListElement {
name: "Todo List"
icon: "pics/Resources/TodoList_48.png"
}
}
Component {
id: appDelegate
Item {
width: 100
height: 100
scale: PathView.iconScale
Image {
id: myIcon
y: 20
anchors.horizontalCenter: parent.horizontalCenter
source: icon
}
Text {
anchors {
top: myIcon.bottom
horizontalCenter: parent.horizontalCenter
}
text: name
}
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = index
Interfacage.mouseClick
}
}
}
}
Component {
id: appHighlight
Rectangle {
width: 100
height: 80
color: "lightsteelblue"
}
}
PathView {
id: view
anchors.fill: parent
highlight: appHighlight
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
focus: true
model: appModel
delegate: appDelegate
path: Path {
startX: 50
startY: 80
PathAttribute {
name: "iconScale"
value: 2.0
}
PathQuad {
x: 250
y: 200
controlX: 50
controlY: 200
}
PathAttribute {
name: "iconScale"
value: 2.0
}
PathQuad {
x: 600
y: 50
controlX: 400
controlY: 200
}
PathAttribute {
name: "iconScale"
value: 2.0
}
}
}
}
When I run this project, i got an error :
error:C2280
However, when I comment this line : qmlRegisterType<interfacageQML>("Interfacage", 1, 0, "Component:MouseArea"); the project runs and I can navigate between the pathview components in the MainForm.
When you use qmlRegisterType you are registering a new data type in QML, it is not an object, in that case the name "Component: MouseArea" is not suitable.
qmlRegisterType<interfacageQML>("Interfacage", 1, 0, "InterfacageQML");
Another error is that you must pass a parent by default, in this case 0 or nullptr since the items may not have parents.
class interfacageQML : public QObject
{
Q_OBJECT
public:
explicit interfacageQML(QObject *parent = nullptr);
[...]
As I said in the first lines, this is a new type, it is not an object so you must create it.
import QtQuick 2.6
import QtQuick.Controls 2.0 as QQC2
import Interfacage 1.0
Rectangle {
width: 800
height: 800
color: "white"
InterfacageQML{
id: myitem
}
[...]
And in the end if you want to use it you must call the function through the item.
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = index
myitem.mouseClick()
}
}
Since you want to connect your QDialog with the QML through that class, you can not do it since they will be different objects, one solution for this is to use a singleton, for this you must do the following:
interfacageqml.h
#ifndef INTERFACAGEQML_H
#define INTERFACAGEQML_H
#include <QObject>
#include <QQmlEngine>
class interfacageQML : public QObject
{
Q_OBJECT
static interfacageQML* instance;
explicit interfacageQML(QObject *parent = nullptr);
public:
static interfacageQML *getInstance();
~interfacageQML();
Q_INVOKABLE void mouseClick();
signals:
void clicked();
};
#endif // INTERFACAGEQML_H
interfacageqml.cpp
#include "interfacageqml.h"
#include <QDebug>
interfacageQML* interfacageQML::instance = 0;
interfacageQML *interfacageQML::getInstance()
{
if (instance == 0)
instance = new interfacageQML;
return instance;
}
interfacageQML::interfacageQML(QObject *parent) : QObject(parent)
{
}
interfacageQML::~interfacageQML()
{
}
void interfacageQML::mouseClick()
{
qDebug() << "qmlinterface::mouseClick()";
emit clicked();
}
main.cpp
#include "interfacageqml.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
static QObject *singletonTypeProvider(QQmlEngine *, QJSEngine *)
{
return interfacageQML::getInstance();
}
int main(int argc, char *argv[])
{
qmlRegisterSingletonType<interfacageQML>("Interfacage", 1, 0, "InterfacageQML", singletonTypeProvider);
// test
interfacageQML *obj = qobject_cast<interfacageQML*>(interfacageQML::getInstance());
QObject::connect(obj, &interfacageQML::clicked,[]{
qDebug()<<"clicked";
});
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
As it is a singleton it is not necessary to create an item, you can do it directly:
import Interfacage 1.0
[...]
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = index
InterfacageQML.mouseClick()
}
}
This last example can be found in the following link.

Mapping MapPolyline on the map QML. C++/Qt

On the instructions I should to display map on the screen and draw on it the line. Try to do this:
main.cpp
#include <QGeoPath>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
class PathController: public QObject{
Q_OBJECT
Q_PROPERTY(QGeoPath geopath READ geoPath WRITE setGeoPath NOTIFY geopathChanged)
public:
PathController(QObject *parent = 0) : QObject(parent) {}
QGeoPath geoPath() const {
return mGeoPath;
}
void setGeoPath(const QGeoPath &geoPath) {
if(geoPath != mGeoPath) {
mGeoPath = geoPath;
emit geopathChanged();
}
}
signals:
void geopathChanged();
private:
QGeoPath mGeoPath;
};
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QGeoPath path;
path.addCoordinate(QGeoCoordinate(55.006355, 92.860984));
path.addCoordinate(QGeoCoordinate(55.1, 93));
path.addCoordinate(QGeoCoordinate(56.1, 92.777));
PathController controller;
controller.setGeoPath(path);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("pathController", &controller);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml
import QtLocation 5.6
import QtPositioning 5.6
Window {
visible: true
width: 640
height: 480
Plugin {
id: osmMapPlugin
name: "osm"
}
Map {
anchors.fill: parent
plugin: osmMapPlugin
center: QtPositioning.coordinate(56.006355, 92.860984)
zoomLevel: 10
MapPolyline {
id: pl
line.width: 3
line.color: 'red'
}
}
Connections{
target: pathController
onGeopathChanged: {
var lines = []
for(var i=0; i < pathController.geopath.size(); i++){
lines[i] = pathController.geopath.coordinateAt(i)
}
pl.path = lines
}
}
}
However, get a blank map. The lines do not appear. Question - how to render lines? They do exist, just like "invisible".
Should be a great triangle
And if you call printf("%s" controller.geoPath().toString().toUtf8().constData()); after assignment - you will see exactly what I entered. But the map is empty for some reason. Help, please.
The error is caused because when a new QGeoPath is established through setGeoPath, it issues the signal to update but at that moment the .qml is not started so the connection is not made so onGeopathChanged is not called in qml since after the first change it never changed back.
A possible solution is to use Component.onCompleted so that it notifies next to onGeopathChanged that there is a change as I show in the following code:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtLocation 5.6
import QtPositioning 5.6
Window {
visible: true
width: 640
height: 480
Plugin {
id: osmMapPlugin
name: "osm"
}
Map {
anchors.fill: parent
plugin: osmMapPlugin
center: QtPositioning.coordinate(56.006355, 92.860984)
zoomLevel: 10
MapPolyline {
id: pl
line.width: 10
line.color: 'red'
}
}
function loadPath(){
var lines = []
for(var i=0; i < pathController.geopath.size(); i++){
lines[i] = pathController.geopath.coordinateAt(i)
}
return lines;
}
Connections{
target: pathController
onGeopathChanged: pl.path = loadPath()
}
Component.onCompleted: pl.path = loadPath()
}

C++ function is being called before page loads even with Component.onCompleted

Here is the issue, I created a C++ class which loads a local file and for every new line it sends out a signal. What I want to do is in my QML file I want to print out these lines into a listview which I have already done but the issue now is that the C++ function loads even before the application starts, it would read the file and populate the listview and then display the page.
Here is my code.
main.cpp
#include <QtCore/QCoreApplication>
#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickView>
#include <QtQml>
#include "boot.h"
int main(int argc, char *argv[])
{
QCoreApplication::setApplicationName("xyz");
QCoreApplication::setAttribute(Qt::AA_X11InitThreads);
qmlRegisterType<Boot>("xyx", 1, 0, "Boot");
QGuiApplication app(argc, argv);
QQuickView quickView;
quickView.setSource(QUrl(QStringLiteral("qrc:/Boot.qml")));
quickView.setResizeMode(QQuickView::SizeRootObjectToView);
quickView.showMaximized();
return app.exec();
}
Boot.qml
import QtQuick 2.0
import "."
import QtQuick.XmlListModel 2.0
import xyz 1.0
Item{
Loader{
id: bootPageLoader
anchors.fill:parent
sourceComponent: bootSystem
focus:true
}
Component{
id:bootSystem
Rectangle{
width: 640
height: 480
color:"black"
focus:true
Component.onCompleted: {
booting.load();
}
Boot{
id:booting
onErrorMsgChanged: {
console.log("New Boot Log Message: " + errorMsg);
//This is where I am adding to the listview every time I receive a signal
logModel.append({msg:errorMsg});
log.positionViewAtEnd();
}
}
ListView {
id:log
anchors.left: parent.left
anchors.leftMargin: 10
anchors.topMargin: 10
anchors.bottomMargin:10
anchors.top: parent.top
width:(parent.width*40)/100
height:parent.height-20
model: BootLogModel{
id:logModel
}
delegate: Text {
text: msg
color: "green"
font.pixelSize: 15
}
}
Text{
id: loading
anchors{
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
text: "LOADING..."
color: "white"
font.pixelSize: Math.round(parent.height/20)
font.bold: true
}
}
}
}
BootLogModel.qml
import QtQuick 2.0
ListModel {
}
Here is the C++ code snippet
In boot.h
#ifndef BOOT_H
#define BOOT_H
#include <QObject>
#include <QString>
#include <string>
class Boot : public QObject{
Q_OBJECT
Q_PROPERTY(QString errorMsg READ errorMsg NOTIFY errorMsgChanged)
public:
explicit Boot(QObject *parent = 0);
Q_INVOKABLE void load();
QString errorMsg();
void setErrorMsg(const QString &errorMsg);
signals:
void errorMsgChanged();
private:
QString m_errorMsg;
};
#endif // BOOT_H
In boot.cpp
Boot::Boot(QObject *parent) : QObject(parent)
{
}
QString Boot::errorMsg()
{
return m_errorMsg;
}
void Boot::setErrorMsg(const QString &errorMsg)
{
m_errorMsg = errorMsg;
emit errorMsgChanged();
}
void Boot::load()
{
int i = 0;
while(i < 10000)
{
setErrorMsg("test: " + QString::number(i));
i++;
}
}
I first see this before the GUI
Then this is the GUI being displayed and already populated
Loops are always a problem in a GUI, it is always better to look for a friendlier alternative to the GUI, in this case a QTimer:
boot.h
int counter = 0;
bool.cpp
void Boot::load()
{
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, [timer, this](){
setErrorMsg("test: " + QString::number(counter));
counter++;
if(counter > 10000){
timer->stop();
timer->deleteLater();
}
});
timer->start(0); //delay
}

Copy QML Item to Clipboard as Image thru C++

I've been trying on with QML Charts API and decided to export the ChartView as image to the Clipboard. I found a working solution surfing on the net, in which one grabs the item as image thru Javascript and sends the QVariant data to C++. This is nice and works but I'm wondering if it wouldn't be possible to send just a QQuickItem* or something as light as that and do the grab and whatever at C++, as everybody says to avoid Javascript as much as possible and grabbing an image seems to be a heavy operation.
Here is the working code I'm using now.
chartexporter.h
#ifndef CHARTEXPORTER_H
#define CHARTEXPORTER_H
#include
#include
class QQuickItem;
class ChartExporter : public QObject
{
Q_OBJECT
public:
explicit ChartExporter(QObject *parent = 0);
Q_INVOKABLE void copyToClipboard(QVariant data);
};
#endif // CHARTEXPORTER_H
chartexporter.cpp
includes....
ChartExporter::ChartExporter(QObject *parent) : QObject(parent)
{
}
void ChartExporter::copyToClipboard(QVariant data){
QImage img = qvariant_cast(data);
QApplication::clipboard()->setImage(img,QClipboard::Clipboard);
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtCharts 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello Chart World")
ColumnLayout{
spacing: 2
anchors.fill: parent
ChartView{
id: chart
title: "Testing Charts"
anchors.fill: parent
legend.alignment: Qt.AlignTop
antialiasing: true
animationOptions: ChartView.AllAnimations
PieSeries {
id: pieSeries
PieSlice { label: "Volkswagen"; value: 13.5; exploded: true}
PieSlice { label: "Toyota"; value: 10.9 }
PieSlice { label: "Ford"; value: 8.6 }
PieSlice { label: "Skoda"; value: 8.2 }
PieSlice { label: "Volvo"; value: 6.8 }
}
}
Button{
Layout.alignment: Qt.AlignBottom
text: qsTr("Copy to Clipboard")
onClicked: {
var stat = chart.grabToImage(function(result) {
Printer.copyToClipboard(result.image);
});
}
}
}
}
main.cpp
includes ....
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
ChartExporter printer;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("Printer", &printer);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
Do you guys have an idea on how it could be done by simply calling a method to send the desired Item to C++ as proposed below and processing all stuff in there?
Button{
Layout.alignment: Qt.AlignBottom
text: qsTr("Copy to Clipboard")
onClicked: {
Printer.copyToClipboard(chart);
}
}
I kind of found a way to do it in C++!
When I do the Printer.copyToClipboard(chart) inside the onClicked handler (Javascript) I'm actually sending a QObject* to the C++ layer. Inside my C++ method I can do a qobject_cast and grab it as image. One must take into account that the grab operation is asyncronous. So, I needed to do the copy to clipboard inside an slot which gets called when the grab is finished.
So, below is the way I got. If anybody has a better approach it would be nice to know.
chartexporter.h
#ifndef CHARTEXPORTER_H
#define CHARTEXPORTER_H
#include &ltQObject>
#include &ltQVariant>
#include &ltQSharedPointer>
class QQuickItem;
class QQuickItemGrabResult;
class ChartExporter : public QObject
{
Q_OBJECT
private:
QSharedPointer p_grabbedImage;
protected slots:
void doCopy();
public:
explicit ChartExporter(QObject *parent = 0);
Q_INVOKABLE void copyToClipboard(QObject *item);
};
#endif // CHARTEXPORTER_H
chartexporter.cpp
#include &ltQDebug>
#include &ltQImage>
#include &ltQQuickItem>
#include &ltQClipboard>
#include &ltQApplication>
#include &ltQSharedPointer>
#include &ltQQuickItemGrabResult>
#include "chartexporter.h"
ChartExporter::ChartExporter(QObject *parent) : QObject(parent), p_grabbedImage(nullptr)
{
}
void ChartExporter::copyToClipboard(QObject* item){
if(item){
auto itm = qobject_cast(item);
p_grabbedImage = itm->grabToImage(QSize(itm->width()*2,itm->height()*2));
connect(p_grabbedImage.data(), &QQuickItemGrabResult::ready, this, &ChartExporter::doCopy);
}
}
void ChartExporter::doCopy(){
if(p_grabbedImage.data()){
auto img = p_grabbedImage->image();
QApplication::clipboard()->setImage(img, QClipboard::Clipboard);
}
disconnect(p_grabbedImage.data(),&QQuickItemGrabResult::ready, this, &ChartExporter::doCopy);
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtCharts 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello Chart World")
ColumnLayout{
spacing: 2
anchors.fill: parent
ChartView{
id: chart
title: "Testing Charts"
anchors.fill: parent
legend.alignment: Qt.AlignTop
antialiasing: true
animationOptions: ChartView.AllAnimations
PieSeries {
id: pieSeries
PieSlice { label: "Volkswagen"; value: 13.5; exploded: true}
PieSlice { label: "Toyota"; value: 10.9 }
PieSlice { label: "Ford"; value: 8.6 }
PieSlice { label: "Skoda"; value: 8.2 }
PieSlice { label: "Volvo"; value: 6.8 }
}
}
Button{
Layout.alignment: Qt.AlignBottom
text: qsTr("Copy to Clipboard")
onClicked: {
Printer.copyToClipboard(chart);
}
}
}
}
main.cpp
#include &ltQApplication>
#include &ltQQmlContext>
#include &ltQQmlApplicationEngine>
#include "chartexporter.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
ChartExporter printer;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("Printer", &printer);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}