i'm stucked on a Qtimer that don't start, so the number that i need to update on the GUI is never showed.
i don't want to use a while loop inside the code because i need to change some values in real time without any problem.
i really don't understand why it don't start... or better, it start, but don't run the function update_label.
any suggestion?
Thanks a lot for your time!
here is my code:
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setObjectName("MainWindow")
self.resize(1300, 768)
self.setMinimumSize(1300,768)
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("Bot", "Bot"))
self.number = QtWidgets.QLabel(self)
self.number .setGeometry(QtCore.QRect(1100, 10, 300, 20))
self.number .setObjectName("number")
self.show()
self.threadpool = QThreadPool()
self.updater = Updater()
self.threadpool.start(self.updater)
self.updater.update_progress.latest_number.connect(self.update_number)
@pyqtSlot(str)
def update_number(self, val):
x = str(val)
self.number.setText("Current block: " + x)
class Updater(QRunnable):
def __init__(self):
super(Updater, self).__init__()
self.update_progress = WorkerSignal()
print("started")
def run(self):
self.timer = QTimer()
self.timer.setInterval(2000)
self.timer.timeout.connect(self.update_label)
self.timer.start()
def update_label(self):
provider = configfile.provider
number = str(nmbr.latest_numer(provider))
self.update_progress.latest_number.emit(number)
print("update label started")
After tyring to understand your script, I reached the conclusion that QRunnable
is automatically released (or stopped) once Updater.run(self)
reaches its end.
But this is supposed to happen, because that's why you should use a QThreadPool
in the first place. It is supposed to recycle expired threads in the background of the application.
So, as you're running a QTimer
in the thread, QThreadPool
thinks that Updater
thread is dead, while in reality, the timer is still running on the background of a background thread. So it does its job and releases Updater
from the Thread pool.
What you must do in order to keep both the Timer and Updater
alive is to manager the Updater
thread's lifecycle yourself.
So instead of using QThreadPool
and QRunnable
, you must go one level lower in terms of abstraction, and use the QThread
and QObject
themselves in order to control when the thread is going to be stopped.
from PySide2 import QtWidgets, QtCore
from PySide2.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
from PySide2.QtCore import Qt, QThreadPool, QRunnable, Slot, Signal, QObject, QTimer, QThread
class WorkerSignal(QObject):
# Changed pyqtSignal to Signal
latest_number = Signal(str)
class Worker(QObject):
def __init__(self):
QObject.__init__(self)
self.update_progress = WorkerSignal()
def update_label(self):
print("update label started")
try:
provider = configfile.provider
number = str(nmbr.latest_numer(provider))
self.update_progress.latest_number.emit(number)
except:
print("Error, but just for testing...")
self.update_progress.latest_number.emit("0")
def main(self):
print('Init worker')
self.timer = QTimer()
self.timer.setInterval(2000)
self.timer.timeout.connect(self.update_label)
self.thread().finished.connect(self.timer.stop)
self.timer.start()
class Updater(QThread):
def __init__(self):
QThread.__init__(self)
self.worker = Worker()
# Move worker to another thread, to execute in parallel
self.worker.moveToThread(self)
# Set which method from Worker that should excute on the other thread.
# In this case: Worker.main
self.started.connect(self.worker.main)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
QMainWindow.__init__(self, *args, **kwargs)
self.setObjectName("MainWindow")
self.resize(1300, 768)
self.setMinimumSize(1300,768)
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("SniperBot", "SniperBot"))
self.number = QtWidgets.QLabel(self)
self.number.setGeometry(QtCore.QRect(1100, 10, 300, 20))
self.number.setObjectName("number")
# I did this to see the label, as it was not attached to any
# widget's layout on your script.
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
layout.addWidget(self.number)
self.setCentralWidget(widget)
self.scene = widget
self.show()
# Create the Updater thread.
self.updater = Updater()
# Connect the Worker's signal to self.update_number
self.updater.worker.update_progress.latest_number.connect(self.update_number)
# Start the thread
self.updater.start()
def closeEvent(self, evt):
# Before exiting from the application, remember, we control now
# the Updater Thread's lifecycle. So it's our job to stop the thread
# and wait for it to finish properly.
self.updater.quit()
self.updater.wait()
# Accept the event. Exit the application.
evt.accept()
# Changed pyqtSlot to Slot
@Slot(str)
def update_number(self, val):
x = str(val)
print("UPDATE NUMBER: ", x)
self.number.setText("Current block: " + x)
if __name__ == '__main__':
app = QApplication()
win = MainWindow()
win.show()
app.exec_()
What you can do now is follow this pattern and start implementing from here. There are many tutorials that use the moveToThread
method. Just be careful to not let the thread or any object be recycled by the python's garbage collector, in order to avoid many other problems.
The script is working, however I use PySide2 instead of PyQt5. So some variable and modules names may change, so just rename them when you try to run the script on your end.
There were a few errors on Worker.update_label(self)
, which is just a copied script from the old Updater.update_label(self)
. I don't know what are the variables: configfile
or nmbr
as they are not initialized on your post. So I made a try-except
block to handle the error and make the script work just for testing.