To keep our time updated. We will need to use threads. Threading in python is easy and straightforward, we will use that instead of Qt’s threading. Thread uses functions or thread calls a function. I prefer we use a technique in Qt known as signals, this is a professional method, and studying it know will make your like better and easier. Lets put our current time code into a function, use underscore(_) for the file name. I will explain why later. It is not a requirement or anything, it is just good practice
To use signals we would have to subclass QObject, straightforward.
Create a subclass of QObject, call it whatever you like. I will call it Backend.
main.py
...
from from PyQt5.QtCore import QObject, pyqtSignal
class Backend(QObject): def __init__(self):
QObject.__init__(self)
...
The above code imports QObject and pyqtSignal, in pyside this is called Signal. It is one of the few differences between pyqt and pyside.
Formally, we had a property string that received our curr_time string from python, now we create a property QtObject to receive the Backend object from python. There are not that many types. Qml converts python base types into bool, int, double, string, list, QtObject and var. var can handle every python type, but its the least loved.
main.qml
...
property string currTime: "00:00:00"
property QtObject backend
...
The above code creates a QtObject backend to hold our python object back_end. The names used are mine, feel free to change them to whatever you like
In the python pass it on
main.py
...
engine.load('./UI/main.qml')
back_end = Backend()
engine.rootObjects()[0].setProperty('backend', back_end)
...
In the above code an object back_end was created from the class Backend. We then set it to the qml property named backend
In Qml, one QtObject can receive numerous functions (called signals) from python that does numerous things, but they would have to be organised under that QtObject.
Create Connections type and target it to backend. Now inside the Connections type can be functions as numerous as we want to receive for the backend.
main.qml
...
Rectangle {
anchors.fill: parent Image {
...
}
...}Connections {
target: backend
}
...
Now thats’ how we connect with the python signals.
If we do not use threading our UI will freeze. Its quite emphatic to state that what we need here is threading and not multiprocessing.
Create two functions, one for the threading one for the actually function. Here is where the underscore comes in handy.
main.py
...
import threading
from time import sleep
...
class Backend(QObject):
def __init__(self):
QObject.__init__(self) def bootUp(self):
t_thread = threading.Thread(target=self._bootUp)
t_thread.daemon = True
t_thread.start() def _bootUp(self):
while True:
curr_time = strftime("%H:%M:%S", gmtime())
print(curr_time)
sleep(1)...
The above code has an underscore function that does the work creating an updated time.
Create a pyqtsignal called updated and call it from a function called updater
main.py
...
from PyQt5.QtCore import QObject, pyqtSignal... def __init__(self):
QObject.__init__(self) updated = pyqtSignal(str, arguments=['updater']) def updater(self, curr_time):
self.updated.emit(curr_time) ...
In the above code the pyqtSignal, updated, has as it arguments parameter the list containing the name of the function ‘updater’. From this updater function, qml shall receive data. In the updater function we call(emit) the signal updated and pass data (curr_time) to it
Update the qml, receive the signal by creating a signal handler, a signal handlers name is the capitalised form of the signal name preceded by ‘on’. So, ‘mySignal’ becomes ‘onMySignal’ and ‘mysignal’ becomes ‘onMysignal’.
main.qml
...
target: backend function onUpdated(msg) {
currTime = msg;
}
...
In the above code you can see the signal handler for updated signal is called onUpdated. It is also has the curr_time passed to it as msg.
All is well but we are yet to call the updater function. Having a seperate function to call the signal is not necessary for a small application as this. But in a big application, it is the recommended way to do it. Change the delay seconds to 1/10 of a second. I have found this figure to the best to update time.
main.py
...
curr_time = strftime("%H:%M:%S", gmtime())
self.updater(curr_time)
sleep(0.1)
...
The bootUp function should be called immediately after the UI has loaded.
...
engine.rootObjects()[0].setProperty('backend', back_end)back_end.bootUp()sys.exit(app.exec())
All is done!!!
Run the code:
>>> python main.py
Your seconds should be updating now