Updating-QML-content-from-Python-threads: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
m (Fixed code blocks formatting)
 
(8 intermediate revisions by 4 users not shown)
Line 1: Line 1:
[[Category:LanguageBindings::PySide]]<br />[[Category:snippets]]<br />[[Category:Developing_with_Qt::Qt Quick]]<br />[[Category:Developing_with_Qt::Qt Quick::QML]]<br />[[Category:Developing_with_Qt::Qt Quick::Tutorial]]<br />[toc align_right=&quot;yes&amp;quot; depth=&quot;3&amp;quot;]


= Updating QML content from Python threads =
[[Category:PySide]]
[[Category:snippets]]
[[Category:Developing_with_Qt::Qt Quick]]
[[Category:Developing_with_Qt::Qt Quick::QML]]
[[Category:Developing_with_Qt::Qt Quick::Tutorial]]
 


This [[PySide]] tutorial shows you how to use native Python threads (non-QThread, i.e. '''threading.Thread''') to carry out work in the background (e.g. downloading files). In the special case of downloading, you might want to use '''QNetworkAccessManager''' and friends, but in this case, we assume that you can't use it for whatever reason (e.g. because you want to use Twisted or because you already have your special download code that you want to reuse).
This [[PySide]] tutorial shows you how to use native Python threads (non-QThread, i.e. '''threading.Thread''') to carry out work in the background (e.g. downloading files). In the special case of downloading, you might want to use '''QNetworkAccessManager''' and friends, but in this case, we assume that you can't use it for whatever reason (e.g. because you want to use Twisted or because you already have your special download code that you want to reuse).
Line 11: Line 15:
We will be using standard Python modules for threading ('''threading''') and for downloading ('''urllib'''). From PySide, we need the standard module '''QtCore''', '''QtGui''' and '''QtDeclarative''':
We will be using standard Python modules for threading ('''threading''') and for downloading ('''urllib'''). From PySide, we need the standard module '''QtCore''', '''QtGui''' and '''QtDeclarative''':


<code><br />import os<br />import sys<br />import threading<br />import urllib
import os
 
import sys
from PySide import QtCore, QtGui, QtDeclarative<br /></code>
import threading
import urllib
from PySide import QtCore, QtGui, QtDeclarative


=== The Downloader object ===
=== The Downloader object ===
Line 19: Line 26:
We now subclass '''QObject''' (so we can have Signals, Slots and Properties in our downloader) and implement all the properties that we need for downloading the file and also for displaying the current status in the UI:
We now subclass '''QObject''' (so we can have Signals, Slots and Properties in our downloader) and implement all the properties that we need for downloading the file and also for displaying the current status in the UI:


<code><br />class Downloader(QtCore.QObject):<br /> def ''init''(self, url, filename=None):<br /> QtCore.QObject.''init''(self)<br /> self._url = url<br /> if filename is None:<br /> filename = os.path.basename(self._url)<br /> self._filename = filename<br /> self._progress = 0.<br /> self._running = False<br /> self._size = <s>1
class Downloader(QtCore.QObject):
<br /> def _download(self):<br /> def reporthook(pos, block, total):<br /> if self.size != total:<br /> self._size = total<br /> self.on_size.emit()<br /> self.progress = float(pos*block)/float(total)<br /> urllib.urlretrieve(self._url, self._filename, reporthook)<br /> self.running = False
    def __init__(self, url, filename=None):
<br /> &amp;#64;QtCore.Slot()<br /> def start_download(self):<br /> if not self.running:<br /> self.running = True<br /> thread = threading.Thread(target=self._download)<br /> thread.start()
        QtCore.QObject.__init__(self)
<br /> def _get_progress(self):<br /> return self._progress
        self._url = url
<br /> def _set_progress(self, progress):<br /> self._progress = progress<br /> self.on_progress.emit()
       
<br /> def _get_running(self):<br /> return self._running
        if filename is None:
<br /> def _set_running(self, running):<br /> self._running = running<br /> self.on_running.emit()
            filename = os.path.basename(self._url)
<br /> def _get_filename(self):<br /> return self._filename
           
<br /> def _get_size(self):<br /> return self._size
        self._filename = filename
<br /> on_progress = QtCore.Signal()<br /> on_running = QtCore.Signal()<br /> on_filename = QtCore.Signal()<br /> on_size = QtCore.Signal()
        self._progress = 0.
<br /> progress = QtCore.Property(float, _get_progress, _set_progress,<br /> notify=on_progress)<br /> running = QtCore.Property(bool, _get_running, _set_running,<br /> notify=on_running)<br /> filename = QtCore.Property(str, _get_filename, notify=on_filename)<br /> size = QtCore.Property(int, ''get_size, notify=on_size)<br /></code>
        self._running = False
<br />h3. Creating a new Downloader instance
        self._size = -1
<br />As an example, we create a new Downloader here that downloads a kernel image for the N900 from the MeeGo repository:
<br /><code><br />downloader = Downloader('http://repo.meego.com/MeeGo/builds/trunk/1.1.80.8.20101130.1/handset/images/meego-handset-armv7l-n900/meego-handset-armv7l-n900-1.1.80.8.20101130.1-vmlinuz-2.6.35.3-13.6-n900')<br /></code>
    def _download(self):
<br />h3. QApplication, QDeclarativeView and context properties
        def reporthook(pos, block, total):
<br />As usual, we simply instantiate a new '''QApplication''' and a '''QDeclarativeView'''. Our Downloader is exposed to the QML context by setting it as context property '''downloader''' on the rootContext of our view. We then simply load the QML file via '''setSource''', '''show''' the view and execute the application:
            if self.size != total:
<br /><code><br />app = QtGui.QApplication(sys.argv)<br />view = QtDeclarative.QDeclarativeView()<br />view.rootContext().setContextProperty('downloader', downloader)<br />view.setSource(file.replace('.py', '.qml'))<br />view.show()<br />app.exec''()<br /></code>
                self._size = total
<br />h2. WorkingOnIt.qml
<br />This is the QML UI for our downloader example. The interesting parts here are:
            self.on_size.emit()
<br />* When the button is clicked, '''downloader.start_download()''' (a PySide Slot) is called, which starts the thread<br />* The UI elements use properties of the downloader to determine visiblity and content</s> they are automatically updated when the properties are notified to be updated
            self.progress = float(pos * block) / float(total)
 
            urllib.urlretrieve(self._url, self._filename, reporthook)
<code><br />import Qt 4.7
            self.running = False
 
Rectangle {<br /> width: 200; height: 160
    @QtCore.Slot()
 
    def start_download(self):
function formatProgress(size, progress) {<br /> return &quot;&quot; + parseInt(progress*size/1024) +<br /> &quot; KiB (&quot;''parseInt(progress*100.)'' &quot;%)&quot;;<br /> }
        if not self.running:
 
            self.running = True
Text {<br /> x: progressBar.x; y: 20<br /> width: progressBar.width<br /> font.pixelSize: 8<br /> text: downloader.filename<br /> elide: Text.ElideRight<br /> }
 
        thread = threading.Thread(target=self._download)
Rectangle {<br /> id: progressBar<br /> color: &quot;#aaa&amp;quot;
        thread.start()
 
x: 20; y: 60<br /> width: parent.width-40<br /> height: 20
    def _get_progress(self):
 
        return self._progress
Rectangle {<br /> color: downloader.progress&amp;lt;1?&quot;#ee8&amp;quot;:&quot;#8e8&amp;quot;<br /> clip: true
    def _set_progress(self, progress):
        self._progress = progress
        self.on_progress.emit()
    def _get_running(self):
        return self._running
    def _set_running(self, running):
        self._running = running
        self.on_running.emit()
    def _get_filename(self):
        return self._filename
    def _get_size(self):
        return self._size
    on_progress = QtCore.Signal()
    on_running = QtCore.Signal()
    on_filename = QtCore.Signal()
    on_size = QtCore.Signal()
    progress = QtCore.Property(float, _get_progress, _set_progress, notify=on_progress)
    running = QtCore.Property(bool, _get_running, _set_running, notify=on_running)
    filename = QtCore.Property(str, _get_filename, notify=on_filename)
    size = QtCore.Property(int, '', _get_size, notify=on_size)


anchors {<br /> top: parent.top<br /> bottom: parent.bottom<br /> left: parent.left<br /> }
=== Creating a new Downloader instance ===
As an example, we create a new Downloader here that downloads a kernel image for the N900 from the MeeGo repository:


width: parent.width*downloader.progress
downloader = Downloader('http://repo.meego.com/MeeGo/builds/trunk/1.1.80.8.20101130.1/handset/images/meego-handset-armv7l-n900/meego-handset-armv7l-n900-1.1.80.8.20101130.1-vmlinuz-2.6.35.3-13.6-n900')


Text {<br /> anchors {<br /> fill: parent<br /> rightMargin: 5<br /> }<br /> color: &quot;black&amp;quot;<br /> text: formatProgress(downloader.size, downloader.progress)<br /> verticalAlignment: Text.AlignVCenter<br /> horizontalAlignment: Text.AlignRight<br /> }<br /> }<br /> }
=== QApplication, QDeclarativeView and context properties ===
As usual, we simply instantiate a new '''QApplication''' and a '''QDeclarativeView'''. Our Downloader is exposed to the QML context by setting it as context property '''downloader''' on the rootContext of our view. We then simply load the QML file via '''setSource''', '''show''' the view and execute the application:


Rectangle {<br /> anchors.left: progressBar.left<br /> anchors.right: progressBar.right
app = QtGui.QApplication(sys.argv)
view = QtDeclarative.QDeclarativeView()
view.rootContext().setContextProperty('downloader', downloader)
view.setSource(file.replace('.py', '.qml'))
view.show()
app.exec()


color: &quot;#aad&amp;quot;<br /> y: progressBar.y + progressBar.height + 20<br /> height: 40
== WorkingOnIt.qml ==
This is the QML UI for our downloader example. The interesting parts here are:


Text {<br /> anchors.fill: parent<br /> color: &quot;#003&amp;quot;<br /> text: downloader.running?&quot;Please wait…&quot;:&quot;Start download&amp;quot;
* When the button is clicked, '''downloader.start_download()''' (a PySide Slot) is called, which starts the thread
* The UI elements use properties of the downloader to determine visiblity and content- they are automatically updated when the properties are notified to be updated


verticalAlignment: Text.AlignVCenter<br /> horizontalAlignment: Text.AlignHCenter<br /> }
import Qt 4.7
Rectangle {
    width: 200; height: 160
    function formatProgress(size, progress) {
        return "" + parseInt(progress*size/1024) + " KiB ("''parseInt(progress*100.)'' "%)";
    }
    Text {
        x: progressBar.x; y: 20
        width: progressBar.width
        font.pixelSize: 8
        text: downloader.filename
        elide: Text.ElideRight
    }
    Rectangle {
        id: progressBar
        color: "#aaa"
        x: 20; y: 60
        width: parent.width-40
        height: 20
        Rectangle {
            color: downloader.progress<1?"#ee8":"#8e8"
            clip: true
            anchors {
                top: parent.top
                bottom: parent.bottom
                left: parent.left
            }
            width: parent.width*downloader.progress
            Text {
                anchors {
                    fill: parent
                    rightMargin: 5
                }
                color: "black"
                text: formatProgress(downloader.size, downloader.progress)
                verticalAlignment: Text.AlignVCenter
                horizontalAlignment: Text.AlignRight
            }
        }
    }
    Rectangle {
        anchors.left: progressBar.left
        anchors.right: progressBar.right
        color: "#aad"
        y: progressBar.y + progressBar.height + 20
        height: 40
        Text {
            anchors.fill: parent
            color: "#003"
            text: downloader.running?"Please wait…":"Start download"
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }
        MouseArea {
            anchors.fill: parent
            onClicked: { downloader.start_download() }
        }
    }
}


MouseArea {<br /> anchors.fill: parent<br /> onClicked: { downloader.start_download() }<br /> }<br /> }<br />}<br /></code>


== How the example app looks ==
== How the example app looks ==


Save the files '''WorkingOnIt.py''' and '''WorkingOnIt.qml''' in the same folder and start the app using '''python WorkingOnIt.py''':
Save the files '''WorkingOnIt.py''' and '''WorkingOnIt.qml''' in the same folder and start the app using '''python WorkingOnIt.py''':
[[Image:Screenshotupdate.jpg]]

Latest revision as of 12:28, 7 May 2022


This PySide tutorial shows you how to use native Python threads (non-QThread, i.e. threading.Thread) to carry out work in the background (e.g. downloading files). In the special case of downloading, you might want to use QNetworkAccessManager and friends, but in this case, we assume that you can't use it for whatever reason (e.g. because you want to use Twisted or because you already have your special download code that you want to reuse).

WorkingOnIt.py

Importing required modules

We will be using standard Python modules for threading (threading) and for downloading (urllib). From PySide, we need the standard module QtCore, QtGui and QtDeclarative:

import os
import sys
import threading
import urllib

from PySide import QtCore, QtGui, QtDeclarative

The Downloader object

We now subclass QObject (so we can have Signals, Slots and Properties in our downloader) and implement all the properties that we need for downloading the file and also for displaying the current status in the UI:

class Downloader(QtCore.QObject):
    def __init__(self, url, filename=None):
        QtCore.QObject.__init__(self)
        self._url = url
        
        if filename is None:
            filename = os.path.basename(self._url)
            
        self._filename = filename
        self._progress = 0.
        self._running = False
        self._size = -1

    def _download(self):
        def reporthook(pos, block, total):
            if self.size != total:
                self._size = total

            self.on_size.emit()
            self.progress = float(pos * block) / float(total)
            urllib.urlretrieve(self._url, self._filename, reporthook)
            self.running = False

    @QtCore.Slot()
    def start_download(self):
        if not self.running:
            self.running = True

        thread = threading.Thread(target=self._download)
        thread.start()

    def _get_progress(self):
        return self._progress

    def _set_progress(self, progress):
        self._progress = progress
        self.on_progress.emit()

    def _get_running(self):
        return self._running

    def _set_running(self, running):
        self._running = running
        self.on_running.emit()

    def _get_filename(self):
        return self._filename

    def _get_size(self):
        return self._size

    on_progress = QtCore.Signal()
    on_running = QtCore.Signal()
    on_filename = QtCore.Signal()
    on_size = QtCore.Signal()

    progress = QtCore.Property(float, _get_progress, _set_progress, notify=on_progress)
    running = QtCore.Property(bool, _get_running, _set_running, notify=on_running)
    filename = QtCore.Property(str, _get_filename, notify=on_filename)
    size = QtCore.Property(int, , _get_size, notify=on_size)

Creating a new Downloader instance

As an example, we create a new Downloader here that downloads a kernel image for the N900 from the MeeGo repository:

downloader = Downloader('http://repo.meego.com/MeeGo/builds/trunk/1.1.80.8.20101130.1/handset/images/meego-handset-armv7l-n900/meego-handset-armv7l-n900-1.1.80.8.20101130.1-vmlinuz-2.6.35.3-13.6-n900')

QApplication, QDeclarativeView and context properties

As usual, we simply instantiate a new QApplication and a QDeclarativeView. Our Downloader is exposed to the QML context by setting it as context property downloader on the rootContext of our view. We then simply load the QML file via setSource, show the view and execute the application:

app = QtGui.QApplication(sys.argv)
view = QtDeclarative.QDeclarativeView()
view.rootContext().setContextProperty('downloader', downloader)
view.setSource(file.replace('.py', '.qml'))
view.show()
app.exec()

WorkingOnIt.qml

This is the QML UI for our downloader example. The interesting parts here are:

  • When the button is clicked, downloader.start_download() (a PySide Slot) is called, which starts the thread
  • The UI elements use properties of the downloader to determine visiblity and content- they are automatically updated when the properties are notified to be updated
import Qt 4.7

Rectangle {
    width: 200; height: 160

    function formatProgress(size, progress) {
        return "" + parseInt(progress*size/1024) + " KiB ("parseInt(progress*100.) "%)";
    }

    Text {
        x: progressBar.x; y: 20
        width: progressBar.width
        font.pixelSize: 8
        text: downloader.filename
        elide: Text.ElideRight
    }

    Rectangle {
        id: progressBar
        color: "#aaa"

        x: 20; y: 60
        width: parent.width-40
        height: 20

        Rectangle {
            color: downloader.progress<1?"#ee8":"#8e8"
            clip: true

            anchors {
                top: parent.top
                bottom: parent.bottom
                left: parent.left
            }

            width: parent.width*downloader.progress

            Text {
                anchors {
                    fill: parent
                    rightMargin: 5
                }
                color: "black"
                text: formatProgress(downloader.size, downloader.progress)
                verticalAlignment: Text.AlignVCenter
                horizontalAlignment: Text.AlignRight
            }
        }
    }

    Rectangle {
        anchors.left: progressBar.left
        anchors.right: progressBar.right

        color: "#aad"
        y: progressBar.y + progressBar.height + 20
        height: 40

        Text {
            anchors.fill: parent
            color: "#003"
            text: downloader.running?"Please wait…":"Start download"

            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }

        MouseArea {
            anchors.fill: parent
            onClicked: { downloader.start_download() }
        }
    }
}


How the example app looks

Save the files WorkingOnIt.py and WorkingOnIt.qml in the same folder and start the app using python WorkingOnIt.py: Screenshotupdate.jpg