How Can I Use QVector inside QML/Javascript?
Example:
C++:
Custom class I use in QML. The class is including function that return QVector of registered ElementType
class CustomType : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE QVector<ElementType*> getElements();
//.....
}
//.........
qmlRegisterType<CustomType>("CustomComponents", 1, 0, "CustomType");
qmlRegisterType<ElementType>("CustomComponents", 1, 0, "ElementType");
qRegisterMetaType<ElementType*>("ElementType*");
QML:
QML code receives instance of CustomType class (custom) and try to get QVector<ElementType*> of elements and reads its properties. But QML can't recognize QVector type.
//.......
function(){
var elements = custom.getElements()
elements[0].property //?
}
QML-accessible list properties need to be constructed with QDeclarativeListProperty. This applies to everything that's list-shaped, also vectors. This part of the Qt documentation has the details.
I am answering this years late, But, here goes. This is a simple problem to resolve. Just use a Qt QVariantList type in your C++ code instead of QVector. Qt's QVariantList is essentially a QVector containing QVariant elements. And QVariants can be damn near anything once you know what you are doing. Also, QML automatically converts a C++ QVariant to its QML equivalent. Hence, if you do this in main.cpp:
#include <QCoreApplication>
#include <QGuiApplication>
#include <QQmlContext>
#include <QQuickView>
int main(int argc, char *argv[])
{
// Prepare to enter the QML GUI world
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
// Create a QVariantList to be used instead of a QVector
QVariantList dataList;
dataList << "Item 1";
dataList << 2;
dataList << 3.3;
// Create QML UI
QQuickView view;
// make your QVariantList visible to QML
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel",
QVariant::fromValue(dataList));
// show QML UI
view.setSource(QUrl("qrc:/main.qml"));
view.show();
return 1;
} // main()
You can then do this in your main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Item
{
// Print contents of C++ QVariantList to console
function printMeBruh()
{
console.log("modelData contains:")
console.log("-------------------")
for (var i = 0; i < 3; i++)
{
console.log(myModel[i])
}
}
Component.onCompleted: printMeBruh()
}
In your example you just need to return QList of QObject* from a Q_INVOKABLE function. Note, that all objects returned that way will have JavaScript ownership and will be garbage collected when your JavaScript function returns. To prevent this behavior use setObjectOwnership before pushing objects into your QList collection.
For more information about using collections in QML see QML Data Models
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've distilled this to trivial Qt code in which the only interesting thing is several lines in synchronize() handling a texture. I get a GL_INVALID_VALUE from the allocateStorage line. Anyone knows why? I suspect it's probably due to the parameters I pass to this function.
My code:
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickFramebufferObject>
#include <QOpenGLFramebufferObject>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
class MyItem : public QQuickFramebufferObject {
Q_OBJECT
public:
Renderer* createRenderer() const;
};
class MyItemRenderer : public QQuickFramebufferObject::Renderer, protected QOpenGLFunctions {
public:
MyItemRenderer() {
initializeOpenGLFunctions();
}
void render() {
}
QOpenGLFramebufferObject* createFramebufferObject(const QSize &size) {
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
return new QOpenGLFramebufferObject(size, format);
}
protected:
void synchronize(QQuickFramebufferObject* qqfbo) {
Q_UNUSED(qqfbo)
QOpenGLTexture tex(QOpenGLTexture::Target2D);
tex.setSize(100, 100);
tex.allocateStorage(QOpenGLTexture::BGRA, QOpenGLTexture::Int8);
qDebug() << "starting loop";
GLenum err;
while ((err = glGetError()) != GL_NO_ERROR) {
qDebug("\tgl error: 0x%x", err, 0, 16);
}
}
};
QQuickFramebufferObject::Renderer* MyItem::createRenderer() const {
return new MyItemRenderer();
}
int main(int argc, char **argv) {
QGuiApplication app(argc, argv);
qmlRegisterType<MyItem>("MyItem", 1, 0, "MyItem");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.0
import MyItem 1.0
import QtQuick.Window 2.2
Window {
visible: true
width: 400
height: 400
MyItem {
anchors.fill: parent
}
}
After looking at the source of QOpenGLTexture::createMutableTexture and QOpenGLTexture::allocateStorage() (no-argument version) I saw that two-argument allocateStorage overload is really, really misleading. The docs make it seem like its 2 args will be used as the internal format of the texture (3rd arg of the glTexImage2D call in the implementation), when in fact they're used only as the "source-data-to-transfer-from-system-RAM" format/type (7th and 8th args of glTexImage2D), which, in the case of passing a null pointer to glTexImage2D, don't matter at all except in fringe OpenGL ES 2 cases.
The way to actually request a certain internal format for the texture is to call tex.setFormat before calling tex.allocateStorage. So I replaced my allocateStorage line with this:
tex.setFormat(QOpenGLTexture::RGBA8_UNorm);
tex.allocateStorage();
And that fixed the error.
You have...
QOpenGLTexture tex(QOpenGLTexture::Target2D);
According to the docs that doesn't actually create the underlying texture object. I think you then need...
tex.create();
before setting the size and allocating storage. So...
QOpenGLTexture tex(QOpenGLTexture::Target2D);
if (!tex.create()) {
/*
* Take appropriate action.
*/
}
tex.setSize(100, 100);
tex.allocateStorage(QOpenGLTexture::BGRA, QOpenGLTexture::Int8);
Edit 1:
Actually, looking at the code it appears that tex.allocateStorage should allocate a texture id if necessary. However, if that fails for some reason (no current context for example) then that could be causing the problem you're seeing.
During "allocatestorage", You are conveying opeGL driver about the texture storage format.
For the formats "GL_BGRA or GL_RGBA", OpenGL recommends "GL_UNSIGNED_BYTE",
which is in Qt ----- "QOpenGLTexture::BGRA" (for GL_BGRA) and "QOpenGLTexture::UInt8" (for GL_UNSIGNED_BYTE).
https://www.khronos.org/opengl/wiki/Textures_-_more
So your allocate storage function based on (openGL recommendation) should be
tex.allocateStorage(QOpenGLTexture::BGRA, QOpenGLTexture::UInt8);
In short, I want to make an JSON-like object, which is easily accessible to both QML / Qt C++ side.
In QML, I can make a settings object like this:
Item {
id: settings
property alias snapshot: snapshot
QtObject {
id: snapshot
property string saveDirectory: "~/hello/world/"
}
}
And make it a singleton, now we can access data via settings.snapshot.saveDirectory .
But accessing it in C++ is very inconvenience, so I'm trying to do this task in C++: create a class Settings, establish instance as settings, then pass it into QQmlApplicationEngine as a context. (the whole application only has only one instance of Settings)
state.cpp
// =============================================================
/// Settings is parent
class Settings : public QObject
{
Q_OBJECT
Q_PROPERTY(SnapshotSettings* snapshot READ snapshot) // <- It's pointer, very important!
public:
explicit Settings(QObject *parent=0) : m_snapshot_settings(new SnapshotSettings) {}
private:
SnapshotSettings* m_snapshot_settings;
}
// =============================================================
// SnapshotSettings is the child of Settings
class SnapshotSettings : public QObject
{
Q_OBJECT
Q_PROPERTY(QString saveDirectory READ saveDirectory)
public:
explicit Settings(QObject *parent=0) : m_save_directory("~/hello/world/") {}
private:
QUrl m_save_directory;
}
main.cpp
I register SnapshotSettings* type. Otherwise, it said:
QMetaProperty::read: Unable to handle unregistered datatype 'SnapshotSettings' for property 'Settings::snapshot'
main.qml:52: TypeError: Cannot read property saveDirectory' of undefined
qmlRegisterType<>() seems unnecessary because I pass its instance into QML as a context, instead of instantialize it in QML?
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QQmlContext>
#include "state.cpp"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Settings settings;
// ....Am I right?
qRegisterMetaType<SnapshotSettings*>("SnapshotSettings");
// This seems unnecessary because I use its instance as a context?
// qmlRegisterType<Settings>("com.aisaka.taiga", 0, 1, "Settings");
engine.rootContext()->setContextProperty("settings", &settings);
engine.addImportPath(QStringLiteral(":/qml"));
engine.load(QUrl("qrc:/qml/main.qml"));
return app.exec();
}
One told me I made an anti-pattern of C++/QML…, saying "I never see anyone do like this".
Now it seems ok, I can use settings.snapshot.saveDirectory in QML. But in QtCreator, auto-completion work incorrectly.(saveDirectory won't show after settings.snapshot.)
My question is: Maybe I really did an anti-pattern? If yes, is there any better way to do this?
Thanks.
Your snapshot property needs to be of type SnapshotSettings*, i.e. a pointer to SnapshotSettings because that class is a QObject and cannot be copied.
You should also mark that property as CONSTANT, since the pointer never changes.
The saveDirectory property will either need a NOTIFY signal or also CONSTANT depending on whether it can change during runtime.
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.
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.