Search code examples
qtsignalsqmlloaderqt-quick

How to propertly connect signal in Loader's item?


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

Solution

  • 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.