4
votes

I want connect one signal from QObject to various pages, loaded by the "Loader" qml element. My problem similar Dead QML elements receiving signals? but loaded items destroyed before calling the "onDestruction" method. For example below, if switch from page1 to page2 in console writed:

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

every second. So there can't disconnect from signal because parent object is destroyed.

How to handle signals from QObject (root) in loaded items? or How to disconnect signal from unloaded page?

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

3 Answers

10
votes

That's nicely described in Loader documenation. It reads:

Any signals emitted from the loaded item can be received using the Connections element.

There is also an example, I copy it below for the sake of clarity:

// 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!")
    }
}

Clearly, if item is destroyed, any signal handlers are ignored until the target is recreated again.

2
votes

My answer is: Don't use the "Loader", create child object by JS and destroy it as no needed, for example:

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 and page2.qml not changed.

1
votes

I got it. My setup:

  • qml file to display ListViews
  • Several qml files defining Listviews, each takes a different column of different SQL tables. The model comes from C++

So here is the shortened code:

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

The above code is the general view of ListViews. The signal roundtrip [1] is necessary, otherwise no data is passed. How to chain signals is described here:

http://doc.qt.io/qt-5/qtqml-syntax-signals.html#connecting-signals-to-methods-and-signals

The input button [3] delivers the confirmed data to be inserted into the db.

A ListView passed to the above function looks like this: DialogSqlSingleColumnEdit {

listViewItem: ListView {

    function insertRecord( text ) {
        console.log( "done:" +  text )
        sqlModel.insertRecord( text )
    }
[...]

The insertRecord is called forwards the text to the sql-C++ model.