is there a way to detect if user trying to close window?
For example, in Tkinter we can do something like this:
def exit_dialog():
#do stuff
pass
root = Tk()
root.protocol("WM_DELETE_WINDOW", exit_dialog)
root.mainloop()
Thanks.
Override the closeEvent method of QWidget in your main window.
For example:
class MainWindow(QWidget): # or QMainWindow
...
def closeEvent(self, event):
# do stuff
if can_exit:
event.accept() # let the window close
else:
event.ignore()
Another possibility is to use the QApplication's aboutToQuit signal like this:
app = QApplication(sys.argv)
app.aboutToQuit.connect(myExitHandler) # myExitHandler is a callable
If you just have one window (i.e the last window) you want to detect then you can use the setQuitOnLastWindowClosed static function and the lastWindowClosed signal.
from PySide2 import QtGui
import sys
def keep_alive():
print("ah..ah..ah..ah...staying alive...staying alive")
window.setVisibility(QtGui.QWindow.Minimized)
if __name__ == '__main__':
app = QtGui.QGuiApplication()
app.setQuitOnLastWindowClosed(False)
app.lastWindowClosed.connect(keep_alive)
window = QtGui.QWindow()
window.show()
sys.exit(app.exec_())
Hope that helps someone, it worked for me as on my first attempt I couldn't override closeEvent(), lack of experience!
Related
I want to add a custom button to QMessagebox that opens up a matplotlib window, along with an Ok button for user to click when they want to close it
I currently have it somewhat working, but I want the two buttons to do separate things and not open the window.
I know I can just create a dialog window with the desired results, but I wanted to know how to with a QMessageBox.
import sys
from PyQt5 import QtCore, QtWidgets
def main():
app = QtWidgets.QApplication(sys.argv)
msgbox = QtWidgets.QMessageBox()
msgbox.setWindowTitle("Information")
msgbox.setText('Test')
msgbox.addButton(QtWidgets.QMessageBox.Ok)
msgbox.addButton('View Graphs', QtWidgets.QMessageBox.YesRole)
bttn = msgbox.exec_()
if bttn:
print("Ok")
else:
print("View Graphs")
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Desired result:
Ok button - closes QMessageBox
View Graph button - opens matplotlib window and keeps QMessageBox open until user clicks Ok
A bit hacky IMO, but after you add the View Graphs button you could disconnect its clicked signal and reconnect it to your slot of choice, e.g.
import sys
from PyQt5 import QtCore, QtWidgets
def show_graph():
print('Show Graph')
def main():
app = QtWidgets.QApplication(sys.argv)
msgbox = QtWidgets.QMessageBox()
msgbox.setWindowTitle("Information")
msgbox.setText('Test')
msgbox.addButton(QtWidgets.QMessageBox.Ok)
yes_button = msgbox.addButton('View Graphs', QtWidgets.QMessageBox.YesRole)
yes_button.clicked.disconnect()
yes_button.clicked.connect(show_graph)
bttn = msgbox.exec_()
if bttn:
print("Ok")
sys.exit(app.exec_())
if __name__ == "__main__":
main()
A QMessageBox, as all QDialogs, blocks everything until exec_() is returned, but it also automatically connects all buttons to either accepted/rejected signals, returning the exec_() in any case.
A possible solution for your code is:
app = QtWidgets.QApplication(sys.argv)
msgbox = QtWidgets.QMessageBox()
# the following is if you need to interact with the other window
msgbox.setWindowModality(QtCore.Qt.NonModal)
msgbox.addButton(msgbox.Ok)
viewGraphButton = msgbox.addButton('View Graphs', msgbox.ActionRole)
# disconnect the clicked signal from the slots QMessageBox automatically sets
viewGraphButton.clicked.disconnect()
# this is just an example, replace with your matplotlib widget/window
graphWidget = QtWidgets.QWidget()
viewGraphButton.clicked.connect(graphWidget.show)
msgbox.button(msgbox.Ok).clicked.connect(graphWidget.close)
# do not use msgbox.exec_() or it will reset the window modality
msgbox.show()
sys.exit(app.exec_())
That said, be careful in using QDialog.exec_() outside (as in "before") the sys.exit(app.exec_()) call, as it might result in unexpected behavior if you don't know what you are doing.
Okay well first you do not use anything in QtCore so no need to import that. This should help you understand what you need to do. I tweaked it a smidge and I had to add the 2 sys.exits or when you pressed View Graphs the program just hung due to how you have this currently set up. If you do not choose to adjust this code flow then pull the sys.exit out of the if/else and put it right after the if/else -- no sense having unnecessary redundant code
from sys import exit as sysExit
from PyQt5.QtWidgets import QApplication, QMessageBox
def Main():
msgbox = QMessageBox()
msgbox.setWindowTitle("Information")
msgbox.setText('Test')
msgbox.addButton(QMessageBox.Ok)
msgbox.addButton('View Graphs', QMessageBox.YesRole)
bttn = msgbox.exec_()
if bttn == QMessageBox.Ok:
print("Ok")
sysExit()
else:
print("View Graphs")
sysExit()
if __name__ == "__main__":
MainThred = QApplication([])
MainApp = Main()
sysExit(MainThred.exec_())
Aka your non-redundant if/else would look as follows
if bttn == QMessageBox.Ok:
print("Ok")
else:
print("View Graphs")
sysExit()
I was trying to analyse the sample code cited here: PyQt - QMessageBox
Here's the snippet:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Window(QMainWindow):
def __init__(self):
super().__init__()
w = QWidget()
b = QPushButton(self)
b.setText("Show message!")
b.clicked.connect(self.showdialog)
w.setWindowTitle("PyQt Dialog demo")
def showdialog(self):
msg = QMessageBox()
msg.setIcon(QMessageBox.Question)
# self.connect(msg, SIGNAL('clicked()'), self.msgbtn)
msg.buttonClicked.connect(self.msgbtn)
msg.exec_()
def msgbtn(self, i):
print("Button pressed is:", i.text())
if __name__ == '__main__':
app = QApplication([])
w = Window()
w.show()
app.exec_()
There are two ways of connecting signals to slots in PyQt. For buttons, it's:
QtCore.QObject.connect(button, QtCore.SIGNAL(“clicked()”), slot_function)
or
widget.clicked.connect(slot_function)
Using it the second way works fine: the msgbtn slot method is called as intended. However, if I try to change it to the more usual, 'PyQt-onic' way of connecting (i.e. the first one - I commented it out in the snippet), the slot method is never called. Could anyone please help me out with this?
the signal you pass to SIGNAL is incorrect, the QMessageBox does not have the clicked signal but the signal is buttonClicked (QAbstractButton *) so the correct thing is:
self.connect(msg, SIGNAL("buttonClicked(QAbstractButton *)"), self.msgbtn)
On the other hand that is not the PyQt-onic style, but the old style which is not recommended to use, but we recommend using the new style.
Old style:
self.connect(msg, SIGNAL("buttonClicked(QAbstractButton *)"), self.msgbtn)
New style:
msg.buttonClicked.connect(self.msgbtn)
For more detail read the docs.
I want to move a QtWidgets.QtWidget using the mouse (not a QPushButton, QLabel etc.). I've searched everywhere on the web, but couldn't find an answer for this. mousePressEvent seemed to be the way, but it doesn't work.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_hGUI(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
def setupUi(self, hGUI):
hGUI.setObjectName("hGUI")
hGUI.resize(161, 172)
hGUI.setMinimumSize(QtCore.QSize(200, 200))
hGUI.setMaximumSize(QtCore.QSize(200, 200))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
hGUI = QtWidgets.QWidget()
ui = Ui_hGUI()
ui.setupUi(hGUI)
hGUI.show()
sys.exit(app.exec_())
I'm using Python 3.5, I'm creating the GUI using Qt Designer, then translate it to python code.
Edit: I'm trying to move a borderless windows by click on it.
That's a really simple question sir,
Let's say you just have to have an variable that holds the position of your widget and interact with it according to your needs.
This position variable let's call it "oldPos".
Now inside your mouse press you update this position.
By the last but not least, you relate your "oldPos" and your mouseMove actual position and move your widget.
Wallahhhh, here we have a beautiful and simple movable widget by mouse events.
Here is the simplest example.
from PyQt5.QtWidgets import QWidget
class MyMovableWidget(QWidget):
"""WToolBar is a personalized toolbar."""
homeAction = None
oldPos = QPoint()
def __init__(self):
super().__init__()
def mousePressEvent(self, evt):
"""Select the toolbar."""
self.oldPos = evt.globalPos()
def mouseMoveEvent(self, evt):
"""Move the toolbar with mouse iteration."""
delta = QPoint(evt.globalPos() - self.oldPos)
self.move(self.x() + delta.x(), self.y() + delta.y())
self.oldPos = evt.globalPos()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
coolWidget = MyMovableWidget()
coolWidget.show()
sys.exit(app.exec_())
So simple isn't it? :D
I managed to make it work, thanks to #bnaecker for telling me that the code actually creates two widgets, I've replaced some stuff in my code. Basically, just edit the code generated when you translate the .ui to .py so it would only create one widget.
The most changes happened here:
if __name__ == "__main__":
import sys
sys.excepthook = excepthook
app = QtWidgets.QApplication(sys.argv)
hGUI = QtWidgets.QWidget(flags=QtCore.Qt.FramelessWindowHint)
ui = Ui_hGUI()
ui.setupUi(hGUI)
hGUI.show()
sys.exit(app.exec_())
Edited to this:
if __name__ == "__main__":
sys.excepthook = excepthook
app = QtWidgets.QApplication(sys.argv)
hGUI = Ui_hGUI()
sys.exit(app.exec_())
Add self.show() at the end of retranslateUi(self), replace every "hGUI" in the code with "self" or delete it if it's an argument (except for controls like buttons and labels).
Here are both codes, non-working one vs. working one: https://gist.github.com/anonymous/0707b4fef11ae4b31cf56dc78dd3af80
Note: In the new code, the app is called "VirtualMemories".
I am having problems showing a QWidget window for the user to input some data.
My script has not GUI, but I just want to show this small QWidget window.
I created the window with QtDesigner, and now I am trying to show the QWidget window like this:
from PyQt4 import QtGui
from input_data_window import Ui_Form
class childInputData(QtGui.QWidget ):
def __init__(self, parent=None):
super(childInputData, self).__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
self.setFocus(True)
self.show()
And then, from my main class, I am doing like that:
class myMainClass():
childWindow = childInputData()
That gave me the error:
QWidget: Must construct a QApplication before a QPaintDevice
So now I am doing, from my main class:
class myMainClass():
app = QtGui.QApplication(sys.argv)
childWindow = childInputData()
Now there is no error, but the window is showed twice and the script does not wait until the data is entered, it just shows the window and continues without waiting.
What is wrong here?
It's perfectly normal that the window is shown and the script goes on: you never told the script to wait for the user to answer. You just told it to show a window.
What you would like is the script to stop until the user is done and the window is closed.
Here's one way to do it:
from PyQt4 import QtGui,QtCore
import sys
class childInputData(QtGui.QWidget):
def __init__(self, parent=None):
super(childInputData, self).__init__()
self.show()
class mainClass():
def __init__(self):
app=QtGui.QApplication(sys.argv)
win=childInputData()
print("this will print even if the window is not closed")
app.exec_()
print("this will be print after the window is closed")
if __name__ == "__main__":
m=mainClass()
The exec() method "Enters the main event loop and waits until exit() is called" (doc):
the script will be blocked on the line app.exec_() until the window is closed.
NB: using sys.exit(app.exec_()) would cause the script to end when the window is closed.
An other way is to use QDialog instead of QWidget. You then replace self.show() by self.exec(), which will block the script
From the doc:
int QDialog::exec()
Shows the dialog as a modal dialog, blocking until the user closes it
Finally, this answer of a related question advocates not to use exec, but to set the window modality with win.setWindowModality(QtCore.Qt.ApplicationModal). However this doesn't work here: it blocks inputs in other windows, but do not block the script.
you dont need the myMainClass...do something like this:
import sys
from PyQt4 import QtGui
from input_data_window import Ui_Form
class childInputData(QtGui.QWidget):
def __init__(self, parent=None):
super(childInputData, self).__init__(parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.setFocus(True)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
win = childInputData()
win.show()
sys.exit(app.exec_())
I need to know how the QDockWidget is normally closed. I have a serial port/thread attached to a QDockWidget, and I need to make sure the thread and serial port close properly.
class SerialDock(QDockWidget):
...
def close(self):
print("Close")
self.serialport.close()
self.thread.close()
def closeEvent(self, event):
print("closeEvent")
self.serialport.close()
self.thread.close()
The close and closeEvents are not called when I click the QMainWindow X button. Do I have to call the close method from the QMainWindow close? The only way I know to solve this is to use the QApplication.aboutToQuit signal, and I really don't want to have to remember to set that for one specific widget. How does the QDockWidget get destroyed or closed?
You can use the destroyed signal in the QDockWidget:
import PyQt4.QtGui as ui
import PyQt4.QtCore as core
app = ui.QApplication([])
mw = ui.QMainWindow()
mw.setCentralWidget(ui.QTextEdit())
dw = ui.QDockWidget("Test",mw)
dw.setWidget(ui.QLabel("Content"))
mw.addDockWidget(core.Qt.RightDockWidgetArea, dw)
def onDestroy(w):
print("Do stuff here")
print(w)
dw.destroyed.connect(onDestroy)
mw.show()
app.exec_()