Details
-
Bug
-
Resolution: Fixed
-
P3: Somewhat important
-
6.5
-
None
-
21e5f0ea6 (dev)
Description
https://doc.qt.io/qt-6/qtqml-syntax-signals.html#connecting-signals-to-methods-and-signals mentions how to connect and disconnect from a signal, but it doesn't mention the lifetime of those connections. Under normal (C++) connections rules, if a sender or receiver is destroyed, the connection is destroyed along with it. The rules for QML/JavaScript, however, are not clear:
import QtQuick import QtQuick.Controls ApplicationWindow { id: root width: 400 height: 400 visible: true CheckBox { id: loadedCheckBox text: "Load some UI that listens to\n the position of rect while it's loaded" width: parent.width } Rectangle { id: rect y: 100 width: 32 height: 32 color: "tomato" } NumberAnimation { target: rect property: "x" duration: 2000 from: 0 to: root.width - rect.width easing.type: Easing.InOutQuad loops: Animation.Infinite running: true } Loader { active: loadedCheckBox.checked sourceComponent: Item { Component.onCompleted: { // In the real-world example, it's also not possible to reference rect via id. let imperativeReferenceToRect = rect imperativeReferenceToRect.xChanged.connect(() => { print("rect.x changed:", imperativeReferenceToRect.x) }) } } } }
In this example I want the connection to "rect" to only be alive as long as the UI it was created in is alive. However, that doesn't work, as the callback is kept alive even after the Loader it was created unloads its item.
To reproduce, check the check box. Output will be printed as long as it's checked. Now uncheck it - the output should stop being printed, but it's not.
For it to work, the disconnect must be done explicitly:
import QtQuick import QtQuick.Controls ApplicationWindow { id: root width: 400 height: 400 visible: true CheckBox { id: loadedCheckBox text: "Load some UI that listens to\n the position of rect while it's loaded" width: parent.width } Rectangle { id: rect y: 100 width: 32 height: 32 color: "tomato" } NumberAnimation { target: rect property: "x" duration: 2000 from: 0 to: root.width - rect.width easing.type: Easing.InOutQuad loops: Animation.Infinite running: true } Loader { active: loadedCheckBox.checked sourceComponent: Item { function xUpdated() { print("rect.x changed:", rect.x) } Component.onCompleted: rect.xChanged.connect(xUpdated) Component.onDestruction: rect.xChanged.disconnect(xUpdated) } } }
The rules surrounding this should be documented; it should be made clear that callbacks live beyond the components that they were created in (if this is actually intentional).
Attachments
Issue Links
- resulted in
-
QTBUG-117943 Add context object to connect() to tie the lifetime of the connection to it.
-
- Reported
-
-
QTBUG-118166 Allow specifying a receiver/context object as an argument to connect()
-
- Closed
-
For Gerrit Dashboard: QTBUG-114194 | ||||||
---|---|---|---|---|---|---|
# | Subject | Branch | Project | Status | CR | V |
510707,4 | doc: Document connections lifetime subtleties | dev | qt/qtdeclarative | Status: MERGED | +2 | 0 |