如何在 Loader 的项目中正确连接信号?
How to propertly connect signal in Loader's item?
我想将一个信号从 QObject 连接到由 "Loader" qml 元素加载的各个页面。我的问题类似于 Dead QML elements receiving signals? 但在调用 "onDestruction" 方法之前加载的项目被销毁。
例如下面,如果在控制台中从第 1 页切换到第 2 页:
"QML: Loading status: 1 Item: QDeclarativeRectangle(0x8dcd408, "page2")
QML Item: Loaded QDeclarativeRectangle(0x8dcd408, "page2") 1
qrc:/page1.qml:12: TypeError: Result of expression 'parent' [null] is not an object.
qrc:/page1.qml:15: ReferenceError: Can't find variable: page1text"
每秒。所以不能断开信号,因为父对象被破坏了。
如何处理加载项中来自 QObject (root) 的信号?或如何断开未加载页面的信号?
main.qml
import QtQuick 1.1
Rectangle {
id: root
objectName: "root"
width: 360
height: 360
state: "page1"
color: "white"
Item {
width: parent.width
height: parent.height/2
anchors.top: parent.top
Loader {
id: pageLoader
objectName: "pageLoader"
anchors.fill: parent
anchors.centerIn: parent
signal textMsg(variant params)
onStatusChanged: console.log("QML: Loading status: ", status, " Item: ", item)
onLoaded: { console.log("QML Item: Loaded",item,status); }
}
}
states: [
State {
name: "page1"
PropertyChanges { target: pageLoader; source: "qrc:/page1.qml"}
}
,State {
name: "page2"
PropertyChanges { target: pageLoader; source: "qrc:/page2.qml"}
}
]
Timer {
// simulate signals from QObject
interval: 1000; running: true; repeat: true
onTriggered: pageLoader.textMsg({"msg2page1":"test","msg2page2":"test"})
}
Rectangle {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "yellow"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 1"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page1";
}
}
}
Rectangle {
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "red"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 2"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page2";
}
}
}
}
page1.qml
import QtQuick 1.1
Rectangle {
id: page1
objectName: "page1"
color: "yellow"
Component.onCompleted: {
parent.textMsg.connect(msgHandler);
}
Component.onDestruction: {
parent.textMsg.disconnect(msgHandler);
}
function msgHandler(params) {
page1text.text += " "+params.msg2page1;
}
Text {
id: page1text
anchors.fill: parent
wrapMode: Text.WordWrap
text: "page1"
}
}
page2.qml
import QtQuick 1.1
Rectangle {
id: page2
objectName: "page2"
color: "red"
}
Loader
documenation 对此进行了很好的描述。上面写着:
Any signals emitted from the loaded item can be received using the Connections element.
还有一个例子,为了清楚起见,我复制在下面:
// Application.qml
import QtQuick 1.0
Item {
width: 100; height: 100
Loader {
id: myLoader
source: "MyItem.qml"
}
Connections {
target: myLoader.item
onMessage: console.log(msg)
}
}
// MyItem.qml
import QtQuick 1.0
Rectangle {
id: myItem
signal message(string msg)
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: myItem.message("clicked!")
}
}
显然,如果 item
被销毁,任何信号处理程序都将被忽略,直到再次重新创建目标。
我的回答是:不要使用"Loader",通过JS创建子对象,不需要时销毁,例如:
main.qml
import QtQuick 1.1
import "qrc:/pageloader.js" as Pageloader
Rectangle {
id: root
objectName: "root"
width: 360
height: 360
state: "page1"
color: "white"
signal textMsg (variant params)
states: [
State {
name: "page1"
StateChangeScript{ script: Pageloader.createPageObject("qrc:/page1.qml");}
}
,State {
name: "page2"
StateChangeScript{ script: Pageloader.createPageObject("qrc:/page2.qml");}
}
]
Timer {
// simulate signals from QObject
interval: 1000; running: true; repeat: true
onTriggered: textMsg({"msg2page1":"test","msg2page2":"test"})
}
Rectangle {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "yellow"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 1"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page1";
}
}
}
Rectangle {
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "red"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 2"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page2";
}
}
}
}
pageloader.js
var component;
var sprite;
function createPageObject(path) {
if(sprite){
console.log("sprite.destroy() ",typeof sprite);
sprite.destroy();
console.log("component.destroy() ",typeof component);
component.destroy();
}
component = Qt.createComponent(path);
if (component.status === Component.Ready)
finishCreation();
else
component.statusChanged.connect(finishCreation);
}
function finishCreation() {
if (component.status == Component.Ready) {
sprite = component.createObject(root);
if (sprite == null) {
// Error Handling
console.log("Error creating object");
}
} else{
if (component.status === Component.Error) {
// Error Handling
console.log("Error loading component:", component.errorString());
}else{
console.log("Component status changed:", component.status);
}
}
}
page1.qml 和 page2.qml 未更改。
我明白了。我的设置:
- qml 文件显示 ListViews
- 几个定义列表视图的 qml 文件,每个文件都有不同的 SQL 表的不同列。模型来自C++
所以这是缩短的代码:
Dialog {
id: dialog
width: 1000; height: 400
property Component listViewItem
signal newDatabaseEntry( string text ) [1]
contentItem: Rectangle {
[...]
TextInputWithButton { [3]
id: newRecords
onInputAccepted: { newDatabaseEntry( text ) } [1]
}
}
[...]
Loader {
id: listViewPlaceholder
anchors.fill: parent
sourceComponent: dialog.listViewItem
onLoaded: {
if( typeof listViewPlaceholder.item.insertRecord === "function" )
// newRecords.inputAccepted.connect( listViewPlaceholder.item.insertRecord ) [1]
dialog.newDatabaseEntry.connect( listViewPlaceholder.item.insertRecord ) [2]
}
以上代码是ListViews的通用视图。信号往返 [1] 是必需的,否则不会传递任何数据。此处描述了如何链接信号:
http://doc.qt.io/qt-5/qtqml-syntax-signals.html#connecting-signals-to-methods-and-signals
输入按钮 [3] 传递确认的数据以插入数据库。
传递给上述函数的 ListView 如下所示:
DialogSqlSingleColumnEdit {
listViewItem: ListView {
function insertRecord( text ) {
console.log( "done:" + text )
sqlModel.insertRecord( text )
}
[...]
调用 insertRecord 将文本转发到 sql-C++ 模型。
我想将一个信号从 QObject 连接到由 "Loader" qml 元素加载的各个页面。我的问题类似于 Dead QML elements receiving signals? 但在调用 "onDestruction" 方法之前加载的项目被销毁。 例如下面,如果在控制台中从第 1 页切换到第 2 页:
"QML: Loading status: 1 Item: QDeclarativeRectangle(0x8dcd408, "page2")
QML Item: Loaded QDeclarativeRectangle(0x8dcd408, "page2") 1
qrc:/page1.qml:12: TypeError: Result of expression 'parent' [null] is not an object.
qrc:/page1.qml:15: ReferenceError: Can't find variable: page1text"
每秒。所以不能断开信号,因为父对象被破坏了。
如何处理加载项中来自 QObject (root) 的信号?或如何断开未加载页面的信号?
main.qml
import QtQuick 1.1
Rectangle {
id: root
objectName: "root"
width: 360
height: 360
state: "page1"
color: "white"
Item {
width: parent.width
height: parent.height/2
anchors.top: parent.top
Loader {
id: pageLoader
objectName: "pageLoader"
anchors.fill: parent
anchors.centerIn: parent
signal textMsg(variant params)
onStatusChanged: console.log("QML: Loading status: ", status, " Item: ", item)
onLoaded: { console.log("QML Item: Loaded",item,status); }
}
}
states: [
State {
name: "page1"
PropertyChanges { target: pageLoader; source: "qrc:/page1.qml"}
}
,State {
name: "page2"
PropertyChanges { target: pageLoader; source: "qrc:/page2.qml"}
}
]
Timer {
// simulate signals from QObject
interval: 1000; running: true; repeat: true
onTriggered: pageLoader.textMsg({"msg2page1":"test","msg2page2":"test"})
}
Rectangle {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "yellow"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 1"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page1";
}
}
}
Rectangle {
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "red"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 2"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page2";
}
}
}
}
page1.qml
import QtQuick 1.1
Rectangle {
id: page1
objectName: "page1"
color: "yellow"
Component.onCompleted: {
parent.textMsg.connect(msgHandler);
}
Component.onDestruction: {
parent.textMsg.disconnect(msgHandler);
}
function msgHandler(params) {
page1text.text += " "+params.msg2page1;
}
Text {
id: page1text
anchors.fill: parent
wrapMode: Text.WordWrap
text: "page1"
}
}
page2.qml
import QtQuick 1.1
Rectangle {
id: page2
objectName: "page2"
color: "red"
}
Loader
documenation 对此进行了很好的描述。上面写着:
Any signals emitted from the loaded item can be received using the Connections element.
还有一个例子,为了清楚起见,我复制在下面:
// Application.qml
import QtQuick 1.0
Item {
width: 100; height: 100
Loader {
id: myLoader
source: "MyItem.qml"
}
Connections {
target: myLoader.item
onMessage: console.log(msg)
}
}
// MyItem.qml
import QtQuick 1.0
Rectangle {
id: myItem
signal message(string msg)
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: myItem.message("clicked!")
}
}
显然,如果 item
被销毁,任何信号处理程序都将被忽略,直到再次重新创建目标。
我的回答是:不要使用"Loader",通过JS创建子对象,不需要时销毁,例如:
main.qml
import QtQuick 1.1
import "qrc:/pageloader.js" as Pageloader
Rectangle {
id: root
objectName: "root"
width: 360
height: 360
state: "page1"
color: "white"
signal textMsg (variant params)
states: [
State {
name: "page1"
StateChangeScript{ script: Pageloader.createPageObject("qrc:/page1.qml");}
}
,State {
name: "page2"
StateChangeScript{ script: Pageloader.createPageObject("qrc:/page2.qml");}
}
]
Timer {
// simulate signals from QObject
interval: 1000; running: true; repeat: true
onTriggered: textMsg({"msg2page1":"test","msg2page2":"test"})
}
Rectangle {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "yellow"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 1"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page1";
}
}
}
Rectangle {
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "red"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 2"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page2";
}
}
}
}
pageloader.js
var component;
var sprite;
function createPageObject(path) {
if(sprite){
console.log("sprite.destroy() ",typeof sprite);
sprite.destroy();
console.log("component.destroy() ",typeof component);
component.destroy();
}
component = Qt.createComponent(path);
if (component.status === Component.Ready)
finishCreation();
else
component.statusChanged.connect(finishCreation);
}
function finishCreation() {
if (component.status == Component.Ready) {
sprite = component.createObject(root);
if (sprite == null) {
// Error Handling
console.log("Error creating object");
}
} else{
if (component.status === Component.Error) {
// Error Handling
console.log("Error loading component:", component.errorString());
}else{
console.log("Component status changed:", component.status);
}
}
}
page1.qml 和 page2.qml 未更改。
我明白了。我的设置:
- qml 文件显示 ListViews
- 几个定义列表视图的 qml 文件,每个文件都有不同的 SQL 表的不同列。模型来自C++
所以这是缩短的代码:
Dialog {
id: dialog
width: 1000; height: 400
property Component listViewItem
signal newDatabaseEntry( string text ) [1]
contentItem: Rectangle {
[...]
TextInputWithButton { [3]
id: newRecords
onInputAccepted: { newDatabaseEntry( text ) } [1]
}
}
[...]
Loader {
id: listViewPlaceholder
anchors.fill: parent
sourceComponent: dialog.listViewItem
onLoaded: {
if( typeof listViewPlaceholder.item.insertRecord === "function" )
// newRecords.inputAccepted.connect( listViewPlaceholder.item.insertRecord ) [1]
dialog.newDatabaseEntry.connect( listViewPlaceholder.item.insertRecord ) [2]
}
以上代码是ListViews的通用视图。信号往返 [1] 是必需的,否则不会传递任何数据。此处描述了如何链接信号:
http://doc.qt.io/qt-5/qtqml-syntax-signals.html#connecting-signals-to-methods-and-signals
输入按钮 [3] 传递确认的数据以插入数据库。
传递给上述函数的 ListView 如下所示: DialogSqlSingleColumnEdit {
listViewItem: ListView {
function insertRecord( text ) {
console.log( "done:" + text )
sqlModel.insertRecord( text )
}
[...]
调用 insertRecord 将文本转发到 sql-C++ 模型。