从内存而不是 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的主要内容,如果未能解决你的问题,请参考以下文章

是否可以通过远程更改 qml 文件来更新 Qml Android 应用程序

类的动态装载java

qml Loader异步导致ComBoBox数据乱序

从 web url 动态加载的图像在加载时不可见

Safari:无法从 blob url 动态加载视频

Safari:无法从 blob url 动态加载视频