I am working a tiny PySide6 project where I need to run light-to-heavy calculations. Because of the duration of the calculations (0.1 - 600 sec), I want to display a small window indicating "processing ..." when the calculation time goes beyond 3 sec.
I tried to create a separate QThread where I receive a signal to trigger a response after some delay, from a main window/thread where I run the calculation.
Here is my sample code :
from PySide6.QtWidgets import QWidget, QPushButton, QVBoxLayout, QApplication, QLabel
from PySide6.QtCore import Slot, Signal, QObject, QThread
import time
import sys
class AnotherWindow(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.label = QLabel("Processing ...")
layout.addWidget(self.label)
self.setLayout(layout)
class MyWorker(QObject):
display = Signal()
@Slot()
def display_trigger(self):
print('before delay')
time.sleep(2)
print('delay done')
self.display.emit()
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.thread = None
self.worker = None
self.button = None
self.w = AnotherWindow()
self.init_ui()
self.setup_thread()
def init_ui(self):
layout = QVBoxLayout()
self.button = QPushButton('User input')
self.button.setEnabled(True)
layout.addWidget(self.button)
self.setLayout(layout)
self.show()
@Slot()
def display(self):
print("display it !")
self.w.show()
@Slot()
def processing(self):
print("=========")
for i in range(8):
print(i)
time.sleep(1)
self.w = None
def closeEvent(self, event):
# self.w = None
self.thread.quit()
del self.thread
def setup_thread(self):
self.thread = QThread()
self.worker = MyWorker()
self.worker.moveToThread(self.thread)
self.worker.display.connect(self.display)
self.button.clicked.connect(self.worker.display_trigger)
self.button.clicked.connect(self.processing)
self.thread.start()
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec())
The problem here is that the 'processing' windows does not appear during the calculation, and then an error occurs obviously because it tries to show it despite it was deleted at the end of the processing method. I want to keep this delete because the window is useless once the processing is done, but I don't understand why the widget does not show up when the signal is triggered. It seems that the signal is blocked while the long process is running. Any idea about this blocked signal in main thread ?
After some tuning, I found a solution thank to a "famous online AI" and previous suggestions. I was doing it wrong : I inverted the problem and the threads. In the next solution, the popup window is simply started from main thread, and the long calculation is started from the other thread. That is the good way to do it.
Here is the working code with my final tuning:
import sys
import time
from PySide6 import QtWidgets, QtCore, QtGui
class ProcessingWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setModal(True)
self.setWindowTitle("Processing...")
self.setFixedSize(200, 100)
layout = QtWidgets.QVBoxLayout(self)
self.label = QtWidgets.QLabel("Processing...", self)
self.label.setFont(QtGui.QFont("Arial", 16))
layout.addWidget(self.label, alignment=QtCore.Qt.AlignmentFlag.AlignCenter)
self.setWindowFlags(QtCore.Qt.WindowType.SplashScreen)
def center(self):
# Calculate the center position of the main window
parent_rect = self.parent().geometry()
parent_center = parent_rect.center()
# Calculate the position of the dialog
dialog_rect = self.geometry()
dialog_rect.moveCenter(parent_center)
self.setGeometry(dialog_rect)
class LongProcessThread(QtCore.QThread):
finished = QtCore.Signal()
def __init__(self, parent=None, duration=None):
super().__init__(parent)
self.parent = parent
self.duration = duration
def run(self):
self.parent.simulate_long_process(self.duration)
self.finished.emit()
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Long Process Example")
self.setFixedSize(500, 300)
layout = QtWidgets.QVBoxLayout(self)
self.button = QtWidgets.QPushButton("Run Long Process", self)
layout.addWidget(self.button, alignment=QtCore.Qt.AlignmentFlag.AlignCenter)
self.button.clicked.connect(self.run_long_process)
self.processing_window = None
def run_long_process(self):
self.button.setEnabled(False)
long_process_duration = 10 # seconds
delay_popup = 3000 # milliseconds
long_process_thread = LongProcessThread(self, long_process_duration)
long_process_thread.finished.connect(self.enable_button)
long_process_thread.start()
QtCore.QTimer.singleShot(delay_popup, lambda: self.show_processing_window(long_process_thread))
@staticmethod
def simulate_long_process(duration=None):
print("before")
if duration:
time.sleep(duration) # Simulating a long process
print(f"after {duration} seconds")
def show_processing_window(self, long_process_thread):
if not long_process_thread.isFinished():
self.processing_window = ProcessingWindow(self)
self.processing_window.center()
self.processing_window.show()
def enable_button(self):
if self.processing_window is not None:
self.processing_window.close()
self.button.setEnabled(True)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())