将运行时QML输出转发到UI

时间:2016-09-21 18:50:10

标签: qt qml

我正在研究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); 
}

Logger的{​​{1}}方法只是将消息附加到QStringList属性并发出相应的addEntry - 信号。在QML方面,我将该属性提供给ListView

changed()

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

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

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

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

上述源有效,但会生成运行时错误。出于某种原因,HandleMessage函数在此特定方案中被调用两次:

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
    }
}

并创建一个绑定循环:

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

自定义消息处理程序也处理了...你会看到它的发展方向:应用程序陷入循环并最终崩溃。

到目前为止我的观察结果:

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

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

    handle message: [...]/App/containerview.qml:76:9: QML Repeater: Binding loop detected for property "model"
    
  3. 不使用基于列表的属性(例如将所有消息存储在单个QString对象中)也可以解决问题,但有一些明显的缺点。

  4. 最后问题是: 有人可以解释为什么会发生这种情况以及如何解决这个问题?

1 个答案:

答案 0 :(得分: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仍然在调用堆栈上。