0

I want to automatically update the current time in Qstatusbar. I used mainUI thread for displaying time and subthreads for other long-running tasks. This is working fine.

Now, I would like to use the subthread for displaying time. As shown below, when I click on button 3, the time is frozen while clicking on buttons 1 and button 2 the time is running.

import time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class WorkerSignals(QObject):
    finished = pyqtSignal()
    result = pyqtSignal(object)

class Worker(QRunnable):
    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()
    def run(self):
        result = self.fn(*self.args, **self.kwargs)
        self.signals.result.emit(result)
        self.signals.finished.emit()

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.threadpool = QThreadPool()
        self.set_main_layout()
        self.show_current_time()

    def set_main_layout(self):
        self.layout = QVBoxLayout()

        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)

        self.btn1 = QPushButton('Sleep with thread')
        self.btn1.clicked.connect(self.btn1_onclick)

        self.btn2 = QPushButton('Sum with thread')
        self.btn2.clicked.connect(self.btn2_onclick)

        self.btn3 = QPushButton('Sleep without thread')
        self.btn3.clicked.connect(self.btn3_onclick)

        self.layout.addWidget(self.btn1)
        self.layout.addWidget(self.btn2)
        self.layout.addWidget(self.btn3)

        w = QWidget()
        w.setLayout(self.layout)
        self.setCentralWidget(w)

        self.status_bar = self.statusBar()
        self.status_bar.showMessage('Ready')
        self.time = QLabel()
        self.status_bar.addPermanentWidget(self.time)
        self.setStatusBar(self.status_bar)

    def show_current_time(self):
        self.timer = QTimer()
        self.timer.setInterval(1000)
        self.timer.timeout.connect(lambda: self.time.setText(QTime.currentTime().toString("hh:mm:ss")))
        self.timer.start()

    def btn1_onclick(self):
        worker = Worker(self.btn1_fn)
        worker.signals.result.connect(self.btn1_result)
        worker.signals.finished.connect(lambda: print('Btn1 finished...'))
        self.threadpool.start(worker)

    def btn1_fn(self):
        i = 0
        while i < 5:
            print(f'Btn1 sleeping...{i}')
            time.sleep(1)
            i += 1
        return i

    def btn1_result(self,i):
        pass

    def btn2_onclick(self):
        worker = Worker(self.btn2_fn)
        worker.signals.result.connect(self.btn2_result)
        worker.signals.finished.connect(lambda: print('Btn2 finished...'))
        self.threadpool.start(worker)

    def btn2_fn(self):
        sum=0
        print('Btn2 summing...')
        for i in range(50000000):
            sum+=1
        return sum

    def btn2_result(self, num):
        print(f'Btn2 sum: {num}')

    def btn3_onclick(self):
        i=0
        while i<5:
            print(f'Btn3 sleeping...{i}')
            time.sleep(1)
            i+=1

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec_()
Tam Phan
  • 1
  • 2

1 Answers1

0

In Qt (and in most other gui frameworks), all gui widgets live in same thread (named gui thread). You can not freeze just one button like you try to do in btn3_onclick. Calling time.sleep(1) from gui thread (like you did) freezes whole gui thread which means it freezes all windows and all widgets.

You dont need thread to periodically update ui, just use timer. QTimer doesn't use thread it just schedules event to eventloop.

timer = QtCore.QTimer()
timer.timeout.connect(lambda: ui.label.setText(datetime.datetime.now().strftime("%H:%M:%S"))
timer.start(1000)
mugiseyebrows
  • 4,138
  • 1
  • 14
  • 15
  • does QtCore.QTimer() lives in another thread ?? – pippo1980 Mar 18 '22 at 17:38
  • nope : https://stackoverflow.com/questions/42279360/does-a-qtimer-object-run-in-a-separate-thread-what-is-its-mechanism Does a QTimer object run in a separate thread? What is its mechanism? – pippo1980 Mar 18 '22 at 17:40
  • 1
    @pippo1980 it does if it's created and started *from* the other thread, and as long as the thread is not blocked by a function that doesn't allow its own event loop to process events. But, obviously, if the timer is in another thread, no direct access to UI is allowed (so the above code won't work), and signals must be used instead, as usual. – musicamante Mar 18 '22 at 20:01