PyQt5. Controlling GUI from separate thread

Viewed 26

I want to produce simple GUI, a label and a push button. When I press the push button it should invoke a separate thread (the GUI unfreezes ). In the thread I have simple loop

for i in range(4):
    self.label.setText(str(i))
    time.sleep(1)

the loop must change the label text each second. So I expect to see the GUI how the label changes 0, 1, 2, 3 each second. This is my code:

    self.pushButton.clicked.connect(self.clicked_1)
    self.label.setText("Lubo1")

def clicked_1(self): 
    for i in range(4):
        self.label.setText(str(i))
        app.processEvents()

        print("Sleep!")
        time.sleep(1)
        print("Weak up!")
x = threading.Thread(target=clicked_1, args=(1,))
x.start()

The code works exactly as I expect when I comment x = threading.Thread(target=clicked_1, args=(1,)) and uncomment app.processEvents().

However when app.processEvents() is commented and x = threading.Thread(target=clicked_1, args=(1,)) is uncommented the code gives an error:

AttributeError: 'int' object has no attribute 'label'

I don't want to use app.processEvents() since I know this is not the right way. The correct way is to use threading, but I don't know how to overcome the AttributeError: 'int' object has no attribute 'label' error. Please help.

Best Regards, thank you in advance.

Lubomir Valkov

1 Answers

You should never do such a process, Qt does not support gui operations of any kind outside the main thread. Sooner or later, if you call your GUI out of your main thread your app will crash because it calls methods which are not thread-safe. You should always use signals and slots to communicate between threads and main gui.

# your thread class
class ThreadClass(QtCore.QThread):

    any_signal = QtCore.pyqtSignal(int)

    def __init__(self, time_to_wait,  parent=None):
        super(ThreadClass, self).__init__(parent)
        self.time_wait = time_to_wait

    def run(self):
        for val in range(self.time_wait):
            time.sleep(1) 
            self.any_signal.emit(val+1)

your GUI

    self.pushButton.clicked.connect(self.clicked_1)
    self.label.setText("Lubo1")

    def clicked_1(self):
       self.thread = ThreadClass(time_to_wait=4, parent=None)
       self.thread.any_signal.connect(self.update_label)
       self.thread.start()
    
   def update_label(self, value):
       self.label.setText(str(i))

As you can see you your button start a thread that emit a signal. Your gui captures this signal and do what ever it need to do in the main GUI, but your thread is never changing anything of the main thread

Related