Python pyqt6 window blocks main loop

Question:

I have a small program that does something, but i want to "switch modes", for now i press a key and an input prompts on the console, but to make it easier i want to make a window with pyqt6, the problem is that the window blocks or halts the main loop while it’s open, i tried with threading/multiprocessing but i can’t make it work.

import threading
from queue import Queue

from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import Qt

queue = Queue()

class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        layout = QVBoxLayout()
        label = QLabel("Change modes")
        btn1 = QPushButton("MODE 1")
        btn2 = QPushButton("MODE 2")
        layout.addWidget(label)
        layout.addWidget(btn1)
        layout.addWidget(btn2)    
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        btn1.clicked.connect(self.mode1)
        btn2.clicked.connect(self.mode2)
        self.show()

    def mode1(self):
        queue.put("mode1")

    def mode2(self):
        queue.put("mode2")


if __name__ == '__main__':

    app = QApplication()
    window = MainWindow()
    app.exec()

    mode = "none"

    while True:

        _mode = queue.get()
        if mode != _mode:
            mode = _mode;
            print(f"mode: {mode}")

        # do stuff here

the only way that the while loop executes is when i close the window.

Asked By: kernelpanic

||

Answers:

Traditional Python multiprocessing/multithreading libraries such as multiprocessing and threading do not work well with Qt-like (PyQt and PySide) graphical programs. Fortunately, among other solutions, PySide provides the QThread interface, allowing multithreading in PySide graphical interfaces. It can be applied to your program as follows:

import threading
from queue import Queue

from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import Qt, QThread

queue = Queue()

class Worker(QThread):
    def __init__(self):
        super(Worker, self).__init__()

    def run(self):
        mode = "none"

        while True:

            _mode = queue.get()
            if mode != _mode:
                mode = _mode;
                print(f"mode: {mode}")

            # do stuff here


class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        layout = QVBoxLayout()
        label = QLabel("Change modes")
        btn1 = QPushButton("MODE 1")
        btn2 = QPushButton("MODE 2")
        layout.addWidget(label)
        layout.addWidget(btn1)
        layout.addWidget(btn2)
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        btn1.clicked.connect(self.mode1)
        btn2.clicked.connect(self.mode2)
        self.show()

        self.worker = Worker()  # Create a Worker instance
        self.worker.start()  # Start the Worker instance (which calls the run function of the Worker instance)

    def mode1(self):
        queue.put("mode1")

    def mode2(self):
        queue.put("mode2")

    def closeEvent(self, event):
        self.worker.terminate()  # When the window closes, stop the thread

if __name__ == '__main__':

    app = QApplication()
    window = MainWindow()
    app.exec()

Please note the changed import statement of PySide6.QtCore (to import QThread), the addition of the self.worker variable in the __init__ function of the MainWindow class (to actually start the thread), as well as the addition of a closeEvent function in the MainWindow class (to terminate the thread when the window closes).

Answered By: Daniel M