How does PySide/PyQt QMainWindow close a QDockWidget? - python

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

Related

Prevent PySide2 dialog from closing when QRunnable still running

I have a PySide2 application where I'm executing a long running process using QRunnable and I don't want the user to accidentally close the dialog until the finished signals is emitted.
While I can use self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) and re-enable it after the QRunnable finished running, I prefer to have a way alert the user that the function is still running if they accidentally close it (despite the dialog showing a progress bar and output log).
I'm thinking of subclassing and override the closeEvent but I wonder if there is other or even better way to approach this problem.
Here's a working example of a closeEvent override that satisfy the need of my original question.
import sys
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from PySide2.QtCore import *
class CloseEventOverrideDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowFlag(Qt.WindowMinimizeButtonHint, True)
self.setWindowFlag(Qt.WindowMaximizeButtonHint, True)
self.setWindowTitle("closeEvent Override Example")
self.is_busy = False
self.setup_ui()
def closeEvent(self, event: QCloseEvent):
print(f"{self.is_busy=}")
if self.is_busy:
msg = (
f"Something is busy running. Do you wish to abort the progress?"
)
is_cancel = QMessageBox.warning(
self,
"Warning!",
msg,
QMessageBox.Ok,
QMessageBox.Cancel
)
print(f"{is_cancel=}")
if is_cancel == QMessageBox.Cancel:
# Prevent dialog from closing!
event.ignore()
else:
print(f"User explicitly close dialog!")
event.accept()
def setup_ui(self):
self.layout = QVBoxLayout()
self.setLayout(self.layout)
checked_label = QLabel(
"Checked the following checkbox to trigger a QMessageBox Warning dialog "
"when user close the dialog."
)
self.layout.addWidget(checked_label)
self.busy_checkbox = QCheckBox("Busy")
self.busy_checkbox.stateChanged.connect(self.toggle_is_busy)
self.layout.addWidget(self.busy_checkbox)
def toggle_is_busy(self):
self.is_busy = True if self.busy_checkbox.isChecked() else False
print(f"{self.is_busy=}")
def main():
app = QApplication(sys.argv)
dialog = CloseEventOverrideDialog()
dialog.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Screenshot of QMessageBox Warning when user tries to close the dialog with closeEvent override

Emit a signal when any button is pressed

In a pyqt5 app I want to untoggle a button when any other button is clicked.
Is it possible to have a signal emitted when any button inside a layout or a widget is clicked?
I can connect each button click to an untoggle function but that would be hard to maintain if the buttons ever change. Is there a clean way to achieve the same functionality without having to connect the buttons one by one?
How to point out a possible solution is to inherit from the button and implement the indicated logic, but in the case of Qt it can be taken advantage of the fact that the clicked signal is associated with the QEvent.MouseButtonRelease event when a button is pressed. Considering the above, notify can be used to implement the logic:
import sys
from PyQt5.QtCore import pyqtSignal, QCoreApplication, QEvent
from PyQt5.QtWidgets import (
QAbstractButton,
QApplication,
QPushButton,
QVBoxLayout,
QWidget,
)
class Application(QApplication):
buttonClicked = pyqtSignal(QAbstractButton)
def notify(self, receiver, event):
if (
isinstance(receiver, QAbstractButton)
and receiver.isEnabled()
and event.type() == QEvent.MouseButtonRelease
):
self.buttonClicked.emit(receiver)
return super().notify(receiver, event)
def main(argv):
app = Application(argv)
view = QWidget()
lay = QVBoxLayout(view)
special_button = QPushButton("Special")
lay.addWidget(special_button)
for i in range(10):
button = QPushButton(f"button{i}")
lay.addWidget(button)
view.show()
def handle_clicked(button):
if button is not special_button:
print(f"{button} clicked")
QCoreApplication.instance().buttonClicked.connect(handle_clicked)
sys.exit(app.exec_())
if __name__ == "__main__":
main(sys.argv)
What you want is added functionality for an existing type. This suggests that you want to write an extension of the Button class. Write a class that inherits from Button. Write your own function to handle button clicks: this function will call your toggle function and then call the parent's button click handler.

Connecting in PyQt4 QMessageBox fails to call the slot method

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.

PyQt4: Only the last signal is being processed

I ran into a strange problem when working on my project. I have a GUI and a QTextEdit that serves as a status browser. When a button is clicked, I want the QTextEdit to display a 10 second countdown while another function is happening in a separate thread. Even though I emit a signal every second, the QTextEdit hangs for 9 seconds, then displays the last countdown number.
I thought this might have something to do with stuff happening in a separate thread, so I created a separate example to test this out. In my simple example, there are two things: a QTextEdit and a Button. When the button is clicked, the status browser should display '5' for two seconds, then '4'.
Here is the code:
import sys
from PyQt4 import QtGui, uic
from PyQt4.QtCore import QObject, pyqtSignal
import time
class MainUI(QObject):
status_signal = pyqtSignal(str)
def __init__(self, window):
super(QObject, self).__init__()
self.ui = uic.loadUi(r'L:\internal\684.07\Mass_Project\Software\PythonProjects\measure\testing\status_test.ui', window)
self.ui.statusBrowser.setReadOnly(True)
self.ui.statusBrowser.setFontPointSize(20)
self.status_signal.connect(self.status_slot)
self.ui.button.clicked.connect(self.counter)
window.show()
def status_slot(self, message):
self.ui.statusBrowser.clear()
self.ui.statusBrowser.append(message)
def counter(self):
print 'clicked'
i = 5
self.status_signal.emit(str(i))
time.sleep(2)
self.status_signal.emit(str(i-1))
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setStyle("cleanlooks")
main_window = QtGui.QDialog()
main_ui = MainUI(main_window)
sys.exit(app.exec_())
In this example, the same thing happens. The status browser hangs for 2 seconds, then only displays '4'. When I alter the status_slot function so that it doesn't clear the status browser before appending to it, the status browser waits for 2 seconds, then emits both signals at once, displaying '5 \n 4'. Does anyone know why this is happening and what I can do to constantly update the display? Thanks in advance!
time.sleep() blocks the Qt main loop so it can't process window redraw events. Use a QTimer to periodically call a method which emits your signal so that control is returned to the Qt event loop regularly.

PySide / PyQt detect if user trying to close window

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!

Categories