Python pyqt6 window blocks main loop - python

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.

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).

Related

Why is my gif appearing only after my function finishes running [duplicate]

I have got this problem. I´m trying to set text on a lineEdit object on pyqt4, then wait for a few seconds and changing the text of the same lineEdit. For this I´m using the time.sleep() function given on the python Time module. But my problem is that instead of setting the text, then waiting and finally rewrite the text on the lineEdit, it just waits the time it´s supposed to sleep and only shows the final text. My code is as follows:
from PyQt4 import QtGui
from gui import *
class Ventana(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
self.button.clicked.connect(self.testSleep)
def testSleep(self):
import time
self.lineEdit.setText('Start')
time.sleep(2)
self.lineEdit.setText('Stop')
def mainLoop(self, app ):
sys.exit( app.exec_())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Ventana()
window.show()
sys.exit(app.exec_())
You can't use time.sleep here because that freezes the GUI thread, so the GUI will be completely frozen during this time.
You should probably use a QTimer and use it's timeout signal to schedule a signal for deferred delivery, or it's singleShot method.
For example (adapted your code to make it run without dependencies):
from PyQt4 import QtGui, QtCore
class Ventana(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setLayout(QtGui.QVBoxLayout())
self.lineEdit = QtGui.QLineEdit(self)
self.button = QtGui.QPushButton('clickme', self)
self.layout().addWidget(self.lineEdit)
self.layout().addWidget(self.button)
self.button.clicked.connect(self.testSleep)
def testSleep(self):
self.lineEdit.setText('Start')
QtCore.QTimer.singleShot(2000, lambda: self.lineEdit.setText('End'))
def mainLoop(self, app ):
sys.exit( app.exec_())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Ventana()
window.show()
sys.exit(app.exec_())
Also, take a look at the QThread sleep() function, it puts the current thread to sleep and allows other threads to run. https://doc.qt.io/qt-5/qthread.html#sleep
You can't use time.sleep here because that freezes the GUI thread, so the GUI will be completely frozen during this time.You can use QtTest module rather than time.sleep().
from PyQt4 import QtTest
QtTest.QTest.qWait(msecs)
So your code should look like:
from PyQt4 import QtGui,QtTest
from gui import *
class Ventana(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
self.button.clicked.connect(self.testSleep)
def testSleep(self):
import time
self.lineEdit.setText('Start')
QtTest.QTest.qWait(2000)
self.lineEdit.setText('Stop')
def mainLoop(self, app ):
sys.exit( app.exec_())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Ventana()
window.show()
sys.exit(app.exec_())

App crashes after closing multiple QMessageBoxes

i have an odd problem with the GUI i am working on with Python using PyQt5.
The GUI has a single button that calls a class method which executes a Worker QThread.
In the Worker's run()-method I emit two consecutive signals that call the main GUIs show_messagebox()-method which displays a messagebox containing the information from the signal.
Displaying multiple QMessageBoxes works fine, but after closing the last one, the whole program including the main GUI exits/crashes. Unfortunately I don't get an error message.
Calling self.show_messagebox.emit() only once in the run()-method works as intended and doesn't close the whole program.
I assume that for some reason the program thinks that the last remaining QMessageBox (the first one that gets emitted) becomes the parent and after closing it the program is done?
Unfortunately I haven't found a solution on how to solve this issue, which is why I decided to ask.
Here is the full source code:
import sys
from PyQt5.QtCore import pyqtSignal, QObject, QThread
from PyQt5.QtWidgets import QPushButton, QVBoxLayout, QMainWindow, QWidget, QMessageBox
from PyQt5 import QtWidgets
class Worker(QObject):
finished = pyqtSignal()
show_messagebox = pyqtSignal(str, str)
def run(self):
self.show_messagebox.emit("Test1", "Test Text 1")
self.show_messagebox.emit("Test2", "Test Text 2")
self.finished.emit()
class WindowMain(QMainWindow):
def __init__(self):
super(WindowMain, self).__init__()
layout = QVBoxLayout()
self.button = QPushButton("Start")
self.messagebox = QMessageBox()
layout.addWidget(self.button)
self.button.clicked.connect(self.execute)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.show()
def execute(self):
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.worker.show_messagebox.connect(self.show_messagebox)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
def show_messagebox(self, title, text, icon=QMessageBox.Information):
messagebox = QMessageBox()
messagebox.setWindowTitle(title)
messagebox.setText(text)
messagebox.setIcon(icon)
messagebox.exec_()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = WindowMain()
window.show()
sys.exit(app.exec_())
Thanks in advance for any help!

Terminate QThread when GUI exits

I am using QThreads to run a function in the background, but when I exit the GUI application, the QThread still continues to run.
There are examples about C++ but I do not know how to implement them in python
class PF35Thread(QtCore.QThread):
signalPF35 = pyqtSignal()
def __init__(self, parent = None):
super().__init__(parent)
def run(self):
newcase = newcaseList[-1]
os.system('EAZ{0}(3,5).EAZ{0}(3,5).OUT'.format(newcase))
self.signalPF35.emit()
How do I terminate QThread when GUI closes?
I think that in your case the thread is only necessary because os.system() is blocking, but if you use QProcess it does a similar task and you do not need the thread, it also allows easier management with the Qt eventloop.
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
signalPF35 = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
button = QtWidgets.QPushButton("Start task", clicked=self.on_clicked)
self.setCentralWidget(button)
self._process = QtCore.QProcess(self)
self._process.finished.connect(self.on_finished)
#QtCore.pyqtSlot()
def on_clicked(self):
newcase = newcaseList[-1]
self._process.start('EAZ{0}(3,5).EAZ{0}(3,5).OUT'.format(newcase))
#QtCore.pyqtSlot()
def on_finished(self):
self.signalPF35.emit()
def closeEvent(self, event):
self._process.kill()
super(MainWindow, self).closeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

PySide2 Non-Blocking QMessageBox doesn't show text

While my application does some time-consuming stuff, I want to display a message box to the user while the applications is busy. I don't want any buttons (like OK or Cancel) and I can't call exec_() on the message box because that is blocking.
I checked a number of qt sites and the code I need seems to boil down to:
message_box = QMessageBox()
message_box.setText(str('Reading Device, Please Wait...'))
message_box.show()
# do work here
message_box.close()
When I run the code, I get the message box, but without the text. What am I doing wrong?
I've included a working example below:
#!/usr/bin/env python3
import sys
import time
from PySide2.QtWidgets import (QLineEdit, QPushButton, QApplication,
QVBoxLayout, QDialog, QMessageBox)
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.button = QPushButton("Click Me")
layout = QVBoxLayout()
layout.addWidget(self.button)
self.setLayout(layout)
# Add button signal to dowork slot
self.button.clicked.connect(self.dowork)
def dowork(self):
message_box = QMessageBox()
message_box.setText(str('Reading Device, Please Wait...'))
message_box.show()
delay = 2.5
while delay:
sys.stdout.write('Working...\n')
time.sleep(0.5) # do some time-consuming stuff...
delay -= 0.5
message_box.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
form = Form()
print('starting app...')
form.show()
sys.exit(app.exec_())
If you click the button, the message box pops up and is shown while the 'work' is being done. When the 'work' is finished, the message box disappears again - as it should. But no text is shown in the message box.
There is a similar question here: qmessagebox-not-show-text-when-call-show, but that does not answer my question.
You can not have a task that consumes a lot of time (more than 30 ms) since it blocks the GUI eventloop preventing Qt from doing its job normally, instead it uses a thread next to the signals to update the GUI from the other thread:
import sys
import threading
import time
from PySide2 import QtCore, QtWidgets
class Form(QtWidgets.QDialog):
started = QtCore.Signal()
finished = QtCore.Signal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.button = QtWidgets.QPushButton("Click Me")
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
# Add button signal to dowork slot
self.button.clicked.connect(self.on_clicled)
self._message_box = QtWidgets.QMessageBox()
self._message_box.setText(str('Reading Device, Please Wait...'))
self._message_box.setStandardButtons(QtWidgets.QMessageBox.NoButton)
self.started.connect(self._message_box.show)
self.finished.connect(self._message_box.accept)
#QtCore.Slot()
def on_clicled(self):
thread = threading.Thread(target=self.dowork, daemon=True)
thread.start()
def dowork(self):
delay = 2.5
self.started.emit()
while delay:
sys.stdout.write('Working...\n')
time.sleep(0.5) # do some time-consuming stuff...
delay -= 0.5
self.finished.emit()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
form = Form()
print('starting app...')
form.show()
sys.exit(app.exec_())
You should almost certainly be using QProgressDialog. Set minimum and maximum to zero for an 'indeterminate' look.
https://doc.qt.io/qt-5/qprogressdialog.html
Although forcing a redraw after your setText() will probably work:
self._message_box.repaint()
QtWidgets.QApplication.processEvents()

PyQt application freezes if dialog rejected

I have a small application, that requires login before it starts.
But if user rejects login(press cancel button), application won't close, it's just freeze.
Here is the simplified code:
import sys
from PyQt5 import QtWidgets, QtCore
class LoginWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(LoginWindow, self).__init__(parent)
self.resize(250, 150)
self.move(500, 500)
self.setWindowTitle('Login')
self.login_input = QtWidgets.QLineEdit(self)
self.login_input.move(10, 10)
self.password_input = QtWidgets.QLineEdit(self)
self.password_input.move(10, 50)
self.password_input.setEchoMode(QtWidgets.QLineEdit.Password)
self.button_box = QtWidgets.QDialogButtonBox(self)
self.button_box.move(10, 80)
self.button_box.setOrientation(QtCore.Qt.Horizontal)
self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel |
QtWidgets.QDialogButtonBox.Ok)
self.button_box.accepted.connect(self.login)
self.button_box.rejected.connect(self.reject)
def login(self):
self.accept()
def cancel(self):
self.reject()
class MainWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(250, 150)
self.move(500, 500)
self.setWindowTitle('Main')
self.input = QtWidgets.QLineEdit(self)
self.input.move(10, 10)
self.show()
def main():
app = QtWidgets.QApplication([])
l = LoginWindow()
l.show()
login_result = l.exec_()
print(login_result)
if login_result == QtWidgets.QDialog.Accepted:
m = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
What am I doing wrong?
I use python 3 and PyQt5
This happens because PySide has not processed any of it's events.
app.exec_()
This starts the main event loop that continually processes every GUI interaction. This should be called before you execute any GUI code, so the events can be processed correctly from the event Queue.
The QDialog.exec_() is a blocking operation preventing the code from continuing until it gets a response.
If you want to see the dialog items then you may be able to get around this.
QtGui.QApplication.processEvents()
This processes all of the events in the event Queue, so you would probably have to keep calling this method.
Also after you initialize your main window you will have to show the main window.
I find a way to avoid this bug:
I've changed main function:
def main():
app = QtWidgets.QApplication([])
if LoginWindow().exec_() == QtWidgets.QDialog.Accepted:
m = MainWindow()
sys.exit(app.exec_())
And it works normal, but is still can't understand, what was the root cause of the problem

Categories

Resources