C++ / QML architecture - a way to reproduce C++ structure in QML? - qt

I am currently trying to develop a quite important application (OS-like) with Qt 5.2 and Qt Quick 2 ; what I would like to do is to have all the logic implemented in C++, the UI being declared thanks to QML. At this point, it seems logical and the way to get around. However, I can’t figure how to do it the clean way.. I’ve read a lot of documentation, tutorials and examples but nothing so big…
Let’s explain a little what I would like to put as an architecture ; let’s say we have a Scene object, which could contains an undefined number of Application objects. What I would like is to define the logic in CPP (how I load the applications from XML, what the scene should have as properties, …) and then show the scene with QML. Also, we have to notice that Scene and Application elements should be re-used as component ; so, here is the basic idea : I’d like to define graphical styles that are common to each object with a file in QML (extending the CPP type).
For example, I could create a file with this content :
Application {
Rectangle { ... }
}
Saying that an application should be representated as a Rectangle ; then, when I create a Scene object that have a list of Application (or one unique Application, to begin with), I would like it to be displayed automatically (‘cause this is a property of Scene object). Is it even possible ? How can I do that ?
I thought that if I extend the C++ object and declare some graphical elements for it, it would be automatic.. But actually it doesn’t look like that !
Maybe there is another way around ?
Thanks

I don't like this question too much, as it's not really asking anything in particular. The Qt documentation is very comprehensive, so I often find it strange when people say they've read documentation, tutorials and examples, and still haven't found what they're looking for. However, I think I understand the gist of what you're asking, and think the answer could be useful to some, so I'll try to answer it.
main.cpp
#include <QtGui/QGuiApplication>
#include <QtQml>
#include <QQuickItem>
#include "qtquick2applicationviewer.h"
class ApplicationItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QString title MEMBER mTitle NOTIFY titleChanged)
public:
ApplicationItem(QQuickItem *parent = 0) : QQuickItem(parent) {
}
public slots:
void close() {
emit closed(this);
}
signals:
void titleChanged(QString title);
void closed(ApplicationItem *app);
private:
QString mTitle;
};
class SceneItem : public QQuickItem
{
Q_OBJECT
public:
SceneItem() {
}
public slots:
void startApp(const QString &qmlFile) {
QQmlComponent *component = new QQmlComponent(qmlEngine(this), QUrl(qmlFile));
if (component->isLoading()) {
QObject::connect(component, SIGNAL(statusChanged(QQmlComponent::Status)),
this, SLOT(componentStatusChanged()));
} else {
// The component was synchronously loaded, but it may have errors.
if (component->isError()) {
qWarning() << "Failed to start application:" << component->errorString();
} else {
addApp(component);
}
}
}
void componentStatusChanged(QQmlComponent::Status status) {
QQmlComponent *component = qobject_cast<QQmlComponent*>(sender());
if (status == QQmlComponent::Ready) {
addApp(component);
} else if (status == QQmlComponent::Error) {
qWarning() << "Failed to start application:" << component->errorString();
}
}
void appClosed(ApplicationItem *app) {
int appIndex = mApplications.indexOf(app);
if (appIndex != -1) {
mApplications.removeAt(appIndex);
app->deleteLater();
}
}
private:
void addApp(QQmlComponent *component) {
ApplicationItem *appItem = qobject_cast<ApplicationItem*>(component->create());
appItem->setParentItem(this);
connect(appItem, SIGNAL(closed(ApplicationItem*)), this, SLOT(appClosed(ApplicationItem*)));
mApplications.append(appItem);
delete component;
}
QList<ApplicationItem*> mApplications;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer viewer;
qmlRegisterType<ApplicationItem>("Test", 1, 0, "ApplicationItem");
qmlRegisterType<SceneItem>("Test", 1, 0, "SceneItem");
viewer.setMainQmlFile(QStringLiteral("qml/quick/main.qml"));
viewer.showExpanded();
return app.exec();
}
#include "main.moc"
I represented both classes as QQuickItem subclasses. SceneItem is composed of many ApplicationItem instances, which are added to the scene by invoking startApp(). This slot takes a path to a QML file as its argument. This file could be loaded over a network, or from a local file, hence we account for the possibility of both synchronous and asynchronous loading.
The QML file should describe the visual appearance of an application, and the scene expects its root type to be ApplicationItem. As an example, here's MySweetApp.qml:
import QtQuick 2.0
import QtQuick.Controls 1.0
import Test 1.0
ApplicationItem {
id: someAppStyle
title: "My Sweet App"
width: 100
height: 100
MouseArea {
anchors.fill: parent
drag.target: parent
}
Rectangle {
radius: 4
color: "lightblue"
anchors.fill: parent
Text {
anchors.left: parent.left
anchors.right: closeButton.right
anchors.leftMargin: 4
anchors.top: parent.top
anchors.topMargin: 4
text: someAppStyle.title
}
Button {
id: closeButton
anchors.right: parent.right
anchors.rightMargin: 4
anchors.top: parent.top
anchors.topMargin: 2
onClicked: close()
text: "x"
width: 20
height: width
}
}
}
Applications can close themselves by invoking the close() slot declared in ApplicationItem.
Here's main.qml:
import QtQuick 2.0
import QtQuick.Controls 1.0
import Test 1.0
SceneItem {
id: scene
width: 360
height: 360
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
text: "Launch app"
onClicked: scene.startApp("qml/quick/MySweetApp.qml")
}
}
This is where the SceneItem is declared, along with a simple interface to launch several instances of My Sweet App (it's a very useful app).
I believe this is the most appropriate way to do what you're asking. It avoids the hassle of setting up lists of ApplicationItems in C++ which are exposed to QML (it's actually not that hard, but this is one area where the documentation could be more obvious), and allows the users of your OS freedom in how applications appear. If you want to be more strict in what can be styled, I'd suggest looking into how Qt Quick Controls does styling.

I would advise against using C++ for the logic unless you really need to - use casese to use C++ for the logic are if you have high-performance requirements like realtime data that needs to processed like 10x per second.
As most of the use cases do not have this requirement, it is better to use QML also for application logic because it will save up to 90% source code (and time) compared with C++. Especially in the beginning of development, you are way faster to code the logic in QML and get results faster. You can later on still move the logic to C++ if needed.
There are 2 good guides about this topic available that explain this in more detail and come with source code examples:
QML Architecture Tips and why/how to avoid C++ in your Qt app
QML Architecture Best Practices and Examples

Related

Can I use Qt's declarative state machine framework in non-graphical applications?

I really like Qt's Declarative State Machine Framework (DSMF), but the way I'd like to use it is (possibly) a bit strange. I've been using Qt quite a lot in non-graphical applications on small(ish) embedded devices for the past few years, and I've recently been learning about the DSMF, which has a syntax that I really like.
The 'declarative' qualifier in the framework's name seems to mean "declared in a QML file", like so:
StateMachine {
id: stateMachine
initialState: s1
running: true
State {
id: s1
SignalTransition {
targetState: s2
signal: button.clicked
}
// do something when the state enters/exits
onEntered: console.log("s1 entered")
onExited: console.log("s1 exited")
}
State {
// create a transition from s2 to s3 when the button is clicked
SignalTransition {
targetState: s1
signal: button.clicked
}
onEntered: console.log("s2 entered")
onExited: console.log("s2 exited")
}
}
If I've understood the framework correctly, it appears I can define the state logic entirely within a (dynamically-loaded) QML file, and achieve any side effects on the world that I need to by dinking properties of C++ objects exposed to QML via, e.g., QQmlContext::setContextProperty(). If I'm right about this, I should be able to change the state machine logic by simply modifying the QML file, without any need to recompile the app.
The difficulty I'm having is that I've only recently begun learning QML, and most (all?) of the examples I've found thus far are for graphical applications. I initially thought that QML was only intended for use in graphical applications, but this SO answer pointed me toward QBS, which looks to be a console application that uses QML.
I'm now trying to pick apart QBS to see if it provides any clues as to how I might go about using the Declarative State Machine Framework in a console application. But it'll take me awhile to figure out, and I worry there may be gotchas along the way that make achieving my goal impossible.
Can anybody confirm whether it is even possible to use the DSMF in a Qt console application? (Extra weight will be given to answers that point to—or include—specific examples...)
Since there have been no responses thus far, I'll submit my own answer to the question of whether it's possible to use the DSMF in non-graphical applications. It is! I'll present the small example program I wrote to satisfy my curiosity. First, here's a C++ class meant to simulate a 'domain object' that knows how to poke the world to achieve some desired side effect—in this case, connecting to a network:
// File: NetworkManager.h
#ifndef NETWORK_MANAGER_H
#define NETWORK_MANAGER_H
#include <QObject>
#include <QTimer>
#include <iostream>
class NetworkManager : public QObject
{
Q_OBJECT
Q_PROPERTY(bool timedOut MEMBER timedOut_ READ getTimedOut)
public:
NetworkManager() : timedOut_(false) { }
~NetworkManager() { }
Q_INVOKABLE void connectAsync()
{
std::cout << "NetworkManager: In connectAsync(), connecting...\n";
// Simulate a successful connection within 1 to 5 seconds...
QTimer::singleShot(
(1 + qrand() % 5)*1000,
[this]() {
std::cout << "NetworkManager: Connected!\n";
emit this->connected();
// ... and a random disconnect 5 to 15 seconds later.
QTimer::singleShot(
(5 + qrand() % 11)*1000,
[this]() {
std::cout << "NetworkManager: Lost connection!\n";
emit this->disconnected();
}
);
}
);
}
bool getTimedOut() const { return timedOut_; }
signals:
void connected();
void disconnected();
private:
bool timedOut_;
};
#endif // NETWORK_MANAGER_H
And here's the QML file defining the state logic. Note how it delegates the 'real' work to the domain object:
// File: NetworkManager.qml
import QtQuick 2.0
import QtQml.StateMachine 1.0
StateMachine {
id: networkFsm
initialState: sConnecting
running: true
State {
id: sConnecting
SignalTransition {
targetState: sConnected
signal: network_.connected
guard: {
network_.timedOut == false;
}
}
onEntered: {
console.log("NetworkFsm: sConnecting entered");
network_.connectAsync();
}
onExited: {
console.log("NetworkFsm: sConnecting exited");
}
}
State {
id: sConnected
SignalTransition {
targetState: sConnecting
signal: network_.disconnected
}
onEntered: {
console.log("NetworkFsm: sConnected entered");
}
onExited: {
console.log("NetworkFsm: sConnected exited");
}
}
}
Finally, here's the main.cpp file that binds the two together, and a CMakeLists.txt file to build everything:
// File: main.cpp
#include <QCoreApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <iostream>
#include "NetworkManager.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
NetworkManager network;
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("network_", &network);
engine.load(QUrl("./NetworkManagerFsm.qml"));
return app.exec();
}
# File: CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(qmlfsm)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt5Core REQUIRED)
find_package(Qt5Qml REQUIRED)
add_executable(qmlfsm main.cpp NetworkManager.h)
target_link_libraries(qmlfsm Qt5::Core Qt5::Qml)
Here's what it looks like when I run it:
qml: NetworkFsm: sConnecting entered
NetworkManager: In connectAsync(), connecting...
NetworkManager: Connected!
qml: NetworkFsm: sConnecting exited
qml: NetworkFsm: sConnected entered
NetworkManager: Lost connection!
qml: NetworkFsm: sConnected exited
qml: NetworkFsm: sConnecting entered
NetworkManager: In connectAsync(), connecting...
NetworkManager: Connected!
qml: NetworkFsm: sConnecting exited
qml: NetworkFsm: sConnected entered
^C
So, yes... it's possible. Whether or not it's useful remains to be seen. My motivation for asking the question in the first place was to see if I could make writing state machines in Qt more similar to my experience using SMC—a tool that has served me well for several years now. I love the succinctness of SMC's .sm file format, and the fact it isn't littered with <qt:editorinfo> tags and other XML bloat. The DSMF seems to hit most of those same sweet spots. Time will tell, but... I think I'll prefer it to SCXML—at least, for less complex FSMs.
NOTE
Please understand that the FSM posted above is just a 'toy' example cobbled together to demonstrate that the framework can be used in non-graphical apps. In particular, note that the timedOut property of the NetworkManager class is only present to highlight the fact that properties of QObject classes exported to QML can be used in guard conditions. (In a real implementation, timedOut would make more sense as a separate SignalTransition.)
Parameters passed to the signal that causes the transition can also be used in guard conditions, but I personally feel that the FSM 'reads better' when properties/methods of the domain object are accessed explicitly. Otherwise, one wonders where, e.g., the timedOut parameter has come from, and must open the header file for the domain object's class in order to be certain.

How can I reset a timer every time I receive a touch event from a qml page

import QtQuick 2.6;
import QtQuick.Controls 2.1 ;
import QtQuick.Layouts 1.3 ;
Page{
id: page
width: 800
height: 1024
background: Rectangle {
color: "black" ;
anchors.fill:parent ;
}
Rectangle {
id:rect1
x: 0
y:10
width: 100
height: 100
color : "red"
MouseArea {
anchors.fill: parent
onClicked: tmr.restart()
}
}
Rectangle {
id:rect2
x: 0
y:110
width: 100
height: 100
color : "blue"
MouseArea {
anchors.fill: parent
onClicked: tmr.restart()
}
}
Timer {
id : tmr
interval : 30000
repeat : true
running: true
onTriggered: {
console.log ("hello world ")
}
}
}
I develop a software for embedded imx6 freescale device using qt framework.
Basically I just want to restart my timer every time I click and every time I get a touch event on my screen whether the click/touch happen inside the mouse area of my rectangles or outside of them.
The idea is similar to a screensaver.
There are multiple ways, and the right way depends on your requirements.
If you don't need to guarantee that the timer triggers during a input you can just layer a MouseArea on top of everything. In this MouseArea you handle the pressed-signals, but dont accept them.
This allows you to handle the mouse input in the lower layers later. However you only realize whenever a new press happens, and the Timer might trigger e.g. during a half-an-hour finger-move input.
The second way is to have all MouseAreas report uppon their handled signals, that the signal happend, to reset the Timer. For all unhandled signals, you layer a MouseArea beneath everything else, handle all signals there to catch what has been falling through.
Resorting to C++ you might create a Item at the root of your Item-tree, and override the childMouseEventFitler
See my answer here for more on this.
In this case you should add a MouseArea right inside this Item, so it has something to filter at any place.
Note! This method will be triggered for each MouseArea that might be under your click. But in your scenario, this would be fine, I guess.
Thanks to GrecKo I looked into the general eventFilter again, and indeed it is really easy.
you create a simple QObject following the singleton pattern, in which you reimplement the eventFilter-method, so that it will emit a signal
mouseeventspy.h
#pragma once
#include <QObject>
#include <QtQml>
#include <QQmlEngine>
#include <QJSEngine>
class MouseEventSpy : public QObject
{
Q_OBJECT
public:
explicit MouseEventSpy(QObject *parent = 0);
static MouseEventSpy* instance();
static QObject* singletonProvider(QQmlEngine* engine, QJSEngine* script);
protected:
bool eventFilter(QObject* watched, QEvent* event);
signals:
void mouseEventDetected(/*Pass meaningfull information to QML?*/);
};
mouseeventspy.cpp
#include "mouseeventspy.h"
#include <QQmlEngine>
#include <QJSEngine>
#include <QEvent>
MouseEventSpy::MouseEventSpy(QObject *parent) : QObject(parent)
{
qDebug() << "created Instance";
}
// This implements the SINGLETON PATTERN (*usually evil*)
// so you can get the instance in C++
MouseEventSpy* MouseEventSpy::instance()
{
static MouseEventSpy* inst;
if (inst == nullptr)
{
// If no instance has been created yet, creat a new and install it as event filter.
// Uppon first use of the instance, it will automatically
// install itself in the QGuiApplication
inst = new MouseEventSpy();
QGuiApplication* app = qGuiApp;
app->installEventFilter(inst);
}
return inst;
}
// This is the method to fullfill the signature required by
// qmlRegisterSingletonType.
QObject* MouseEventSpy::singletonProvider(QQmlEngine *, QJSEngine *)
{
return MouseEventSpy::instance();
}
// This is the method is necessary for 'installEventFilter'
bool MouseEventSpy::eventFilter(QObject* watched, QEvent* event)
{
QEvent::Type t = event->type();
if ((t == QEvent::MouseButtonDblClick
|| t == QEvent::MouseButtonPress
|| t == QEvent::MouseButtonRelease
|| t == QEvent::MouseMove)
&& event->spontaneous() // Take only mouse events from outside of Qt
)
emit mouseEventDetected();
return QObject::eventFilter(watched, event);
}
Than you register it as singleton type to QML like this:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "mouseeventspy.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterSingletonType<MouseEventSpy>("MouseEventSpy", 1, 0, "MouseEventSpy", MouseEventSpy::singletonProvider);
// We do this now uppon creation of the first instance.
// app.installEventFilter(MouseEventSpy::instance());
engine.load(QUrl(QStringLiteral("main.qml")));
return app.exec();
}
Now in QML you can import the instance of the singleton in the necessary files and use the signal, e.g. to reset a Timer
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import MouseEventSpy 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Connections {
target: MouseEventSpy
onMouseEventDetected: myTimer.restart()
}
Timer {
id: myTimer
interval: 1000
onTriggered: console.log('It has been 1 seconds since the last mouse event')
}
Text {
anchors.center: parent
text: myTimer.running ? 'Timer is Running\nMove the mouse to reset'
: 'Move the Mouse to make the timer run again.'
}
}

Avoid QQuickView delayed qml loading

Here's my situation. I'm trying to combine qml with a mostly widget based UI. To do this, I'm using QQuickView with QWidget::createWindowContainer. I can't use QQuickWidget, because I need to convert the window into a native window, and QQuickWidget doesn't like that. But back to the issue.
My problem is that the first time the view is displayed, it takes like half a second to load causing a very obvious flicker. After that I can hide/show the view all I want, it displays immediately. It's only the first time the qml loads. And I'm fairly certain it's the loading of the qml that causes the issue. Because I have two different QQuickViews that get the same qml as their source. But after any one of them loads once, the other has no issues displaying instantly.
I tried to call show() on view early to get it to load in time. But this causes the qml to appear for a brief moment before any of the widgets get displayed.
Has anyone encountered a similar issue? How can I get the QQuickView to behave.
Edit: I'm using Qt 5.4.2, and I can't update to a newer version due to various reasons.
I was going to say that you can use the same approach as in this answer, but it seems that even that is too early to being loading the QML. It's hacky, but the only other thing I can think of is using a very short Timer:
main.cpp:
#include <QtWidgets>
#include <QtQuick>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0) :
QMainWindow(parent)
{
QQuickView *view = new QQuickView();
QWidget *container = QWidget::createWindowContainer(view, this);
container->setFocusPolicy(Qt::TabFocus);
view->rootContext()->setContextProperty("window", view);
view->setSource(QUrl("qrc:/main.qml"));
setCentralWidget(container);
resize(400, 400);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 2.0
Item {
anchors.fill: parent
// Connections {
// target: window
// onAfterSynchronizing: loader.active = true
// }
Timer {
running: true
repeat: true
interval: 50
onTriggered: {
loader.active = true
}
}
Loader {
id: loader
active: false
sourceComponent: Column {
anchors.fill: parent
Repeater {
model: 30000
delegate: Button {
text: index
}
}
}
}
BusyIndicator {
running: loader.status === Loader.Null
anchors.centerIn: parent
}
}
For brevity, I chucked the "heavy" QML into sourceComponent, but you can also use the source property to point to a URL.
BusyIndicator runs its animation on the render thread, so it can continue to spin while the GUI thread is blocked.

How to control caching of HTTP resources in QML?

Consider the following piece of code, which fetches an RSS feed and then displays the images within as a slideshow which loops forever:
import QtQuick 2.2
import QtQuick.XmlListModel 2.0
Rectangle {
id: window
color: "black"
width: 800
height: 480
PathView {
anchors.fill: parent
highlightRangeMode: PathView.StrictlyEnforceRange
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
highlightMoveDuration: 500
snapMode: PathView.SnapOneItem
pathItemCount: 3 // only show previous, current, next
path: Path { // horizontal
startX: -width; startY: height/2
PathLine{x: width*2; y: height/2}
}
model: XmlListModel {
source: "http://feeds.bbci.co.uk/news/business/rss.xml"
query: "/rss/channel/item"
namespaceDeclarations: "declare namespace media = 'http://search.yahoo.com/mrss/';"
XmlRole { name: "image"; query: "media:thumbnail/#url/string()" }
}
delegate: Image {
width: PathView.view.width
height: PathView.view.height
source: image
fillMode: Image.PreserveAspectCrop
}
Timer { // automatically loop through the images
interval: 1000; running: true; repeat: true;
onTriggered: {
parent.incrementCurrentIndex()
}
}
Timer {
interval: 600000; running: true; repeat: true;
onTriggered: parent.model.reload()
}
}
}
This code loads the images from the web as it needs them. However, once an image is no longer displayed it discards the data. The next time the slideshow loops around the image will be reloaded from the web. As a result, the code hits the remote image server once per second for as long as it is running, downloading 50-300KB each time.
The code runs on an embedded system with not much RAM, so caching the decoded image data by keeping the delegate when it is not on the path is not an option.
Instead the caching should be done at the HTTP level, storing the original downloaded files. It should therefore obey the HTTP cache control headers.
The caching should be done in memory only as the system has only a small flash disk.
How can I implement this in Qt? I assume it will involve C++ code, that is fine.
To control the caching behaviour when QML fetches a network resource you would subclass QQmlNetworkAccessManagerFactory and have it create QNetworkAccessManagers with a cache attached. Then you attach the factory to your QQmlEngine:
class MyNAMFactory : public QQmlNetworkAccessManagerFactory
{
public:
virtual QNetworkAccessManager *create(QObject *parent);
};
QNetworkAccessManager *MyNAMFactory::create(QObject *parent)
{
QNetworkAccessManager *nam = new QNetworkAccessManager(parent);
nam->setCache(new QNetworkDiskCache(parent));
return nam;
}
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
QQuickView view;
view.engine()->setNetworkAccessManagerFactory(new MyNAMFactory);
view.setSource(QUrl("qrc:///main.qml"));
view.show();
return app.exec();
}
Caches must implement the QAbstractNetworkCache interface. Qt has one built in cache type, QNetworkDiskCache which, as the name implies, saves the cache to disk. There is no built-in class for in-memory caching, but it would be fairly easy to implement one by using a QHash to store the URLs, data, and metadata.

Qt5.6 QML, why are dynamic models destroyed after garbage collection?

I have a variable number of components, so i'm trying to give each one its own model. In this example, i just create one, but the idea is the same.
GC() is a bit random, so in the example, i force the gc() after a click to flush out the problem. What happens is that the model is destroyed and becomes null. after that the click method cannot use it.
main.qml:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.2
import com.example.qml 1.0
ApplicationWindow
{
visible: true
width: 640
height: 480
// builder of dynamic models
ModelFactory { id: maker }
Column
{
anchors.fill: parent
Repeater
{
// create dynamic model
model: maker.makeModel();
delegate: Label
{
id: label
text: model.name
MouseArea
{
anchors.fill: parent
onClicked:
{
// works once until gc()
console.log("clicked on " + model.name)
// wont work anymore. model is destroyed
gc();
}
}
}
}
}
}
c++/mymodel.h:
#include <QAbstractListModel>
#include <QQmlApplicationEngine>
#include <QObject>
#include <QString>
#include <QDebug>
class BoxModel : public QAbstractListModel
{
Q_OBJECT
public:
~BoxModel()
{
// see that it does get destroyed
qDebug() << "~BoxModel()";
}
int rowCount(const QModelIndex& parent = QModelIndex()) const override
{
return 5;
}
QVariant data(const QModelIndex &index, int role) const override
{
int ix = index.row();
if (ix < 1) return "Larry";
if (ix < 2) return "Barry";
if (ix < 3) return "Gary";
if (ix < 4) return "Harry";
return "Sally";
}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> roles;
roles[Qt::UserRole+1] = "name";
return roles;
}
};
class ModelFactory: public QObject
{
Q_OBJECT
public:
Q_INVOKABLE BoxModel* makeModel()
{
return new BoxModel();
}
};
main.cpp just registers the types:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <qqmlcontext.h>
#include <qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include "mymodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<BoxModel>("com.example.qml", 1, 0, "BoxModel");
qmlRegisterType<ModelFactory>("com.example.qml", 1, 0, "ModelFactory");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
what you see:
Click on any of the names. it will work once and after that they will be undefined because model becomes null.
eg
qml: clicked on Sally
~BoxModel()
qml: clicked on undefined
My question is why is this, when i still have a reference to it?
In the example, onClicked could be changed to label.text rather than model.name to fix, but the real problem is that, in general, the model is accessed by the object at any time, for any data. For example, when the box needs to redraw. randomly the data is gone, depending on GC.
I've tried making c++ manage the life of the dynamic model. this could work if i know when exactly QML has finished with it.
thanks for info and ideas.
running on windows 8.1/qt5.6mingw
EDIT1: files as a gist,
https://gist.github.com/anonymous/86118b67ec804e6149423c14792f312d
As Kuba said, this does indeed seem like a bug. However, you can take another approach and take ownership of the models yourself via QQmlEngine::setObjectOwnership(). Specifically, changing
Q_INVOKABLE BoxModel* makeModel()
{
return new BoxModel();
}
to
Q_INVOKABLE BoxModel* makeModel()
{
BoxModel *model = new BoxModel(this);
QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership);
return model;
}
will fix this (remember to parent the returned model to BoxModel so it gets deleted appropriately). The reason for the behaviour is explained here:
Generally an application doesn't need to set an object's ownership explicitly. QML uses a heuristic to set the default ownership. By default, an object that is created by QML has JavaScriptOwnership. The exception to this are the root objects created by calling QQmlComponent::create() or QQmlComponent::beginCreate(), which have CppOwnership by default. The ownership of these root-level objects is considered to have been transferred to the C++ caller.
Objects not-created by QML have CppOwnership by default. The exception to this are objects returned from C++ method calls; their ownership will be set to JavaScriptOwnership. This applies only to explicit invocations of Q_INVOKABLE methods or slots, but not to property getter invocations.
I just had the same problem with a ComboBox.
As a workaround, you may create your own property to keep a strong reference to it:
Repeater {
property QtObject myModel: maker.makeModel();
model: myModel
// …
}
I know this is an old question, but I've just faced similar issue, and found your question in process of writing mine. See QObject gets destroyed after being put into QML variable for full story, and I'll cite it here.
What I've figured out that if I set the parent of that QObject before I pass it into QML, then it doesn't get deleted. So, I've concluded that passing unparented QObject into QML scope makes that scope become a parent of QObject and call its destructor after scope ends.

Resources