PyQt application freezes if dialog rejected - python

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

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_())

Python pyqt6 window blocks main loop

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

Opening another window after successful login PyQt5 Python3

I want to open another window upon a successful login but it continuously give this error.
QCoreApplication::exec: The event loop is already running
Here is the code.
LoginWindow - Login Page
MainWindow - Post login page
Is there anyway I can open another window upon a successful login and close the current one.
from PyQt5.QtWidgets import QMainWindow, QApplication
from login_ui import *
import sys
LOGGED_IN = False
TOKEN = ""
class LoginWindow(QMainWindow, Ui_LoginWindow):
GLOBAL_STATE = True
loginPortal = QApplication([])
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.show()
self.handleButtons()
sys.exit(self.loginPortal.exec_())
def handleButtons(self):
self.CloseBtn.clicked.connect(self.close)
self.MinBtn.clicked.connect(self.showMinimized)
self.loginBtn.clicked.connect(self.login)
def login(self):
if self.username.text() == "admin" and self.password.text() == "password":
LOGGED_IN = True
TOKEN = "admin"
elif self.username.text() == "coord" and self.password.text() == "password":
LOGGED_IN = True
TOKEN = "coord"
else:
LOGGED_IN = False
Token = None
if LOGGED_IN:
MainWindow().show()
else:
self.close()
self.loginPortal.quit()
from main_ui import *
class MainWindow(QMainWindow, Ui_MainWindow):
app = QApplication([])
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowFlag(Qt.FramelessWindowHint)
self.showMaximized()
self.handleButtons()
sys.exit(self.app.exec_())
def handleButtons(self):
self.MinBtn.clicked.connect(self.showMinimized)
self.MaxBtn.clicked.connect(self.maximize)
self.CloseBtn.clicked.connect(self.close)
def maximize(self):
if self.GLOBAL_STATE:
self.showNormal()
self.GLOBAL_STATE = False
else:
self.showMaximized()
self.GLOBAL_STATE = True
if __name__ == "__main__":
LoginWindow()
There are two problems with your code:
Only one QApplication instance should exist.
You're creating a QApplication in the class definition.
The second problem is important, as QtWidgets can only be created when a QApplication is created. When python opens a script, it also initializes all the classes in its main indentation, which also means that every function in the base indentation of the class is executed.
In your case, it means that when each class is loaded, each one will try to create a QApplication, since app = QApplication([]) is in their main indentation. As explained in the first point, only one QApplication instance can only exist.
While technically your code could run fine when using a single class (the QApplication is created before actually creating the widget instance), this leads to problems exactly like in your case.
A possible workaround would be to check if a QApplication already exists:
# note: this is just for explanation purposes, do NOT do this unless you
# really know what you're doing
class LoginWindow(QMainWindow, Ui_MainWindow):
if not QApplication.instance():
loginPortal = QApplication([])
# ...
class MainWindow(QMainWindow, Ui_MainWindow):
if not QApplication.instance():
app = QApplication([])
But that's not a good approach anyway, and the reason is that the application should be also executed with exec_() (or exec()). While you're doing it within the __init__ of each widget, that's absolutely not the place for that: exec_() is blocking, which prevents returning the actual instance to the caller until the application quits. Even if you're not actually interested in that, it's not good practice, as the __init__ should always return as soon as possible. Finally, when you try to create the main window instance, it will try to exec the other QApplication instance, and this is the (final) source of your problem.
That's what the common if __name__ == '__main__': is important for: ensuring that only the main program executes what should be actually run, and in case of Qt, to create and run the only QApplication that should ever exist in the program lifetime.
A better approach when dealing with multiple windows is to create a signal and use that to interact with anything else.
This is a possible correct version of your code (be aware of the commented lines)
class LoginWindow(QMainWindow, Ui_LoginWindow):
loggedSignal = pyqtSignal()
GLOBAL_STATE = True
# loginPortal = QApplication([])
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.show()
self.handleButtons()
# sys.exit(self.loginPortal.exec_())
# ...
def login(self):
# ...
if LOGGED_IN:
self.loggedSignal.emit()
else:
QApplication.quit()
self.close()
class MainWindow(QMainWindow, Ui_MainWindow):
# app = QApplication([])
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowFlag(Qt.FramelessWindowHint)
# self.showMaximized()
self.handleButtons()
# sys.exit(self.app.exec_())
if __name__ == '__main__':
app = QApplication(sys.argv)
login = LoginWindow()
mainWindow = MainWindow()
login.loggedSignal.connect(mainWindow.showMaximized)
sys.exit(app.exec_())
Note that you are using the GLOBAL_STATE in the main window, but you defined it in the login window only instead.

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

how to check if PyQt window exists

I have an application in development using PyQt4.
This app will popup a window on particular events in the application.
I want to know if the popup window which was popped up exists on the next event i wan to display the message on the same window rather tan creating another window.
For example you can consider a messaging application. where when we get a message the window will popup. and if we receive the message again from same user the message will be appended to that window itself.
My scenario is also the same.
Anyone have any idea on this...?
All you have to do is keep a reference to the popup window and then reset the text as necessary.
Here's a simple demo:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('ShowTime!', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
self._dialog = None
def handleButton(self):
if self._dialog is None:
self._dialog = QtGui.QMessageBox(self)
self._dialog.setWindowTitle('Messages')
self._dialog.setModal(False)
pos = self.pos()
pos.setX(pos.x() + self.width() + 10)
self._dialog.move(pos)
self._dialog.setText(
'The time is: %s' % QtCore.QTime.currentTime().toString())
self._dialog.show()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Categories

Resources