QML object from C++ class not being destructed? - c++

I've encountered an unexpected behavior (for my point of view) of objects from C++ classes created in QML part of application. An example to be clear:
The starting point is empty QML application.
After that TestEntity class is added:
#ifndef TESTENTITY_H
#define TESTENTITY_H
#include <Qt3DCore>
#include <Qt3DExtras>
class TestEntity : public Qt3DCore::QEntity
{
Q_OBJECT
public:
TestEntity(QNode *parent = nullptr);
~TestEntity();
};
#endif // TESTENTITY_H
#include <QDebug>
#include "testentity.h"
TestEntity::TestEntity(QNode *parent) : Qt3DCore::QEntity(parent)
{
Qt3DExtras::QCuboidMesh *mesh = new Qt3DExtras::QCuboidMesh();
Qt3DCore::QTransform *transform = new Qt3DCore::QTransform();
mesh->setXExtent(12);
mesh->setYExtent(6);
mesh->setZExtent(6);
Qt3DExtras::QDiffuseSpecularMaterial *material = new Qt3DExtras::QDiffuseSpecularMaterial();
material->setAmbient(QColor(Qt::black));
material->setSpecular(QColor(Qt::white));
material->setShininess(4);
this->addComponent(material);
this->addComponent(mesh);
this->addComponent(transform);
}
TestEntity::~TestEntity()
{
qDebug() << "TestEntity destroyed";
}
TestEntity is registered in main.cpp:
qmlRegisterType<TestEntity>("testEntity", 1, 0, "TestEntity");
main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import Qt3D.Core 2.15
import QtQuick.Scene3D 2.15
import Qt3D.Render 2.15
import Qt3D.Extras 2.15
import QtQuick3D.Materials 1.15
import testEntity 1.0
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Scene3D {
anchors.fill: parent
Entity {
id: rootEntity
RenderSettings {
activeFrameGraph: ForwardRenderer {
camera: camera
clearColor: "transparent"
}
}
Entity {
components: [
PointLight {
color: "#ffffff"
intensity: 0.5
enabled: true
},
Transform {
translation: Qt.vector3d(15, 0, 15)
}
]
}
Camera {
id: camera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
nearPlane : 0.1
farPlane : 150
aspectRatio: 1
position: Qt.vector3d(0.0, -20.0, 20.0)
upVector: Qt.vector3d(0.0, 0.0, 1.0)
viewCenter: Qt.vector3d(0.0, 0.0, 0.0)
}
TestEntity {
id: testEntity
}
}
}
}
Everything works as expected except the destruction of TestEntity object - destructor is not called for some reason...
So the question is what is the proper way to add testEntity in QML, so that it'll be rendered and destroyed without any problems?
Thank in advance!

Related

Control OSM map in QML qt

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)
}
}
}

Cannot use C++ QQuickPaintedItem Singleton in QML

I have to create a C++ singleton class, but it doesn't work in qml.
#include "myimage.h"
MyImage::MyImage(QQuickPaintedItem *parent)
{
}
MyImage* MyImage::myImage = new MyImage;
MyImage *MyImage::instance()
{
return myImage;
}
void MyImage::paint(QPainter *painter)
{
QRectF target(10.0, 20.0, 80.0, 60.0);
QRectF source(0.0, 0.0, 70.0, 40.0);
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::SmoothPixmapTransform, true );
painter->drawImage(target, this->m_Image, source);
}
const QImage &MyImage::getM_Image() const
{
return m_Image;
}
void MyImage::setM_Image(const QImage &mimage)
{
if (mimage != m_Image) {
m_Image = mimage;
emit m_ImageChanged();
}
}//This is my singleton class.
Then I register it in main.cpp.
QObject *getMySingletonImage(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return MyImage::instance();
}
...
qmlRegisterSingletonType<MyImage>("s0.image", 1, 0, "MyImage", &getMySingletonImage);
In QML:
import s0.image 1.0
MyImage{
}
I cannot run the program successfully.
qrc:/Camview_Page.qml:371:13: Element is not creatable.
Actually, I use the singleton class both in my backend and qml.
In my backend, I will get QImage type images but not be saved local, so I cannot use QUrl and I only figure out this method.
Expectation:
My backend pass images of QImage type to the singleton class, my singleton class realize the method of paint, my qml show the image.
BTW...my backend gets images from camera.
A singleton is already created for QML so you get that error message since by using "{}" you are trying to create the object.
In this case it is enough to set a parent and the size:
import QtQuick 2.15
import QtQuick.Window 2.15
import s0.image 1.0
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Component.onCompleted: {
MyImage.parent = root.contentItem
MyImage.width = 100
MyImage.height = 100
}
}
If you want to create a binding for example set anchors.fill: parent then you can use the Binding component, if you want to hear any signal then you must use Connections component.
import QtQuick 2.15
import QtQuick.Window 2.15
import s0.image 1.0
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Binding{
target: MyImage
property: "parent"
value: root.contentItem
}
Binding {
target: MyImage
property: "anchors.fill"
value: MyImage.parent
}
Connections{
target: MyImage
function onWidthChanged(){
console.log("width:", MyImage.width)
}
function onHeightChanged(){
console.log("height:", MyImage.height)
}
}
}

Access to existing QML component from C++

I am a newbie in QML, and cannot resolve a simple issue. I want to get access to the QML components from C++, but I cannot.
The pointer test is always 0. What can be the reason?
The code is the following:
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication &app=reg6::Bonder::BonderGuiApplication::instance();
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QObject* test=engine.rootObjects().first()->findChild<QObject*> ("cameraArea");
test->setProperty("color","black");
return app.exec();
}
main.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 1800
height: 900
SplitView
{
anchors.fill: parent
orientation: Qt.Vertical
SplitView {
Layout.fillHeight: true
SplitView {
orientation: Qt.Vertical
width:400
Layout.minimumWidth: 400
Layout.maximumWidth: 500
Camera {
id: cameraArea
height: 400
Layout.maximumHeight: 400
Layout.minimumHeight: 300
}
List {
id: listArea
}
}
Bonder {
id: mainArea
Layout.fillWidth: true
}
Properties {
id: propertiesArea
Layout.minimumWidth: 300
Layout.maximumWidth: 400
}
}
Error {
id: errorArea
Layout.minimumHeight: 100
height: 200
}
}
}
Camera.qml
import QtQuick 2.5
Rectangle {
color: "lightblue"
}
You have to set the objectName property also of the QML component to get a valid pointer to your QObject because T QObject::findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const needs the objectName not the ID

Accessing qml objects from loaded qml using cpp code

I have a main.qml which loads Page1.qml using loaders. How can I find object 'whiteArea' within Page1.qml from my cpp code?
I am currently using the following to fetch an object and would like to obtain the loaded qml as well like this as well.
QObject * object = engine.rootObjects().at(0)->findChild<QObject *> ("yourObjectName");
main.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
import myplugin 1.0
ApplicationWindow {
id:app
visible: true
width: 640
height: 480
title: qsTr(" World")
objectName: "Appwindow"
property ApplicationWindow appwindow:app
Label {
objectName: "label"
text: qsTr(" World")
anchors.centerIn: parent
}
MyItemTest{
objectName: "myItem"
anchors.fill: parent
}
Rectangle{
objectName: "Rectangle"
id:rect
width: 50
height: 50
color: "yellow"
}
Button {
objectName: "MyButton"
id: btnClick
text : "btn"
Loader { id: pageLoader }
onClicked: {
pageLoader.source = "Page1.qml"
}
}
}
Page1.qml
import QtQuick 2.0
import QtQuick 2.3
import QtQuick.Controls 1.2
import myplugin 1.0
Item {
Rectangle{
objectName: "whiteArea"
id:rect
width: 50
height: 50
color: "white"
}
}
From the Qt documentation:
The loaded object can be accessed using the item property.
So you should do some subsearch inside a loaded item, for example like this:
QObject * loader = engine.rootObjects().at(0)->findChild<QObject*>("loader");
qWarning() << loader;
QObject * item = qvariant_cast<QObject*>(QQmlProperty::read(loader,"item"));
qWarning() << item;
QObject *whiteArea = item->findChild<QObject *>("whiteArea");
qWarning() << whiteArea;
The output:
QQuickLoader(0x24918240, name = "loader")
QQuickItem(0x24919740)
QQuickRectangle(0x24919728, name = "whiteArea")
First of all , give Loader an object name property, like "loader".
then be sure at the time you running the below code , loader.item is set with"Page1.qml" then do something like this:
QObject* loader = m_engine->rootObjects()[0]->findChild<QObject*>("loader");
QObject* page= qvariant_cast<QObject *>(loader->property("item"));
QObject* whiteArea = page->findChild<QObject*>("whiteArea");

QtQuick: QQmlApplicationEngine failed to load component qrc:/main.qml:23 Invalid attached object assignment

Given my thoughts below am I barking up the wrong tree? Or provided the information below am I misusing Qt API to get the error in the title?
I am trying to modify the sample at http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html to work with the default QtQuick project generated with Qt Creator 3.3.0 (opensource)
Based on Qt 5.4.0 (GCC 4.6.1, 64 bit).
After looking through the code the first thing that stands out to me is:
The samples main.cpp uses:
qmlRegisterType<Squircle>("OpenGLUnderQML", 1, 0, "Squircle");
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:///scenegraph/openglunderqml/main.qml"));
view.show();
With some renaming my main.cpp uses
qmlRegisterType<MainScreen>("OpenGLUnderQML", 1, 0, "MainScreen");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
I am not sure if the difference in using a QQmlApplicationEngine over a QuickView could be causing my error:
QQmlApplicationEngine failed to load component qrc:/main.qml:23
Invalid attached object assignment
Where my main.qml looks like:
import QtQuick 2.4
import QtQuick.Window 2.2
import OpenGLUnderQML 1.0
import "qmlmodel"
Window {
id: mainWindow
width: 800
height: 600
visible: true
color: "black"
title: "Army Calculator"
objectName: "mainWindow"
ListView {
id: mainListView
anchors.fill: parent
objectName: "mainListView"
}
MainScreen {
SequentialAnimation on DeltaT {
NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
loops: Animation.Infinite
running: true
}
}
}
and the sample uses:
import QtQuick 2.0
import OpenGLUnderQML 1.0
Item {
width: 320
height: 480
Squircle {
SequentialAnimation on t {
NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
loops: Animation.Infinite
running: true
}
}
Rectangle {
color: Qt.rgba(1, 1, 1, 0.7)
radius: 10
border.width: 1
border.color: "white"
anchors.fill: label
anchors.margins: -10
}
Text {
id: label
color: "black"
wrapMode: Text.WordWrap
text: "The background here is a squircle rendered with raw OpenGL using the 'beforeRender()' signal in QQuickWindow. This text label and its border is rendered using QML"
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 20
}
}
As per request in comment below MainScreen.h is
#ifndef MAINSCREEN_H
#define MAINSCREEN_H
#include <QQuickItem>
class MainScreenRenderer;
class QQuickWindow;
class MainScreen : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(qreal DeltaT READ getDeltaT WRITE setDeltaT NOTIFY deltaTChanged)
public:
MainScreen();
~MainScreen();
qreal getDeltaT() const;
void setDeltaT(qreal deltaT);
signals:
void deltaTChanged();
public slots:
void sync();
void cleanup();
private slots:
void handleWindowChanged(QQuickWindow *win);
private:
qreal m_DeltaT;
MainScreenRenderer *m_Renderer;
};
#endif // MAINSCREEN_H
Property name should start with lowercase letter. You need to change DeltaT to deltaT.
MainScreen.h
Q_PROPERTY(qreal deltaT READ getDeltaT WRITE setDeltaT NOTIFY deltaTChanged)
main.qml
MainScreen {
SequentialAnimation on deltaT {
}
}