Extending a Widget Loaded from a UI File - python

I'd like to be able to load a widget (QMainWindow in this case) from a .ui file, but also be able to extend the class so that I can do things like catch keyboard events.
For example, I have the following that doesn't work but shows what I'm trying to accomplish:
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.window = QUiLoader().load("mainwindow.ui", self)
self.setCentralWidget(self.window)
self.window.keyPressEvent = self.key_pressed
self.window.setEnabled(True)
def show(self):
self.window.show()
def key_pressed(self, event):
print(event)
Because I cannot extend the object loaded from QUiLoader, I attempted to hijack the keyPressEvent method in that object, and assign it to my own key_pressed method. This doesn't work, but I'm unsure how else to go about capturing keyboard events.
I know I could create MyMainWindow and set it's base class to QMainWindow, and then override the keyPressEvent method, but then I have to do all the layout in code, and I'd much rather leverage the .ui file. How do you go about doing this?

To listen to events from other widgets you must use an eventFilter. Also the initial window is never displayed so it is better to replace it with a QObject.
from PySide2 import QtCore, QtWidgets, QtUiTools
class MyApp(QtCore.QObject):
def __init__(self):
super(MyApp, self).__init__()
self.window = QtUiTools.QUiLoader().load("mainwindow.ui")
self.window.installEventFilter(self)
def show(self):
self.window.show()
def eventFilter(self, obj, event):
if obj is self.window:
if event.type() == QtCore.QEvent.KeyPress:
print(event.key())
return super(MyApp, self).eventFilter(obj, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MyApp()
w.show()
sys.exit(app.exec_())

Related

PyQt5in Python - Use a QLabel like a button [duplicate]

I have a Qlabel filled with QPixmap and I want to start a process/function once this label clicked. I had extended QLabel class as follows:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class QLabel_alterada(QLabel):
clicked=pyqtSignal()
def __init(self, parent):
QLabel.__init__(self, QMouseEvent)
def mousePressEvent(self, ev):
self.clicked.emit()
Then, in my pyuic5-based .py file (I used QtDesigner to do the layout) after importing the module where I save the extended QLabel class, inside the automatically generated setupui, function I changed my Label from
self.label1=QtWidgets.QLabel(self.centralwidget)
to
self.label1 = QLABEL2.QLabel_alterada(self.centralwidget)
Finally, in the core app Python file where I put all the procedures/classes whetever needed to the application functionality I added
self.ui.label1.clicked.connect(self.dosomestuff)
The application does not crashes but the labels still not clickable. Can someone give me some help on this?
I do not understand why you pass QMouseEvent to the parent constructor, you must pass the parent attribute as shown below:
class QLabel_alterada(QLabel):
clicked=pyqtSignal()
def mousePressEvent(self, ev):
self.clicked.emit()
To avoid having problems with imports we can directly promote the widget as shown below:
We place a QLabel and right click and choose Promote to ...:
We get the following dialog and place the QLABEL2.h in header file and QLabel_changed in Promoted class Name, then press Add and Promote
Then we generate the .ui file with the help of pyuic. Obtaining the following structure:
├── main.py
├── QLABEL2.py
└── Ui_main.ui
Obtaining the following structure:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.label.clicked.connect(self.dosomestuff)
def dosomestuff(self):
print("click")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Since Python can pass function as object, I think make a class inherit QLabel only to make it clickable is overkill. Instead I do like this:
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QFrame, QLabel
def foo(*arg, **kwargs):
print("Bar")
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
Frame = QFrame()
label = QLabel(Frame)
label.mousePressEvent = foo
label.setText("Label")
Frame.show()
sys.exit(app.exec_())
class ClickableLabel(QtWidgets.QLabel):
def __init__(self, whenClicked, parent=None):
QtWidgets.QLabel.__init__(self, parent)
self._whenClicked = whenClicked
def mouseReleaseEvent(self, event):
self._whenClicked(event)
and then:
my_label = ClickableLabel(self.my_label_clicked)
...
def my_label_clicked(self, event):
button = event.button()
modifiers = event.modifiers()
if modifiers == Qt.NoModifier and button == Qt.LeftButton:
logger.debug('my_label_clicked: hooray!')
return
LOGGER.debug('my_label_clicked: unhandled %r', event)
In PyQT5, you can try to add code in setupUi and create clicked method:
def setupUi()
...
self.label1.linkActivated.connect(self.clicked)
...
def clicked(self):
print(self.label1.text()) #For example, printing the label text...
Hope this helps!

How to do override closeEvent functions to make it working

I'm using pyside2 and pyqt5 lib to loading my UI file.
from PySide2 import QtWidgets
from PySide2.QtWidgets import QApplication
from PySide2.QtUiTools import QUiLoader
class A(QtWidgets.QWidget):
def __init__(self, parent=None):
super(A, self).__init__(parent)
self.ui = QUiLoader().load('uiFile.ui')
def closeEvent(self, event):
event.ignore()
app = QApplication([])
mainWin = A()
mainWin.ui.show()
app.exec_()
In my thought, it will show '11111' when I clicked the X button.
However, it's not working at all.
here is the ui file
The problem is that "A" is a widget that is not displayed, it is not the window, so override closeEvent does not make sense. Instead you should use an event filter to monitor the window's events.
from PySide2.QtCore import QEvent, QObject
from PySide2.QtWidgets import QApplication
from PySide2.QtUiTools import QUiLoader
class Manager(QObject):
def __init__(self, ui, parent=None):
super(Manager, self).__init__(parent)
self._ui = ui
self.ui.installEventFilter(self)
#property
def ui(self):
return self._ui
def eventFilter(self, obj, event):
if obj is self.ui:
if event.type() == QEvent.Close:
event.ignore()
return True
super().eventFilter(obj, event)
def main():
app = QApplication([])
manager = Manager(QUiLoader().load("uiFile.ui"))
manager.ui.show()
app.exec_()
if __name__ == "__main__":
main()
If you want to override the closeEvent method of the QMainWindow used in the .ui then you must promote it and register it as this other answer shows.

How to set an event handler for an item (QTableWidget) being enabled/disabled?

I have a QTableWidget that gets enabled and disabled depending on certain data.
I have a button I want to be enabled when the QTableWidget is enabled, and disabled when the widget is disabled.
Is there any event handler that would let me do this?
Such as (and this does not work):
myTable.changeEvent.connect(lambda: print("test"))
Ideally in the above code, 'test' would be printed every time the table gets enabled or disabled.
The simplest solution is that the moment you deactivate the QTableWidget you also deactivate the button (or use a signal to transmit the information).
Instead, another solution is to use an event filter that allows to emit a signal every time the widget's state changes and then use that information to change the button's state.
import random
import sys
from PyQt5 import QtCore, QtWidgets
class EnableHelper(QtCore.QObject):
enableChanged = QtCore.pyqtSignal(bool)
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, obj, event):
if obj is self.widget and event.type() == QtCore.QEvent.EnabledChange:
self.enableChanged.emit(self.widget.isEnabled())
return super().eventFilter(obj, event)
class Widget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableWidget(5, 4)
self.button = QtWidgets.QPushButton("Hello world")
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.table)
lay.addWidget(self.button)
helper = EnableHelper(self.table)
helper.enableChanged.connect(self.button.setEnabled)
self.test()
def test(self):
self.table.setEnabled(random.choice([True, False]))
QtCore.QTimer.singleShot(1000, self.test)
def main():
app = QtWidgets.QApplication(sys.argv)
win = Widget()
win.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
Note: changeEvent is not a signal so you should not use connect as this is a class method. Also it is not good to use it if you only want to detect if the widget changes state from enabled to disabled, or vice versa, since this is also called in other cases.

PySide: event-filter for QGraphicsView in container class

I have communications working between similar widgets from imported child widgets and the parent main window and widgets. However, I am stumped when it comes to a QGraphicsScene widget imported as a module and sub-widget. I have put some simplified files below. So, QGraphicsView (from the QGraphicsScene) will be the actual widget I need to emit and signal events to other QWidgets inside the main window.
If I have all classes in one file, it works but if I have the classes as separate modules, I get "does not have attribute" errors, specifically in the simple version here for QGraphicsScene .viewport
Attribute Error "self.graphicsView.viewport().installEventFilter(self)"
I guess that the composite graphics widget is actually now a QWidget and I am not initialising the imported module functions/attributes for the QGraphicsView element. Thing is, I want it to exist that way so I can separate the GUI elements and functions of different modules. The others I have used so far have been straightforward QWidget to QWidget signals derived from QObjects, so work fine, but I haven't been able to achieve the same with the imported QGraphicsScene to QWidgets since it errors when it tries to reach QGraphicsView within the main window. Again, all fine if all classes exist in one large file.
Any kind person can point out my error here? How can I separate the module scripts to behave the same way as the single script?
Working single script:
# QWidgetAll.py
from PySide import QtGui, QtCore
class GraphicsView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = QtGui.QGraphicsView(self)
self.graphicsLabel = QtGui.QLabel("Graphics View within QWidget")
self.graphicsView.setMouseTracking(True)
self.graphicsView.viewport().installEventFilter(self)
self.edit = QtGui.QLineEdit(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.graphicsLabel)
layout.addWidget(self.edit)
layout.addWidget(self.graphicsView)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.MouseMove and
source is self.graphicsView.viewport()):
pos = event.pos()
self.edit.setText('x: %d, y: %d' % (pos.x(), pos.y()))
return QtGui.QWidget.eventFilter(self, source, event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = GraphicsView()
window.show()
window.resize(200, 100)
sys.exit(app.exec_())
The same file as separate modules. qWidgetView.py errors with attribute error:
# qWidgetView.py
from PySide import QtGui, QtCore
from qGraphicView import GraphicsView
class WidgetView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = GraphicsView()
self.graphicsView.setMouseTracking(True)
self.graphicsView.viewport().installEventFilter(self)
self.edit = QtGui.QLineEdit(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
layout.addWidget(self.graphicsView)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.MouseMove and
source is self.graphicsView.viewport()):
pos = event.pos()
self.edit.setText('x: %d, y: %d' % (pos.x(), pos.y()))
return QtGui.QWidget.eventFilter(self, source, event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = WidgetView()
window.show()
window.resize(200, 100)
sys.exit(app.exec_())
with imported qGraphicView.py module:
# qGraphicView.py
from PySide import QtGui, QtCore
class GraphicsView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = QtGui.QGraphicsView(self)
self.graphicsLabel = QtGui.QLabel("Graphics View within QWidget")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.graphicsLabel)
layout.addWidget(self.graphicsView)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = GraphicsView()
window.show()
window.resize(200, 100)
sys.exit(app.exec_())
You need to filter events for the QGraphicsView that is a child widget of the GraphicsView class, because you only want mouse-moves on the graphics-view itself, not the whole container widget. So I would suggest something like this:
The qGraphicView.py module:
class GraphicsView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = QtGui.QGraphicsView(self)
self.graphicsView.setMouseTracking(True)
self.graphicsLabel = QtGui.QLabel("Graphics View within QWidget")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.graphicsLabel)
layout.addWidget(self.graphicsView)
def viewport(self):
return self.graphicsView.viewport()
The qWidgetView.py module:
class WidgetView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = GraphicsView()
self.graphicsView.viewport().installEventFilter(self)
self.edit = QtGui.QLineEdit(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
layout.addWidget(self.graphicsView)

How to detect any mouse click on PySide Gui?

I am trying implement a feature such that when a mouse is clicked on the gui, a function is triggered
Below is my mouse click detection, it doesn't work when I click on any part of the gui
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QHBoxLayout(self)
layout.addWidget(QLabel("this is the main frame"))
layout.gui_clicked.connect(self.anotherSlot)
def anotherSlot(self, passed):
print passed
print "now I'm in Main.anotherSlot"
class MyLayout(QHBoxLayout):
gui_clicked = Signal(str)
def __init__(self, parent=None):
super(MyLayout, self).__init__(parent)
def mousePressEvent(self, event):
print "Mouse Clicked"
self.gui_clicked.emit("emit the signal")
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())
This is my goal
Mouseclick.gui_clicked.connect(do_something)
Any advice would be appreciated
Define mousePressEvent inside Main:
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QHBoxLayout(self)
layout.addWidget(QLabel("this is the main frame"))
def mousePressEvent(self, QMouseEvent):
#print mouse position
print QMouseEvent.pos()
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())
This can get complicated depending on your needs. In short, the solution is an eventFilter installed on the application. This will listen the whole application for an event. The problem is "event propagation". If a widget doesn't handle an event, it'll be passed to the parent (and so on). You'll see those events multiple times. In your case, for example QLabel doesn't do anything with a mouse press event, therefore the parent (your main window) gets it.
If you actually filter the event (i.e. you don't want the original widget to respond to the event), you won't get that problem. But, I doubt that this is your intent.
A simple example for just monitoring:
import sys
from PySide import QtGui, QtCore
class MouseDetector(QtCore.QObject):
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
print 'mouse pressed', obj
return super(MouseDetector, self).eventFilter(obj, event)
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
layout = QtGui.QHBoxLayout()
layout.addWidget(QtGui.QLabel('this is a label'))
layout.addWidget(QtGui.QPushButton('Button'))
self.setLayout(layout)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mouseFilter = MouseDetector()
app.installEventFilter(mouseFilter)
main = MainWindow()
main.show()
sys.exit(app.exec_())
You can see that, clicking on the QLabel will give you something like:
mouse pressed <PySide.QtGui.QLabel object at 0x02B92490>
mouse pressed <__main__.MainWindow object at 0x02B92440>
Because, QLabel receives the event and since it doesn't do anything with it, it's ignored and passed to the parent (MainWindow). And it's caught by the filter/monitor again.
Clicking on the QPushButton doesn't have any problem because it uses that event and does not pass to the parent.
PS: Also note that this can cause performance problems since you are inspecting every single event in the application.

Categories