我正在研究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
自定义消息处理程序也处理了...你会看到它的发展方向:应用程序陷入循环并最终崩溃。
到目前为止我的观察结果:
不将传入的消息转发到QStringList属性(只是对它们进行cout)会导致只调用一次自定义消息处理程序。
在构建时间之后创建相同的错误不会造成任何问题:
handle message: [...]/App/containerview.qml:76:9: QML Repeater: Binding loop detected for property "model"
不使用基于列表的属性(例如将所有消息存储在单个QString对象中)也可以解决问题,但有一些明显的缺点。
最后问题是: 有人可以解释为什么会发生这种情况以及如何解决这个问题?
答案 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引擎)可能会做很多事情,而信号 - addEntry
和HandleMessage
仍然在调用堆栈上。