从内存而不是 URL 动态加载 QML
Posted
技术标签:
【中文标题】从内存而不是 URL 动态加载 QML【英文标题】:Dynamically Loading QML from Memory Instead of a URL 【发布时间】:2017-01-19 04:19:01 【问题描述】:是否可以从内存中动态加载 QML 而不是使用文件?我见过很多总是需要 URL 的代码,像这样:
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();
在上面的示例中,main.qml
将被加载,QML 的魔力将确保任何类型都得到解析。特别是,Qt 将文件的名称视为 QML 类型,该类型可用于同一目录中的任何其他 QML 文件。因此,如果main.qml
使用了Simple
类型,它将查找文件Simple.qml
并将其用作类型定义(http://doc.qt.io/qt-5/qtqml-documents-definetypes.html)。
现在我要变得危险了:我想使用内存中的 QML 而不是使用文件。这就是它的工作方式(请暂时忽略糟糕的设计、内存泄漏等):
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;
所以SimpleQml()
只是一个带有“Hello, World!”的文本对象。作为文本。当createObjectFromMemory()
以SimpleQml()
作为数据源调用时,会出现一个带有文本“Hello, World!”的小窗口。
我正在使用 QQuickView::setContent(),这是一个在 Qt 源代码中标记为内部的函数,但它在 qmlscene 中使用,我试图在这里实现类似的效果 (https://code.woboq.org/qt5/qtdeclarative/tools/qmlscene/main.cpp.html#main) .
显然,问题是当我尝试使用TextExampleQml()
作为数据源调用createObjectFromMemory()
时。 QML 引擎不知道 Simple
应该是一个类型——我如何告诉 QML 这个类型?我认为 qmlRegisterType
(http://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType) 可能是一个不错的候选者,但是我找不到一种方法来注册来自内存中的 QML 块的类型。我想说,“嘿,我有这段 QML 代码,它的类型是‘简单’。”
假设我可以轻松预测所有类型的依赖关系,并且能够知道加载 QML 内存块的正确顺序。我可以将 SimpleQml() 返回的 QML 代码注册为 Simple
类型(将其保存在内存中,而不是将其保存到文件中)吗?如果有的话,我想了解 QML 引擎是如何做到这一点的。
我意识到我违反了一条黄金法则:不要从 C++ 接触到 QML(而是从 QML 接触到 C++)。因此,如果有人有更简洁的 javascript 解决方案,我愿意接受建议,但请记住我正在尝试从内存中加载,而不是从文件中加载。
更新:
如 cmets 中所述,有一个名为 createQmlObject() 的 JavaScript 函数从字符串创建 QML 对象,这与我在上面的 C++ 中所做的非常相似。但是,这会遇到同样的问题——createQmlObject
创建的类型是……嗯,它不是类型,而是对象。我想要的是一种将内存中 QML 字符串注册为 QML 类型的方法。 所以我的问题仍然存在。
【问题讨论】:
你有两种在 JavaScript 中创建动态对象的方法,提到了here。但是,我不知道您在使用它们时是否会遇到与您在这里相同的问题。我怀疑 QML 引擎无法为您加载的组件提供类型名称,因为它是“匿名的”,因为它不是来自 URL。 QML 中声明的组件的类型名称取自文件名。如果您将 URL 传递给setData()
,它可能会起作用。
我猜如果您从 c++ 创建/加载组件,您的应用程序设计会有一些问题。在 QML 中有简单明了的方法可以做到这一点。
@Mitch 感谢 JavaScript 中动态对象创建的链接。我更新了我的问题以包括这种可能性。但是,它似乎仍然没有解决从内存中 QML 源代码中注册类型的挑战。当然,QML 引擎必须在后台执行此操作……也许 API 仅适用于 Qt 的设计者,但我认为这有很好的用途。
【参考方案1】:
一种选择是为您的 QML 引擎提供自定义 QNetworkAccessManager
,并通过 createRequest()
实现自定义 URL 方案的处理。
例如createRequest()
的实现将检查传递的 URL 是否具有您的方案,比如说“内存”,如果是,则需要 URL 的其余部分来决定调用哪个函数或传递哪些数据。
对于其他任何事情,它只调用基本实现。
然后可以像这样加载您的主 QML 文件
QQuickView view;
view.setSource(QUrl("memory://main.qml"));
如果没有指定,QML 中的所有 URL 都相对于当前文件,否则查找 Simple
应该查找 memory://Simple.qml
【讨论】:
谢谢!我将把它标记为最佳答案,因为它是两全其美的:它可以快速(内存速度)访问 QML 数据(这是内存中的文件),并且因为它是一个文件,所以效果很好QML 如何进行类型(通过基于文件名创建类型)。太棒了!以上是关于从内存而不是 URL 动态加载 QML的主要内容,如果未能解决你的问题,请参考以下文章