0
votes

I am experimenting with using signals in my UI project. I want a button to start some processes. Whilst I was doing this I realised I didn't understand what was going on, so I wrote the below code to try to pin-point what was happening.

When run, main.qml creates some coloured boxes as controls, and some white boxes for output (made from SubOne.qml).

If you click the green box it emits signal 'broadcast', and each of the white boxes are connected to that signal.

On 'broadcast' each white box calls its function bar.

And bar starts a loop. At the end of the loop it prints the number of iterations, and changes the text property to show the same number.

The first and third white boxes do 1 loop, but the second box does enough loops to cause a noticable delay.

OK, all seems fine. As expected the three functions bar are called sequentially, and output their print sequentially. But I was suprised to find that the text properties are not updated sequentially. Instead all the bars finish and then all three text properties are updated together.

In other words in SubOne.qml line 17 doesn't take effect until ALL loops complete, but line 18 takes effect as EACH loop completes.

Can anyone tell me what is going on? Is qml doing something to optimise writing to the display? If so, can it be controlled.

Mainly, how can I make the printing and text updates happen together after EACH loop exits.

Thanks for any help.

//qml.main

import QtQuick 2.4
import QtQuick.Controls 1.3

ApplicationWindow {
    visible: true

    Rectangle {
        Rectangle {
            x: 50
            y: 50
            width: 50
            height: 50
            color: "green"
            Text {
                text: "Start"
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
            }
            MouseArea {
                id: start
                signal broadcast()
                anchors.fill: parent
                onClicked: broadcast();
            }

        }
        Rectangle {
            function set() {
                first.thisText = "---";
                second.thisText = "---";
                third.thisText = "---";
            }
            id: reset
            x: 110
            y: 50
            width: 50
            height: 50
            color: "yellow"
            Text {
                text: "Reset"
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
            }
            MouseArea {
                anchors.fill: parent
                onClicked: reset.set();
            }
        }
        Rectangle {
            id: exit
            x: 170
            y: 50
            width: 50
            height: 50
            color: "red"
            Text {
                text: "Exit"
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter
            }
            MouseArea {
                anchors.fill: parent
                onClicked: Qt.quit();
            }
        }
    }


    SubOne {
        id: third
        thisName: "third"
        x: 50
        y: 110
        loop: 1
    }
    SubOne {
        id: second
        thisName: "second"
        x: 50
        y: 170
        loop: 100000000
    }
    SubOne {
        id: first
        thisName: "first"
        x: 50
        y: 230
        loop: 1
    }

}

//SubOne.qml

import QtQuick 2.0

Rectangle {
    id: rectangle1
    property string thisName: ""
    property string thisText: "nothing yet"
    property int loop: 1
    height:50
    width:170

    function bar() {
        print("looping " + thisName + " with: " + loop);
        for (var counter=0; counter<loop; counter+=1){
            //do nothing much
        }
        thisText = counter;
        print("looped " + thisName + " to: " + counter);
    }

    Text {
        text: thisText
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
    }

    Connections {
        target: start
        onBroadcast: bar()
    }
}

Here is the new code using WorkerScript, as suggested.

//SubOne.qml

import QtQuick 2.0

Rectangle {
    id: rectangle1
    property string thisName: ""
    property string thisText: "nothing yet"
    property int loop: 1
    height:50
    width:170

    WorkerScript {
        id: myWorker
        source: "script.js"
        onMessage: thisText = messageObject.reply
    }

    Text {
        text: thisText
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
    }

    Connections {
        target: start
        onBroadcast: {
            myWorker.sendMessage({'loopVal':loop, 'nameVal':thisName});
        }
    }
}

//script.js

WorkerScript.onMessage = function(obj) {
    print("looping " + obj.nameVal + " to: " + obj.loopVal);
    for (var counter=0; counter<obj.loopVal; counter+=1){
        //do nothing much
    }
    print("finished " + obj.nameVal + " at: " + counter);
    WorkerScript.sendMessage({ 'reply': counter})
}

It is worth noting that the sendMessage on line 26 of SubOne.qml does not get handled by the onMessage at line 14 of SubOne, it gets handled by the WorkerScript.onMessage at line 1 of script.js, and similarly the WorkerScript.sendMessage at line 7 of script.js is handled back in the SubOne.qml file at line 14. Also, the message sent to the new thread gets only one parameter, so to send the nameVal and loopVal I have put them in an object, created inline at line 26 of SubOne.qml

Maybe that is obvious, but it confused me for a while ;)

Its interesting that if you press 'red' during the looping, the main thread exits immediately but the second thread finishes what its doing first.

Hope this helps someone.

And thanks to BaCaRoZzo and Kuba Ober.

1
As Kuba Ober pointed out you are blocking the main thread. GUI is updated in that very same thread. You should offload the offending code to a working thread. Try to look at WorkerScript for a possible approach. - BaCaRoZzo
Oh yes, of course. I should have thought of that. I will rewrite it to use WorkerScript and post that for others as soon as I can. Thanks to you both. - shoulderstack

1 Answers

3
votes

QML isn't doing anything much here - you are, because you're blocking the event loop. When your code is running, the event loop isn't, and there's no way for QML to do any display updates. You must relinquish control back to the event loop for the display to update. This is no different from a widget-based UI. You'd have same behavior there, too.