如何分析“绑定循环”
Posted
技术标签:
【中文标题】如何分析“绑定循环”【英文标题】:How to analyse a "Binding loop" 【发布时间】:2016-07-05 06:33:28 【问题描述】:我有一个带有 C++ 模型和 QML 可视化的 Qt/QML 应用程序。
在运行时(启动),我收到警告
QML 项目:检测到属性“xyz”的绑定循环
我在 QML 中没有看到明显的循环。 我可以启用更多调试以了解此循环的来源吗?其他建议?
【问题讨论】:
我不确定,但 IIRC qml 分析器可以让您查看绑定调用。您还可以在属性分配中添加控制台调试输出。您可以在此处发布您的代码,以便我们为您提供帮助:) 感谢您的意见。 QML 分析器确实显示了 QML 绑定调用。但是,afaics,它没有显示循环中涉及哪些底层 C++ 代码。我曾经(现在仍然)希望有一些日志表明例如。 “a 绑定到 b” “b 绑定到 c” “c 绑定到 a” 好吧,如果有这个选项,我不知道。但这就是我有时用来调试循环的方法:height: console.debug("binding height"); return parent.height;
。请注意,这不会创建绑定本身,您必须使用 Qt.binding...
doc.qt.io/qt-5/qtqml-syntax-propertybinding.html
这里有一个关于绑定循环以及如何避免它们的精彩视频:youtube.com/watch?v=aSMEcAmcPDc
【参考方案1】:
我通常通过在打印警告的 Qt 代码中放置一个断点来做到这一点。为此,您需要一个带有调试符号的 Qt。
在 Qt 源代码中搜索“检测到绑定循环”会给我QQmlAbstractBinding::printBindingLoopError()。在此处放置断点通常会导致回溯,从而清楚地了解情况。
更新:David Edmundson 开发了一个小工具,可以在绑定循环上显示仅 QML 的回溯,请参阅 his blog here。引擎盖下的内容与此处描述的完全一样,只是它很好地自动化并包装在 Python 脚本中。
例子:
Rectangle
id: parent
width: child.width + 1
height: child.height + 1
Rectangle
id: child
anchors.fill: parent
回溯:
1 QQmlAbstractBinding::printBindingLoopError qqmlabstractbinding.cpp 178 0x7ffff6eb36da
2 QQmlBinding::update qqmlbinding.cpp 221 0x7ffff6eb9abe
3 QQmlBinding::update qqmlbinding_p.h 97 0x7ffff6eba354
4 QQmlBinding::expressionChanged qqmlbinding.cpp 260 0x7ffff6eb9e68
5 QQmljavascriptExpressionGuard_callback qqmljavascriptexpression.cpp 361 0x7ffff6eb223e
6 QQmlNotifier::emitNotify qqmlnotifier.cpp 94 0x7ffff6e9087a
7 QQmlData::signalEmitted qqmlengine.cpp 763 0x7ffff6e19a45
8 QMetaObject::activate qobject.cpp 3599 0x7ffff683655e
9 QMetaObject::activate qobject.cpp 3578 0x7ffff6836364
10 QQuickItem::widthChanged moc_qquickitem.cpp 1104 0x7ffff7a7ba49
11 QQuickItem::geometryChanged qquickitem.cpp 3533 0x7ffff7a6e9cd
12 QQuickItem::setSize qquickitem.cpp 6389 0x7ffff7a75f35
13 QQuickAnchorsPrivate::setItemSize qquickanchors.cpp 400 0x7ffff7a60d94
14 QQuickAnchorsPrivate::fillChanged qquickanchors.cpp 177 0x7ffff7a5fe0e
15 QQuickAnchorsPrivate::itemGeometryChanged qquickanchors.cpp 441 0x7ffff7a6106f
16 QQuickItem::geometryChanged qquickitem.cpp 3523 0x7ffff7a6e96c
17 QQuickItem::setWidth qquickitem.cpp 6091 0x7ffff7a74c1d
18 QQuickItem::qt_static_metacall moc_qquickitem.cpp 874 0x7ffff7a7b0dc
19 QQuickItem::qt_metacall moc_qquickitem.cpp 946 0x7ffff7a7b4d8
20 QQuickRectangle::qt_metacall moc_qquickrectangle_p.cpp 610 0x7ffff7c189c2
21 QMetaObject::metacall qmetaobject.cpp 296 0x7ffff680118b
22 QQmlPropertyPrivate::writeBinding qqmlproperty.cpp 1512 0x7ffff6e33ec3
23 QQmlBinding::update qqmlbinding.cpp 199 0x7ffff6eb992a
24 QQmlBinding::update qqmlbinding_p.h 97 0x7ffff6eba354
25 QQmlBinding::expressionChanged qqmlbinding.cpp 260 0x7ffff6eb9e68
26 QQmlJavaScriptExpressionGuard_callback qqmljavascriptexpression.cpp 361 0x7ffff6eb223e
27 QQmlNotifier::emitNotify qqmlnotifier.cpp 94 0x7ffff6e9087a
28 QQmlData::signalEmitted qqmlengine.cpp 763 0x7ffff6e19a45
29 QMetaObject::activate qobject.cpp 3599 0x7ffff683655e
30 QMetaObject::activate qobject.cpp 3578 0x7ffff6836364
31 QQuickItem::widthChanged moc_qquickitem.cpp 1104 0x7ffff7a7ba49
32 QQuickItem::geometryChanged qquickitem.cpp 3533 0x7ffff7a6e9cd
33 QQuickItem::setSize qquickitem.cpp 6389 0x7ffff7a75f35
34 QQuickAnchorsPrivate::setItemSize qquickanchors.cpp 400 0x7ffff7a60d94
35 QQuickAnchorsPrivate::fillChanged qquickanchors.cpp 177 0x7ffff7a5fe0e
36 QQuickAnchorsPrivate::update qquickanchors.cpp 431 0x7ffff7a60fc6
37 QQuickAnchorsPrivate::updateOnComplete qquickanchors.cpp 425 0x7ffff7a60f93
38 QQuickItem::componentComplete qquickitem.cpp 4593 0x7ffff7a70944
39 QQmlObjectCreator::finalize qqmlobjectcreator.cpp 1207 0x7ffff6ecab66
40 QQmlComponentPrivate::complete qqmlcomponent.cpp 928 0x7ffff6e38609
41 QQmlComponentPrivate::completeCreate qqmlcomponent.cpp 964 0x7ffff6e386ee
42 QQmlComponent::completeCreate qqmlcomponent.cpp 957 0x7ffff6e386a0
43 QQmlComponent::create qqmlcomponent.cpp 791 0x7ffff6e37edd
44 QQuickView::continueExecute qquickview.cpp 476 0x7ffff7b720d4
45 QQuickViewPrivate::execute qquickview.cpp 124 0x7ffff7b7101f
46 QQuickView::setSource qquickview.cpp 253 0x7ffff7b71426
47 main main.cpp 24 0x4033e4
在回溯中,可以看到子项的anchors.fill
锚点是在加载文件时计算的(第35、36 帧)。这会导致子项的宽度发生变化(第 31 帧),从而导致对父项的“宽度”属性(第 17 帧)的绑定进行绑定更新(第 25 帧)。这反过来又会强制重新计算子锚点(第 14 帧),这会改变子锚点的宽度(第 10 帧),从而更新绑定(第 4 帧)。这与第 25 帧中已更新的绑定相同,因此存在绑定循环。可以看到第 25 帧和第 4 帧中的this
指针是相同的,即递归更新相同的绑定。
【讨论】:
这看起来很有希望!您能指导我如何获得“带有调试符号的 Qt”吗?我应该自己编译 Qt 吗? 如果您使用的是 Linux,请安装*-dbg
软件包。
我自己从源代码编译 Qt,所以我只需将 -debug
标志传递给 configure
。不知道预构建的包。
bugreports.qt.io 是否有功能请求默认启用有用的调试打印输出?我很想为它投票,但我的 google-foo 不能胜任这项任务。
@Velkan ,是否愿意为正确解决的问题投票? bugreports.qt.io/browse/QTBUG-36525【参考方案2】:
非常感谢您的收据,但它对我没有帮助。如果有人需要它,请添加另一种可能的解决方案。我在ListView
中得到绑定循环,试图将所有项目宽度和列表宽度设置为项目最大值:
ListView
implicitWidth: contentItem.childrenRect.width
delegate: listItem
Item
id: listItem
width: Math.max(internalWidth, listView.implicitWidth)
在项目计数更新时出现绑定循环错误,但并非每次都出现 - 仅在某些批量绑定更新时出现,而没有实际的绑定循环。能够通过将绑定表达式移动到 Binding QML Type 并向其添加 delayed
属性来解决该问题:
Item // Item causing binding loop
Binding on item_property_causing_loop
value: <binding_expression>
when: <when_expression> // Optional however could also help
delayed: true // Prevent intermediary values from being assigned
所以在我的情况下是:
Item // Item causing binding loop
id: listItem
Binding on width
value: Math.max(internalWidth, listView.implicitWidth)
when: index >= 0 // Optional however could also help
delayed: true // Prevent intermediary values from being assigned
【讨论】:
以上是关于如何分析“绑定循环”的主要内容,如果未能解决你的问题,请参考以下文章