将运行时 QML 输出转发到 UI 的问题

Posted

技术标签:

【中文标题】将运行时 QML 输出转发到 UI 的问题【英文标题】:Issue with forwarding runtime QML output to the UI 【发布时间】:2016-09-21 18:50:10 【问题描述】:

我正在开发this open source project,它基本上是一个实时 QML 解释器。由于用户的主要活动是编写 QML 代码,我想在应用程序中嵌入所有日志和警告等,以提供一些基本的类似 IDE 的功能。首先,我通过qInstallMessageHandler 安装了一个自定义消息处理程序,它接收来自 QML 的所有运行时消息:

auto Instance::HandleMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg) -> void 
    std::cout << "handle message: " << msg.toStdString() << std::endl;
    Logger::GetInstance()->addEntry(msg); 

LoggeraddEntry 方法只是将消息附加到QStringList 属性并发出相应的changed() 信号。在 QML 方面,我将该属性提供给 ListView:

ListView 
    anchors.fill: parent;
    model: App.logger.entries;
    delegate: Text 
        text: modelData;
    

这行得通。但问题与动态 QML 对象实例化结合出现。我的应用程序的“神奇”部分是对 Qt.createQmlObject 的调用,它需要一些 QML 源作为字符串并返回新创建的对象:

function compile() 
    try 
        qmlObject = Qt.createQmlObject(<qml_source>, container, "root");
     catch (exc) 
        // handle *compile-time* errors
    

如果“编译”失败,控制立即转移到 catch 子句,并且不会创建新的 QML 对象。但如果我尝试从以下来源创建对象,应用程序会崩溃:

function compile() 
    try 
        qmlObject = Qt.createQmlObject("import QtQuick 2.6\nItem  anchors.fill: parent; Rectangle  width: parent.width; height: a.height;  ", container, "root");
     catch (exc) 
        // handle *compile-time* errors
    

上述来源有效,但会产生运行时错误。出于某种原因,HandleMessage 函数在这个特定场景中被调用了两次:

handle message: [...]App/root:2: ReferenceError: a is not defined
handle message: [...]App/root:2: ReferenceError: a is not defined

并创建一个绑定循环:

handle message: [...]/App/containerview.qml:76:9: QML Repeater: Binding loop detected for property "model"

这也由自定义消息处理程序处理...您看到这是怎么回事:应用程序陷入循环并最终崩溃。

到目前为止我的观察:

    不将传入消息转发到 QStringList 属性(只是计算它们)只会导致一次自定义消息处理程序的调用。

    构造时间之后创建相同的错误不会造成任何问题:

    qmlObject = Qt.createQmlObject("import QtQuick 2.6\nimport QtQuick.Controls 1.4\nItem  anchors.fill: parent; Rectangle  id: rect; width: parent.width;  Button  anchors.centerIn: parent; text: \"do something stupid\"; onClicked:  rect.height = a.height;   ", container, "root");
    

    不使用基于列表的属性(例如将所有消息存储在单个QString 对象中)也可以解决问题,但有一些明显的缺点。

最后是问题: 有人能解释一下为什么会发生这种情况以及如何解决这个问题吗?

【问题讨论】:

我想知道拥有两个 QML 引擎会不会更好,一个运行 IDE,一个运行正在编辑的内容? @KevinKrammer 这实际上是一个好点!我试试看,谢谢! 【参考方案1】:

您让消息处理程序重新进入。你不应该:

class Instance 
  QThreadStorage<bool> handlerEntered;
  ...
;

QThreadStorage<bool> Instance::handlerEntered;

void Instance::HandleMessage(
    QtMsgType type, const QMessageLogContext& context, const QString& msg)

    if (handlerEntered.localData()) return;
    QScopedValueRollback<bool> roll(handlerEntered.localData(), true);
    std::cout << "handle message: " << msg.toStdString() << std::endl;
    Logger::GetInstance()->addEntry(msg); 

您还必须确保Logger::addEntry 方法不会重新进入事件循环,也不会发出任何消息。最有可能的是,您需要将对changed 信号的调用排队:

void Logger::addEntry(const QString & msg) 
    ...
    QMetaObject::invokeMethod(this, "listChanged(QStringList)",
       Qt::QueuedConnection, Q_ARG(QStringList, data));

否则,任何直接连接的插槽(如 QML 引擎)可能会在信号 - 和 addEntryHandleMessage 仍在调用堆栈上时做很多事情。

【讨论】:

以上是关于将运行时 QML 输出转发到 UI 的问题的主要内容,如果未能解决你的问题,请参考以下文章

如何将消息转发到具有干净范围的 Sentry(无运行时信息)

在运行时更改 QML 小部件

在运行时动态更改 QML 主题

QT 不更新 UI

当firebase ui升级到3.2.2时输出未显示

如何在运行时生成的 QML 中加载图像(jpg)