How does one properly capture the close event coming out of a PySide QtUiTools.QUiLoader() setup?
I can get the instanced class to connect to widgets and everything else, but I am not sure how to intercept the signals in this setup.
Ideally, I want all close calls to pass through my closeEvent (obviously) so that I can ensure that it's safe to close the window. But since my self.closeEvent() is tied to my View(QtWidgets.QMainWindow) and not the self._qt.closeEvent(), I don't know how to get to the self._qt.closeEvent() method to override it in this case.
Or is there a better way to set this up to capture those window events?
# Compatible enough with Pyside 2
from PySide import QtGui as QtWidgets
from PySide import QtUiTools
from PySide import QtCore
class View(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(View, self).__init__(parent=parent)
self.setup()
def closeEvent(self, event):
# Do things
event.accept()
def setup(self):
loader = QtUiTools.QUiLoader()
fy = QtCore.QFile('example.ui')
fy.open(QtCore.QFile.ReadOnly)
self._qt = loader.load(fy, self)
fy.close()
self._qt.pCanceled.clicked(self._qt.close)
Doesn't apply:
PySide / PyQt detect if user trying to close window
Close, but PySide doesn't use PyQt's uic and appears to run differently (and didn't work):
PyQt: clicking X doesn't trigger closeEvent
closeEvent is not a signal, it is a method that is called when the QCloseEvent event is sent. A signal and an event are different things. Going to the problem, in Qt there are 2 ways to listen to events, the first one is overwriting the fooEvent() methods and the second one using an event filter as I show below:
from PySide import QtGui as QtWidgets
from PySide import QtUiTools
from PySide import QtCore
class View(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(View, self).__init__(parent=parent)
self._qt = None
self.setup()
def closeEvent(self, event):
self.do_things()
super(View, self).closeEvent(event)
def do_things(self):
print("do_things")
def setup(self):
loader = QtUiTools.QUiLoader()
fy = QtCore.QFile('example.ui')
fy.open(QtCore.QFile.ReadOnly)
self._qt = loader.load(fy, self)
fy.close()
self._qt.pCanceled.clicked.connect(self._qt.close)
self._qt.installEventFilter(self)
def eventFilter(self, watched, event):
if watched is self._qt and event.type() == QtCore.QEvent.Close:
self.do_things()
return super(View, self).eventFilter(watched, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())
Update:
Normally in the eventFilter it is enough to return True for the event to be ignored but in the case of QCloseEvent you must ignore the event and return True as shown below:
def eventFilter(self, watched, event):
if watched is self._qt and event.type() == QtCore.QEvent.Close:
self.do_things()
event.ignore()
return True
return super(View, self).eventFilter(watched, event)
Related
I am programming a simple GUI, that will open a opencv window at a specific point. This window has some very basic keyEvents to control it. I want to advance this with a few functions. Since my QtGui is my Controller, I thought doing it with the KeyPressedEvent is a good way. My Problem is, that I cannot fire the KeyEvent, if I am active on the opencv window.
So How do I fire the KeyEvent, if my Gui is out of Focus?
Do I really need to use GrabKeyboard?
The following code reproduces my Problem:
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.Qt import Qt
import cv2
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.first = True
def openselect(self):
im = cv2.imread(str('.\\images\\Steine\\0a5c8e512e.jpg'))
self.r = cv2.selectROI("Image", im)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Space and self.first:
self.openselect()
self.first = False
print('Key Pressed!')
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
The keyPressEvent method is only invoked if the widget has the focus so if the focus has another application then it will not be notified, so if you want to detect keyboard events then you must handle the OS libraries, but in python they already exist libraries that report those changes as pyinput(python -m pip install pyinput):
import sys
from PyQt5 import QtCore, QtWidgets
from pynput.keyboard import Key, Listener, KeyCode
class KeyMonitor(QtCore.QObject):
keyPressed = QtCore.pyqtSignal(KeyCode)
def __init__(self, parent=None):
super().__init__(parent)
self.listener = Listener(on_release=self.on_release)
def on_release(self, key):
self.keyPressed.emit(key)
def stop_monitoring(self):
self.listener.stop()
def start_monitoring(self):
self.listener.start()
class MainWindow(QtWidgets.QWidget):
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
monitor = KeyMonitor()
monitor.keyPressed.connect(print)
monitor.start_monitoring()
window = MainWindow()
window.show()
sys.exit(app.exec_())
I am trying to implement a QMainWindow with centralWidget and dockWidget. When the user resize the dock widget I want resizeEvent to be called for the dock widget and some values to be returned. I have implemented the resizeEvent for the complete QMainWindow and it is working fine. How can i call resizeEvent for the dockWidget which is a Qwidget without making another class which will inherit from Qwidget and implement the resizeEvent there and afterwards to create an object in QMainwindow. The first given example is working just fine.
class ui(QMainWindow):
def __init__(self):
super().__init__()
self.bottom_dock_widget = DockWidget('Results')
self.addDockWidget(Qt.BottomDockWidgetArea, self.bottom_dock_widget)
self.resize(500, 500)
def resizeEvent(self, event):
print('mainWindow')
self.update()
class DockWidget(QDockWidget):
def __init__(self, name, image_view):
super().__init__()
self.setWindowTitle(name)
def resizeEvent(self, event):
print('in Dock')
self.update()
Is there a way to be able to implement the example like this:
class ui(QMainWindow):
def __init__(self):
super().__init__()
self.bottom_dock_widget = QDockWidget('Results')
self.addDockWidget(Qt.BottomDockWidgetArea, self.bottom_dock_widget)
self.resize(500, 500)
but to be able to call resizeEvent only for the dock widget
def resizeEvent(self, event):
print('dock')
self.update()
like on c++ with the scope
def bottom_dock_widget :: resizeEvent(self):
If you want to hear the resize event of a widget it is not necessary to override the resizeEvent() method since it is enough to install an event filter analyzed the QEvent::Resize event
import sys
from PyQt5.QtCore import QEvent, Qt
from PyQt5.QtWidgets import QApplication, QDockWidget, QMainWindow
class UI(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.bottom_dock_widget = QDockWidget("Results")
self.bottom_dock_widget.installEventFilter(self)
self.addDockWidget(Qt.BottomDockWidgetArea, self.bottom_dock_widget)
self.resize(500, 500)
def eventFilter(self, obj, event):
if obj is self.bottom_dock_widget and event.type() == QEvent.Resize:
print("dock")
return super().eventFilter(obj, event)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = UI()
w.show()
sys.exit(app.exec_())
I try to find a way to catch event when user move Qdialog from titleBar.
My goal is to attach a drag event to dock my custom qdialog inside my mainWindow. (on Linux)
In other terms, do what dockwidgets do (I can use dockwidget) I have to do the same with Custom Qdialog (or widget with Qt.Window flags)
I see in c++ Qt source code than for QDockWidget, They use this kind of stuff:
bool QDockWidget::event(QEvent *event)
{ [...]
case QEvent::NonClientAreaMouseMove:
case QEvent::NonClientAreaMouseButtonPress:
case QEvent::NonClientAreaMouseButtonRelease:
case QEvent::NonClientAreaMouseButtonDblClick:
d->nonClientAreaMouseEvent(static_cast<QMouseEvent*>(event));
But when I try to catch this kind of event on pyside, I recieve nothin:
def event(self, e):
print('event %s' % e.type())
return super(myDyalig,self).event(e)
event PySide2.QtCore.QEvent.Type.ActivationChange
event PySide2.QtCore.QEvent.Type.UpdateRequest
event PySide2.QtCore.QEvent.Type.Paint
# I recieve only this move event when user stop moving (when he
# release the button)
event PySide2.QtCore.QEvent.Type.Move
event PySide2.QtCore.QEvent.Type.WindowActivate
event PySide2.QtCore.QEvent.Type.ActivationChange
event PySide2.QtCore.QEvent.Type.UpdateRequest
event PySide2.QtCore.QEvent.Type.Paint
Any idea how to do this ? (or another idea how to realize a drag event with qdialog)
Edit:
a minimal example:
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
class CustomDialog(QDialog):
def __init__(self, parent=None):
super(CustomDialog,self).__init__(parent)
self.setFixedSize(QSize(200,200))
def event(self, e):
print('event %s' % e.type())
return super(CustomDialog,self).event(e)
def main():
import sys
app = QApplication(sys.argv)
dial = CustomDialog()
dial.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You need to install an event filter:
def __init__(self, parent=None):
super().__init__(parent)
#...
self.installEventFilter(self)
def eventFilter(self, obj, event):
if event.type() in (QEvent.NonClientAreaMouseButtonPress, QEvent.NonClientAreaMouseButtonPress, QEvent.Move):
print(event)
return super().eventFilter(obj, event)
See also: https://doc.qt.io/qt-5/eventsandfilters.html
Either I don't understand completely how Qt's event propagation works or something, but I cannot understand why exactly closeEvent is not being called both for QPushButton-derived class and for QWidget-derived one itself.
Shouldn't wid.closeEvent() trigger closeEvents of all children widgets?
#!/bin/env python
# -*- coding: utf-8 -*-
import sys, os
from Qt.QtCore import *
from Qt.QtWidgets import *
from Qt.QtGui import *
class butt(QPushButton):
def __init__(self, parent, name='Button'):
super(self.__class__, self).__init__(parent)
self.name = name
def closeEvent(self, e):
print('butt closeevent')
e.accept()
class wid(QWidget):
def __init__(self, parent=None):
super(self.__class__, self).__init__(parent)
self.initUI()
def initUI(self):
#self.setAttribute(Qt.WA_DeleteOnClose)
self.vl = QVBoxLayout(self)
self.button = butt(self)
self.button.setText('test1')
self.vl.addWidget(self.button)
self.button.clicked.connect(QCoreApplication.quit)
def closeEvent(self, e):
print('wid closeevent')
e.accept()
def show():
app = QApplication(sys.argv)
win = QMainWindow()
widget = wid(win)
win.setCentralWidget(widget)
win.show()
app.exec_()
if __name__ == "__main__":
show()
I'm expecting to see 2 lines
wid closeevent
butt closeevent
as an output, but I see nothing. Why closeEvent it not being called for them?
In the following examples when you press the button visually you will observe the same behavior: the window will close, but we see the difference, in the first one it is called closeEvent(), and in the second one it is not.
Example1:
#!/bin/env python
# -*- coding: utf-8 -*-
from Qt import QtCore, QtGui, QtWidgets
class Button(QtWidgets.QPushButton):
def closeEvent(self, event):
print("button closeEvent")
event.accept()
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
button = Button(text="Press me")
button.clicked.connect(button.close)
button.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Example2:
#!/bin/env python
# -*- coding: utf-8 -*-
from Qt import QtCore, QtGui, QtWidgets
class Button(QtWidgets.QPushButton):
def closeEvent(self, event):
print("button closeEvent")
event.accept()
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
button = Button(text="Press me")
button.clicked.connect(QtCore.QCoreApplication.quit)
button.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Why do not you call closeEvent when you call QCoreApplication::quit?
Because this method is used to exit the event loop of Qt, and if there is no event loop the events(QCloseEvent) do not work.
When a widget is closed, the children widget is not closed, that is, only if a widget is closed, only its own closeEvent will be called. So if you want the closeevent of the widget to be called, call your method close.
#!/bin/env python
# -*- coding: utf-8 -*-
from Qt import QtCore, QtGui, QtWidgets
class Button(QtWidgets.QPushButton):
def __init__(self, name="Button", parent=None):
super(Button, self).__init__(parent)
self.m_name = name
def closeEvent(self, event):
print("button closeEvent")
event.accept()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.initUI()
def initUI(self):
vl = QtWidgets.QVBoxLayout(self)
button = Button()
button.setText("test1")
vl.addWidget(button)
button.clicked.connect(self.close)
def closeEvent(self, event):
print("Widget closeevent")
event.accept()
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QMainWindow()
widget = Widget()
w.setCentralWidget(widget)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
In the previous example depending on how you interact with the widget you will get the following behavior:
If you press the button the widget will close so it will be called closeEvent, and will not call the closeEvent of the button because even your child is not closing it.
If you press the "X" button in the window it will not be called the closeEvent of the widget but the QMainWindow, the explanation is the same as the previous one.
Conclusions:
Each type of event has its own workflow, some events are only received by the widget and not by the children, while others send the information to the children.
The close method uses the event loop to notify that the widget is to be closed, but QCoreApplication::quit() terminates the event loop.
Update:
why wid.closeEvent is not called when user pressing "X" button on Qt window? Aren't main window supposed to call closeEvent on all it's children widgets and then destroy them properly?
No, one thing is the closing of a widget and another thing is the destruction of the widget, it can be destroyed without closing and closing the window does not involve destroying the object.
As already pointed out closing a window does not imply deleting it, there may be other windows open, but if the last QApplication window is closed by default the eventloop that implies the destruction of the widget will be terminated which does not necessarily imply calling the close method.
To be understood, let's use the following code:
from Qt import QtCore, QtGui, QtWidgets
class Button(QtWidgets.QPushButton):
def closeEvent(self, event):
print("closeEvent Button")
super(Button, self).closeEvent(event)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
button_quit = Button(
text="quit",
clicked=QtCore.QCoreApplication.quit
)
button_close = Button(
text="close",
clicked=self.close
)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(button_quit)
lay.addWidget(button_close)
def closeEvent(self, event):
print("closeEvent Widget")
super(Widget, self).closeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
There are 2 buttons, in the case of the first button that calls QCoreApplication::quit() that will end the eventloop so all the widgets will be destroyed, and in that case no closeEvent will be called, in the case of the second button it will be call near the window so it will call its closeEvent but not the closeEvents of its children.
my actual problem is that I have saveUI() func in closeEvent, and it's not being called upon widgets hierarchical destruction on window close
If you want the closeEvent method to be called hierarchically then you must call the close method manually since Qt does not design it that way. In the next part there is an example:
from PyQt5 import QtCore, QtGui, QtWidgets
class PushButton(QtWidgets.QPushButton):
def closeEvent(self, event):
for children in self.findChildren(
QtWidgets.QWidget, options=QtCore.Qt.FindDirectChildrenOnly
):
children.close()
print("closeEvent PushButton")
super(PushButton, self).closeEvent(event)
class LineEdit(QtWidgets.QLineEdit):
def closeEvent(self, event):
for children in self.findChildren(
QtWidgets.QWidget, options=QtCore.Qt.FindDirectChildrenOnly
):
children.close()
print("closeEvent LineEdit")
super(LineEdit, self).closeEvent(event)
class ComboBox(QtWidgets.QComboBox):
def closeEvent(self, event):
for children in self.findChildren(
QtWidgets.QWidget, options=QtCore.Qt.FindDirectChildrenOnly
):
children.close()
print("closeEvent ComboBox")
super(ComboBox, self).closeEvent(event)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
button_close = PushButton(text="close", clicked=self.close)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(button_close)
lay.addWidget(LineEdit())
lay.addWidget(ComboBox())
def closeEvent(self, event):
for children in self.findChildren(
QtWidgets.QWidget, options=QtCore.Qt.FindDirectChildrenOnly
):
children.close()
print("closeEvent Widget")
super(Widget, self).closeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I'm trying to write a program that will interact with QGraphicsView. I want to gather mouse and keyboard events when the happen in the QGraphicsView. For example, if the user clicks on the QGraphicsView widget I will get the mouse position, something like that. I can hard code it rather easily, but I want to use QtDesigner because the UI will be changing frequently.
This is the code that I have for the gui.py. A simple widget with a QGraphicsView in it.
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_graphicsViewWidget(object):
def setupUi(self, graphicsViewWidget):
graphicsViewWidget.setObjectName(_fromUtf8("graphicsViewWidget"))
graphicsViewWidget.resize(400, 300)
graphicsViewWidget.setMouseTracking(True)
self.graphicsView = QtGui.QGraphicsView(graphicsViewWidget)
self.graphicsView.setGeometry(QtCore.QRect(70, 40, 256, 192))
self.graphicsView.setObjectName(_fromUtf8("graphicsView"))
self.retranslateUi(graphicsViewWidget)
QtCore.QMetaObject.connectSlotsByName(graphicsViewWidget)
def retranslateUi(self, graphicsViewWidget):
graphicsViewWidget.setWindowTitle(QtGui.QApplication.translate("graphicsViewWidget", "Form", None, QtGui.QApplication.UnicodeUTF8))
The code for the program:
#!/usr/bin/python -d
import sys
from PyQt4 import QtCore, QtGui
from gui import Ui_graphicsViewWidget
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_graphicsViewWidget()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.graphicsView, QtCore.SIGNAL("moved"), self.test)
def mouseMoveEvent(self, event):
print "Mouse Pointer is currently hovering at: ", event.pos()
self.emit(QtCore.SIGNAL("moved"), event)
def test(self, event):
print('in test')
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyForm()
myapp.show()
sys.exit(app.exec_())
When I run this code, it gives me the opposite of what I want. I get the mouse position everywhere except for inside the QGraphicsView.
I'm sure it's a problem with my QObject.connect. But every time I go back and read about signals and slots it makes sense but I can't get it.
Please help, I've been banging my head for the past few days now. I'm sorry if this as been asked before but I've been through all the threads on this topic and I can't get anywhere.
Thanks
The signal must come from the QGraphicsView object that was defined in the ui.
You can create a class derived from QGraphicsView like this
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyView(QGraphicsView):
moved = pyqtSignal(QMouseEvent)
def __init__(self, parent = None):
super(MyView, self).__init__(parent)
def mouseMoveEvent(self, event):
# call the base method to be sure the events are forwarded to the scene
super(MyView, self).mouseMoveEvent(event)
print "Mouse Pointer is currently hovering at: ", event.pos()
self.moved.emit(event)
Then, in the designer:
right-click on the QGraphicsView then Promote to
write the class name in the Promoted Class Name field (e.g. "MyView"),
write the file name where that class is in the Header file field but without the .py extension,
click on the Add button and then on the Promote button.
And you can regenerate your file gui.py with pyuic4.