Is it possible to dynamically load QML from memory instead of using a file? I've seen a lot of code that always needs a URL, like this:
QGuiApplication app(argc, argv);
QQmlEngine* engine = new QQmlEngine;
QQmlComponent component(engine, QUrl(QStringLiteral("qrc:/main.qml")));
// ignoring error checking (make sure component is ready, compiles w/o errors, etc)
QObject* object = component.create();
app.exec();
In the example above, main.qml
will be loaded, and the magic of QML will ensure any types get resolved. In particular, Qt considers the name of the file to be the QML type, which is made available to any other QML file within the same directory. So if main.qml
used the type Simple
, it would look for the file Simple.qml
and use that as the type definition (http://doc.qt.io/qt-5/qtqml-documents-definetypes.html).
Now I'm going to get dangerous: I want to use in-memory QML instead of using a file. This is how it might work (please ignore bad design, memory leaks, etc. for the moment):
QByteArray SimpleQml()
{
return QByteArray("import QtQuick 2.7; Text {") +
"text: \"Hello, World!\"}";
}
QByteArray TextExampleQml()
{
return QByteArray("import QtQuick 2.7; Simple{") +
"color: \"Red\"}";
}
QObject* createObjectFromMemory(QByteArray qmlData, QQmlEngine* engine, QQmlComponent* componentOut)
{
componentOut = new QQmlComponent(engine);
// note this line: load data from memory (no URL specified)
componentOut->setData(qmlData, QUrl());
if (componentOut->status() != QQmlComponent::Status::Ready)
{
qDebug() << "Component status is not ready";
foreach(QQmlError err, componentOut->errors())
{
qDebug() << "Description: " << err.description();
qDebug() << err.toString();
}
qDebug() << componentOut->errorString();
return nullptr;
}
else
{
return component.create();
}
}
int main(int argc, char* argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
QQmlComponent* component = nullptr;
// works great: shows a small window with the text "Hello, World!"
QObject* object = createObjectFromMemory(SimpleQml(), view.engine(), component);
// problem: Simple is not a type
// QObject* object = createObjectFromMemory(TextExampleQml(), view.engine(), component);
// setContent() is marked as an internal Qt method (but it's public)
// see source for qmlscene for why I'm using it here
view.setContent(QUrl(), component, object);
view.show();
app.exec();
// don't forget to clean up (if you're not doing a demo snippet of code)
//delete object;
//delete component;
}
So SimpleQml()
simply is a Text object with "Hello, World!" as the text. When createObjectFromMemory()
is called with SimpleQml()
as the data source, a small window appears with the text "Hello, World!".
I'm using QQuickView::setContent(), which is a function that is marked as internal in the Qt source, but it's used in qmlscene, and I'm trying to achieve a similar effect here (https://code.woboq.org/qt5/qtdeclarative/tools/qmlscene/main.cpp.html#main).
Obviously, the problem is when I try to call createObjectFromMemory()
with TextExampleQml()
as the data source. The QML engine has no idea that Simple
should be a type--how can I tell QML about this type? I thought that qmlRegisterType
(http://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType) might be a good candidate, but I could not find a way to register a type that is coming from a chunk of QML sitting in memory. I'd like to say, "Hey, I have this chunk of QML code, and it's type is 'Simple'."
Let's assume I could easily predict all type dependencies and would be able to know the proper order to load QML memory chunks. Can I register the QML code returned by SimpleQml() as the type Simple
(keeping it in memory, and not saving it to a file)? If anything, I'd like to understand how the QML engine does this under the hood.
I realize I'm breaking a golden rule: don't reach into QML from C++ (reach into C++ from QML instead). So if anyone has a JavaScript solution that's cleaner, I'm open to suggestions, but please remember I'm trying to load from memory, not from file(s).
Update:
As noted in the comments, there is a JavaScript function called createQmlObject() that creates a QML object from a string, which is extremely similar to what I'm doing in C++ above. However, this suffers the same problem--the type that createQmlObject
creates is...well, it's not a type, it's an object. What I'd like is a way to register a string of in-memory QML as a QML type. So my question still stands.