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

From Qt Wiki
Jump to navigation Jump to search
No edit summary
 
No edit summary
Line 1: Line 1:
=Updating <span class="caps">QML</span> content from Python threads=
[[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;]


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).
= Updating QML content from Python threads =


==WorkingOnIt.py==
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).


===Importing required modules===
== 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''':
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''':


===The Downloader object===
<code><br />import os<br />import sys<br />import threading<br />import urllib
 
from PySide import QtCore, QtGui, QtDeclarative<br /></code>
 
=== 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:
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:


===Creating a new Downloader instance===
<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
<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
<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()
<br /> def _get_progress(self):<br /> return self._progress
<br /> def _set_progress(self, progress):<br /> self._progress = progress<br /> self.on_progress.emit()
<br /> def _get_running(self):<br /> return self._running
<br /> def _set_running(self, running):<br /> self._running = running<br /> self.on_running.emit()
<br /> def _get_filename(self):<br /> return self._filename
<br /> def _get_size(self):<br /> return self._size
<br /> on_progress = QtCore.Signal()<br /> on_running = QtCore.Signal()<br /> on_filename = QtCore.Signal()<br /> on_size = QtCore.Signal()
<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>
<br />h3. Creating a new Downloader instance
<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>
<br />h3. QApplication, QDeclarativeView and context properties
<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:
<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>
<br />h2. WorkingOnIt.qml
<br />This is the QML UI for our downloader example. The interesting parts here are:
<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


As an example, we create a new Downloader here that downloads a kernel image for the N900 from the MeeGo repository:
<code><br />import Qt 4.7


===QApplication, QDeclarativeView and context properties===
Rectangle {<br /> width: 200; height: 160


As usual, we simply instantiate a new '''QApplication''' and a '''QDeclarativeView'''. Our Downloader is exposed to the <span class="caps">QML</span> context by setting it as context property '''downloader''' on the rootContext of our view. We then simply load the <span class="caps">QML</span> file via '''setSource''', '''show''' the view and execute the application:
function formatProgress(size, progress) {<br /> return &quot;&quot; + parseInt(progress*size/1024) +<br /> &quot; KiB (&quot;''parseInt(progress*100.)'' &quot;%)&quot;;<br /> }


==WorkingOnIt.qml==
Text {<br /> x: progressBar.x; y: 20<br /> width: progressBar.width<br /> font.pixelSize: 8<br /> text: downloader.filename<br /> elide: Text.ElideRight<br /> }


This is the <span class="caps">QML</span> UI for our downloader example. The interesting parts here are:
Rectangle {<br /> id: progressBar<br /> color: &quot;#aaa&amp;quot;


* When the button is clicked, '''downloader.start_download()''' (a PySide Slot) is called, which starts the thread
x: 20; y: 60<br /> width: parent.width-40<br /> height: 20
* 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


==How the example app looks==
Rectangle {<br /> color: downloader.progress&amp;lt;1?&quot;#ee8&amp;quot;:&quot;#8e8&amp;quot;<br /> clip: true


Save the files '''WorkingOnIt.py''' and '''WorkingOnIt.qml''' in the same folder and start the app using '''python WorkingOnIt.py''':
anchors {<br /> top: parent.top<br /> bottom: parent.bottom<br /> left: parent.left<br /> }
 
width: parent.width*downloader.progress
 
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 /> }
 
Rectangle {<br /> anchors.left: progressBar.left<br /> anchors.right: progressBar.right
 
color: &quot;#aad&amp;quot;<br /> y: progressBar.y + progressBar.height + 20<br /> height: 40


[[Image:5226958058_6b5b91d1a3.jpg|Screenshot of the application in action]]
Text {<br /> anchors.fill: parent<br /> color: &quot;#003&amp;quot;<br /> text: downloader.running?&quot;Please wait…&quot;:&quot;Start download&amp;quot;


===Categories:===
verticalAlignment: Text.AlignVCenter<br /> horizontalAlignment: Text.AlignHCenter<br /> }


* [[:Category:Developing with Qt|Developing_with_Qt]]
MouseArea {<br /> anchors.fill: parent<br /> onClicked: { downloader.start_download() }<br /> }<br /> }<br />}<br /></code>
** [[:Category:Developing with Qt::Qt-Quick|Qt Quick]]
* [[:Category:Developing with Qt::Qt-Quick::QML|QML]]


* [[:Category:Developing with Qt::Qt-Quick::Tutorial|Tutorial]]
== How the example app looks ==


* [[:Category:LanguageBindings|LanguageBindings]]
Save the files '''WorkingOnIt.py''' and '''WorkingOnIt.qml''' in the same folder and start the app using '''python WorkingOnIt.py''':
** [[:Category:LanguageBindings::PySide|PySide]]
* [[:Category:snippets|snippets]]

Revision as of 09:29, 24 February 2015






[toc align_right="yes&quot; depth="3&quot;]

Updating QML content from Python threads

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:

<br />import os<br />import sys<br />import threading<br />import urllib

from PySide import QtCore, QtGui, QtDeclarative<br />

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:

<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
<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
<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()
<br /> def _get_progress(self):<br /> return self._progress
<br /> def _set_progress(self, progress):<br /> self._progress = progress<br /> self.on_progress.emit()
<br /> def _get_running(self):<br /> return self._running
<br /> def _set_running(self, running):<br /> self._running = running<br /> self.on_running.emit()
<br /> def _get_filename(self):<br /> return self._filename
<br /> def _get_size(self):<br /> return self._size
<br /> on_progress = QtCore.Signal()<br /> on_running = QtCore.Signal()<br /> on_filename = QtCore.Signal()<br /> on_size = QtCore.Signal()
<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 />


h3. 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:


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


h3. 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:


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


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

<br />import Qt 4.7

Rectangle {<br /> width: 200; height: 160

function formatProgress(size, progress) {<br /> return &quot;&quot; + parseInt(progress*size/1024) +<br /> &quot; KiB (&quot;''parseInt(progress*100.)'' &quot;%)&quot;;<br /> }

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

Rectangle {<br /> id: progressBar<br /> color: &quot;#aaa&amp;quot;

x: 20; y: 60<br /> width: parent.width-40<br /> height: 20

Rectangle {<br /> color: downloader.progress&amp;lt;1?&quot;#ee8&amp;quot;:&quot;#8e8&amp;quot;<br /> clip: true

anchors {<br /> top: parent.top<br /> bottom: parent.bottom<br /> left: parent.left<br /> }

width: parent.width*downloader.progress

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

Rectangle {<br /> anchors.left: progressBar.left<br /> anchors.right: progressBar.right

color: &quot;#aad&amp;quot;<br /> y: progressBar.y + progressBar.height + 20<br /> height: 40

Text {<br /> anchors.fill: parent<br /> color: &quot;#003&amp;quot;<br /> text: downloader.running?&quot;Please wait&quot;:&quot;Start download&amp;quot;

verticalAlignment: Text.AlignVCenter<br /> horizontalAlignment: Text.AlignHCenter<br /> }

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

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: