PyQt5 Interrupt Close Event from Outside the Main Gui Module - python

I use Qt Designer to build my GUI's and convert them to py files using pyuic5. My end goal here is to interrupt the user from closing the program when a variable == 1 and present them with an 'are you sure you want to close?' type dialog. If said variable == 0 then just close the program normally.
I have seen lots of examples on how to do this, but all of them require editing the code in the GUI module. I import my gui.py file created by pyuic5 into my main script where I do all my connections to buttons, line edits, etc.. I do this so that at anytime I can update the GUI with Qt Designer and not affect the programs functionality.
Is there a way to do this from my main script that has the GUI module from Qt Designer imported?
Example of how my main script is structured:
import philipsControlGui
import sys
def main():
MainWindow.show()
sys.exit(app.exec_())
def test():
print('test')
# Main window setup
app = philipsControlGui.QtWidgets.QApplication(sys.argv)
MainWindow = philipsControlGui.QtWidgets.QMainWindow()
ui = philipsControlGui.Ui_MainWindow()
ui.setupUi(MainWindow)
# Main window bindings
ui.onButton.clicked.connect(test)
### Can I insert something here to do: if user closes the window... do something else instead?
if __name__ == "__main__":
main()

You should create a subclass from your imported gui so you can reimplement the closeEvent method:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from philipsControlGui import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setUpUi(self)
self.ui.onButton.clicked.connect(self.test)
self._check_close = True
def test(self):
print('test')
self._check_close = not self._check_close
def closeEvent(self, event):
if self._check_close:
result = QtWidgets.QMessageBox.question(
self, 'Confirm Close', 'Are you sure you want to close?',
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

If there's a specific 'ExitButton' in your design, you should be able to connect it in the main code and create a pop up dialog. You would have to import the QtCore/QtGui components. I always write my GUI directly (QtDesigner is pain when it comes to these things) so I'm assuming something like this:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
[YOUR CODE]
ui.ExitButton.clicked.connect(Exit)
def Exit():
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Are you sure you want to close this window?")
msg.setWindowTitle("MessageBox demo")
msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
msg.buttonClicked.connect(msgbtn)
retval = msg.exec_()
print "value of pressed message box button:", retval

Related

Maya wait for Qt window to close

Normally in Qt applications you would use this at the start and end of your code:
app = QtWidgets.QApplication(sys.argv)
...
app.exec_()
But in Maya, you don't use this because Qt runs on the Maya application it self. I'm sure this works the same for many other applications as well if you don't know what Maya is. That said, my code looks like this:
import sys
from PySide2 import QtWidgets, QtGui, QtCore
class Test():
def __init__(self):
self.open_qt()
def open_qt(self):
# app = QtWidgets.QApplication(sys.argv) # Don't need this in Maya
self.window = QtWidgets.QWidget() # I tried QDialog also
btn = QtWidgets.QPushButton("press me")
btn.clicked.connect(self.login)
lay = QtWidgets.QVBoxLayout()
lay.addWidget(btn)
self.window.setLayout(lay)
self.window.show()
# app.exec_() # Don't need this in Maya
def login(self):
print("logged in!")
print("before")
temp = Test()
print("after")
But running this in Maya I get this result:
before
after
logged in!
But I need it to be:
before
logged in!
after
If you run this code outside of Maya (and you use those two commented out lines) then you get the correct result (block above here).
How can I get the Maya Qt to also wait correctly like it would if you used QtWidgets.QApplication(sys.argv)?
A QDialog might be more well suited for your needs, as it runs its own event loop that won't block the program, while waiting for the dialog to be completed.
The important thing to do is to call of its exec_() and call accept() when needed.
from PySide2 import QtWidgets, QtGui, QtCore
class Test(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
layout = QtWidgets.QVBoxLayout(self)
self.spinBox = QtWidgets.QSpinBox()
layout.addWidget(self.spinBox)
btn = QtWidgets.QPushButton("press me")
layout.addWidget(btn)
btn.clicked.connect(self.login)
def login(self):
print("logged in!")
self.accept()
dialog = Test()
if dialog.exec_():
print(dialog.spinBox.value())
I don't have Maya, but according to this answer you can get its main window using the maya.OpenMayaUI module and shiboken's wrapInstance().

Hide current QMainWindow when next QMainWindow is called

I have multiple windows in a Python GUI application using PyQt5.
I need to hide current window when a button is clicked and show the next window.
This works fine from WindowA to WindowB but I get an error while going from WindowB to WindowC.
I know there is some problem in initialization as the initialization code in WindowB is unreachable, but being a beginner with PyQt, i can't figure out the solution.
WindowA code:
from PyQt5 import QtCore, QtGui, QtWidgets
from WindowB import Ui_forWindowB
class Ui_forWindowA(object):
def setupUi(self, WindowA):
# GUI specifications statements here
self.someButton = QtWidgets.QPushButton(self.centralwidget)
self.someButton.clicked.connect(self.OpenWindowB)
# More GUI specifications statements here
def retranslateUi(self, WindowA):
# More statements here
def OpenWindowB(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_forWindowB()
self.ui.setupUi(self.window)
WindowA.hide()
self.window.show()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WindowA = QtWidgets.QMainWindow()
ui = Ui_forWindowA()
ui.setupUi(WindowA)
MainWindow.show()
sys.exit(app.exec_())
WindowB code:
from PyQt5 import QtCore, QtGui, QtWidgets
from WindowB import Ui_forWindowB
class Ui_forWindowB(object):
def setupUi(self, WindowB):
# GUI specifications statements here
self.someButton = QtWidgets.QPushButton(self.centralwidget)
self.someButton.clicked.connect(self.OpenWindowC)
# More GUI specifications statements here
def retranslateUi(self, WindowB):
# More statements here
def OpenWindowB(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_forWindowC()
self.ui.setupUi(self.window)
WindowB.hide() # Error here
self.window.show()
# The below code doesn't get executed when Ui_forWindowB is called from A
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WindowB = QtWidgets.QMainWindow()
ui = Ui_forWindowB()
ui.setupUi(WindowB)
MainWindow.show()
sys.exit(app.exec_())
It works fine from A to B where
WindowA.hide() # Works Properly
While calling WindowC from WindowB
WindowB.hide() # Shows error: name 'WindowB' is not defined
I understand that the initialization isn't done as the "if" statement doesn't get executed.
How to get this working?
I have many more windows to connect in this flow
When you run a Python script, the first file executed will be assigned the name __main__, therefore, if you first execute WindowA the code inside the block if __name__ == "__main__" gets executed and the application is started using WindowA as the main window, similarly if you execute your WindowB script first, the application will be started usingWindowB as the main window.
You cannot start two applications within the same process so you have to choose which one you want to be the main window, all the others will be secondary windows (even if they inherit from QMainWindow).
Nevertheless, you should be able to instantiate new windows from a method in your main window.
As a good practice, you could create a main script to handle the initialization of your application and start an empty main window that will then handle your workflow, also, you may want to wrap your UI classes, specially if they are generated using Qt creator, here is an example:
main.py
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication
from views.main_window import MainWindow
class App(QApplication):
"""Main application wrapper, loads and shows the main window"""
def __init__(self, sys_argv):
super().__init__(sys_argv)
# Show main window
self.main_window = MainWindow()
self.main_window.show()
if __name__ == '__main__':
app = App(sys.argv)
sys.exit(app.exec_())
main_window.py
This is the main window, it doesn't do anything, just control the workflow of the application, i.e. load WindowA, then WindowB etc., notice that I inherit from Ui_MainWindow, by doing so, you can separate the look and feel from the logic and use Qt Creator to generate your UI:
from PyQt5.QtWidgets import QWidget, QMainWindow
from views.window_a import WindowA
from views.window_b import WindowB
from widgets.main_window import Ui_MainWindow
class MainWindow(Ui_MainWindow, QMainWindow):
"""Main application window, handles the workflow of secondary windows"""
def __init__(self):
Ui_MainWindow.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# start hidden
self.hide()
# show window A
self.window_a = WindowA()
self.window_a.actionExit.triggered.connect(self.window_a_closed)
self.window_a.show()
def window_a_closed(self):
# Show window B
self.window_b = WindowB()
self.window_b.actionExit.triggered.connect(self.window_b_closed)
self.window_b.show()
def window_b_closed(self):
#Close the application if window B is closed
self.close()
window_a.py
from PyQt5.QtWidgets import QWidget, QMainWindow
from widgets.main_window import Ui_forWindowA
class WindowA(Ui_forWindowA, QMainWindow):
"""Window A"""
def __init__(self):
Ui_forWindowA.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# Do some stuff
window_b.py
from PyQt5.QtWidgets import QWidget, QMainWindow
from widgets.main_window import Ui_forWindowB
class WindowA(Ui_forWindowB, QMainWindow):
"""Window B"""
def __init__(self):
Ui_forWindowB.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# Do some stuff
Hopefully should give you an idea to get you going.

PyQt QApplication.aboutToQuit()

I'm trying to catch the global about to quit signal from my application class which subclasses QApplication. Here is how I attempt to set it up in main.
def cleanUp():
os.system('rosnode kill -a')
sys.exit(0)
## Start Qt event loop
if __name__ == '__main__':
app = Application(sys.argv)
app.aboutToQuit.connect(cleanUp)
app.exec_()
The issue is that this doesn't seem to catch the signal I have apparently connected.
Edit
I'm using PyQtGraph, so preferably is there some way to catch global window closes?
# The main application
class Application(QtGui.QApplication):
def __init__(self, args):
QtGui.QApplication.__init__(self, args)
self.plot = pg.plot(title="UWB")
self.raw_signal = self.plot.plot()
self.filtered_signal = self.plot.plot()
# Start the main loop
self.listen()
You can also override the close event when your QMainWindow is closed. That might be just as useful depending on your use case.
# override exit event
def closeEvent(self, event):
cleanUp()
# close window
event.accept()
Edit: This minimal example works for me; 'closing' is printed when the plot window is closed.
import pyqtgraph as pg
from PyQt4 import QtGui
import sys
# The main application
class Application(QtGui.QApplication):
def __init__(self, args):
QtGui.QApplication.__init__(self, args)
self.plot = pg.plot(title="UWB")
def cleanUp(self):
print 'closing'
## Start Qt event loop
if __name__ == '__main__':
app = Application(sys.argv)
app.aboutToQuit.connect(app.cleanUp)
sys.exit(app.exec_())

PyQt dialog closes entire app on exit

I have a PyQt wizard that includes a dialog box that asks the user a question. This dialog box is optional and only for use if the user wants it. A button sends a signal that the app receives and opens the window. The problem I have is that when the dialog is closed, it closes the whole app with it. How do I make sure that when the dialog is closed, the main app stays open and running? Here the code that handles the dialog box:
def new_item(self):
app = QtGui.QApplication(sys.argv)
Dialog = QtGui.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.exec_()
I tried adding a 'Cancel' button to close it manually but the result was the same, the whole app closed.
QtCore.QObject.connect(self.cancel, QtCore.SIGNAL(_fromUtf8("clicked()")), Dialog.close)
You shouldn't create new QApplication objects in your code, and I am not surprised that destroying that object closes the application.
Your code should look something like this:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtGui, QtCore
class MyWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.dialog = QtGui.QMessageBox(self)
self.dialog.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
self.dialog.setIcon(QtGui.QMessageBox.Question)
self.dialog.setText("Click on a button to continue.")
self.pushButtonQuestion = QtGui.QPushButton(self)
self.pushButtonQuestion.setText("Open a Dialog!")
self.pushButtonQuestion.clicked.connect(self.on_pushButtonQuestion_clicked)
self.layoutHorizontal = QtGui.QHBoxLayout(self)
self.layoutHorizontal.addWidget(self.pushButtonQuestion)
#QtCore.pyqtSlot()
def on_pushButtonQuestion_clicked(self):
result = self.dialog.exec_()
if result == QtGui.QMessageBox.Ok:
print "Dialog was accepted."
elif result == QtGui.QMessageBox.Cancel:
print "Dialog was rejected."
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.show()
sys.exit(app.exec_())
Try to use Dialog.reject instead of Dialog.close
.close() method is being used mith QMainWindow Widget, .reject() with QDialog.
In my case, I had QSystemTrayIcon as an "entry point" to my app instead of QMainWindow or QWidget.
Calling .setQuitOnLastWindowClosed(False) on my main QApplication instance helped, thanks to this answer

pyqt event trigger on click of minimze button

I've figured out how to trigger an event when the window changes, but this happens when I'm reading window information from the database, and I want to write to the database when the window is reduced, so, I would like to trigger my event based on clicking the reduce button as opposed to just any time the window changes.
The script below works on both Linux and Win XP (and probably OSX, but I can't test it):
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
def changeEvent(self, event):
if event.type() == QtCore.QEvent.WindowStateChange:
if self.windowState() & QtCore.Qt.WindowMinimized:
print('changeEvent: Minimised')
elif event.oldState() & QtCore.Qt.WindowMinimized:
print('changeEvent: Normal/Maximised/FullScreen')
QtGui.QWidget.changeEvent(self, event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(300, 300)
window.show()
sys.exit(app.exec_())
You can use QWidget.hideEvent and check for self.isMinimized(), because hideEvent called when closing window too. Example:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt4.QtGui import *
class HookMinimize(QWidget):
def hideEvent(self, event):
QWidget.hideEvent(self, event)
if self.isMinimized():
print "Doing background task"
if __name__ == '__main__':
app = QApplication(sys.argv)
window = HookMinimize()
window.resize(300, 400)
window.show()
sys.exit(app.exec_())

Categories

Resources