About the super() function - python

I'm using QtDesign to create my own UI and convert it to python version. So after subclass the UI python file, i had written some function to implement mouseEvent for QGraphicsView. Just one small question. How can i call the super() function for the QGraphicsView?
class RigModuleUi(QtGui.QMainWindow,Ui_RiggingModuleUI):
def __init__(self,parent = None):
super(RigModuleUi,self).__init__(parent = parent)
self.GraphicsView.mousePressEvent = self.qView_mousePressEvent
def qView_mousePressEvent(self,event):
if event.button() == QtCore.Qt.LeftButton:
super(RigModuleUi,self).mousePressEvent(event)
Look like the super(RigModuleUi,self).mousePressEvent(event)will return the mouseEvent for QMainWindow, not QGraphicsView. So all other option for mouse like rubberBand will lost.
Thanks

I'm not quite sure what you expect to happen here. You're storing a bound method. When it's called, it'll still be called with the same self it had when you stored it.
Your super is walking up the ancestry of RigModuleUi, which doesn't inherit from QGraphicsView.
self.GraphicsView is a funny name for an instance attribute; is that supposed to be the name of a class, or is it just capitalized incidentally? (Please follow PEP8 naming conventions.) Perhaps you'd have more luck if you defined the method as a global function and assigned that to the instance.
def qView_mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
super(QGraphicsView, self).mousePressEvent(event)
class RigModuleUi(QtGui.QMainWindow, Ui_RiggingModuleUI):
def __init__(self, parent=None):
super(RigModuleUi,self).__init__(parent=parent)
self.GraphicsView.mousePressEvent = qView_mousePressEvent
Guessing wildly here; I don't know PyQt's class hierarchy :)

Related

How to modify the same method in a set of sibling classes?

I have two classes (Table and Button) inherited from the same class Widget. Both subclasses have their own keyEvent() methods and both call Widget.keyEvent() when necessary. I want to modify the keyEvent() behaviour for both classes in the same way (for example make A and D keys to trigger LEFT and RIGHT keys).
This code works exactly as I want
class KeyModifier:
def keyEvent():
# some lines of code
super().keyEvent()
class MyTable(KeyModifier,Table):
pass
class MyButton(KeyModifier,Button):
pass
But Pylance is angry because KeyModifier.super() doesn't have any keyEvent() method (which is true).
Is there a way to do it better? Also, I would like Pylance to warn me when using the KeyModifier with something not inherited from Widget.
This example comes from a PyQT app, but the question is more general.
Edit:
Making KeyModifier a subclasss of Widget makes KeyModifier.super().keyEvent() call Widget.keyEvent() and I want to call the child class method (Table.keyEvent() or Button.keyEvent())
Does it help?
from abc import abstractmethod
class Table:
pass
class Button:
pass
class KeyModifier:
#abstractmethod
def custom_operation(self):
pass
def key_event(self, condition):
if condition:
self.custom_operation()
class MyTable(KeyModifier, Table):
def __init__(self):
super(MyTable, self).__init__()
def custom_operation(self):
pass
class MyButton(KeyModifier, Button):
def custom_operation(self):
pass
If you make KeyModifier inherit from Widget, the warning will be gone because keyEvent will actually be defined for the object. If you also add super().keyEvent() calls to your modified classes, all the proper events will fire thanks to something called MRO - Method Resolution Order.
class Base:
def event(self):
print("Base")
class A(Base):
def event(self):
print("A")
class B(Base):
def event(self):
print("B")
class Modifier(Base):
def event(self):
print("Modified")
super().event()
class ModifiedA(Modifier, A):
def event(self):
print("ModifiedA")
super().event()
class ModifiedB(Modifier, B):
def event(self):
print("ModifiedB")
super().event()
ModifiedA().event()
Output:
ModifiedA
Modified
A
It is important to note that if A and B do not call a super on their own (I'm fairly certain that PyQt widgets DO call their parent though), Modifier has to be the first class inherited, as it will cause it to be first in MRO and have a chance to call the other class method in turn.
I've found a workaround as I don't really have to manage any class methods but the event that is being handled.
def KeyModifier(event: Event) -> Event:
# some lines of code and edit 'event' if necessary
return event
class MyButton(Button):
def keyEvent(self, event: Event):
super().keyEvent(KeyModifier(event))
I think this is the simplest way to write it. Thank you all for your suggestions :)

How do I create GUI classes for windows, which have subwindows/classes that can access to the main GUI's functions?

How do I create GUI classes for windows which have subwindows/classes that can access the main GUI's functions?
I have the below code which modifies the compiled .ui code from designer. What I want it to do is, when clicking the top-right "X", or using File -> Exit function, to close the window comprising the Window_SecondWindow class, and show the main window again--effectively calling the main window's show() from the subclass. I want to show only one window at a time.
When the code is run as-is, the Window_SecondWindow class hides, but immediately shows again, leading me to believe super is acting as self.
from PyQt5 import QtWidgets
from GUI import compiled_MainWindow
from GUI import compiled_SecondWindow
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.ui = compiled_MainWindow.Ui_MainWindow()
self.ui.setupUi(self)
self.ui.closeEvent = self.clicked_EXIT
# connect widgets
self.ui.Btn.clicked.connect(self.clicked_Btn)
self.ui.actionExit.triggered.connect(self.clicked_EXIT)
# add windows
self.SecondWindow = SecondWindow()
# more windows attached to main window
def clicked_Btn(self):
self.hide()
self.SecondWindow.show()
def clicked_EXIT(self):
self.close()
class Window_SecondWindow(Window_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = compiled_SecondWindow.Ui_MainWindow()
self.ui.setupUi(self)
self.ui.closeEvent = self.clicked_EXIT
self.ui.actionExit.triggered.connect(self.clicked_EXIT)
def clicked_EXIT(self):
self.hide()
super().show()
Before answering your question, I'd like to address some important aspects.
First of all, never edit the generated code from pyuic to create your programs. They are intended to be used as imported modules, mostly as "resources": you import and integrate them into your code, but you should always leave them as they are. See the documentation on using Designer for more insight about this.
Be careful in overriding functions within the __init__: some functions are not "virtual" (thus, cannot be overwritten in such a way) and in some cases Qt always calls the base class function name anyway; just overwrite the method and call the base class implementation with super() if required. Also, closeEvent has the close event as a mandatory argument, and you have to add that to your overridden function, even if you don't use it (in the following examples I'm just using *args). That said, you should never use an overridden function as a slot that has a different argument, or viceversa.
Finally, you should not use capitalized names for attribute and variable names, as it is confusing and prone to errors (capitalization is mostly used for class names only, not their instancies).
Now, the answer
You are almost right, super() acts "as self", in the sense that it just calls the inherited show() method of the class against the instance. So, it calls the show method of Window_MainWindow, but since the instance is the second window, it's the same as doing Window_MainWindow.show(self), with self being the Window_SecondWindow instance; it is exactly as doing self.show().
There are two (and a half) possibilities.
The first, more obvious solution, is to give a reference of the main window instance to the second one:
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.secondWindow = Window_SecondWindow()
self.secondWindow.mainWindow = self
class Window_SecondWindow(Window_MainWindow):
# ...
def clicked_EXIT(self, *args):
self.hide()
self.mainWindow.show()
Be aware that while, as #noras points out in the comment, you could set the main window as a parent in the init argument, but this only works as expected with QMainWindow and QDialog descendants; if the child widget is of any other kind, it will be shown inside the parent, not as a separate window.
The second (and more "Qt-wise correct") is to create a signal for the second class that is emitted when it's closed, and connect it in the main window so that it's shown again when that happens:
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.secondWindow = Window_SecondWindow()
self.secondWindow.closed.connect(self.show)
class Window_SecondWindow(Window_MainWindow):
closed = QtCore.pyqtSignal()
def clicked_EXIT(self, *args):
self.hide()
self.closed.emit()
The second-and-a-half solution is to use an event filter:
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.secondWindow = Window_SecondWindow()
self.secondWindow.installEventFilter(self)
def eventFilter(self, source, event):
if source == self.secondWindow and event.type() == QtCore.QEvent.Close:
self.show()
return super().eventFilter(source, event)

Why is my python class inheritance incorrect?

I'm learning to use PyQt5 and have run across a problem. My code is attempting to just draw a simple black box in the QMainWindow object by writing a second class PaintWidget which inherits from QWidget. I've posted my code first, and the correct one below it.
class PaintWidget(QWidget):
def __init__(self):
super().__init__()
self.qp = QPainter()
self.initUI()
def initUI(self):
self.qp.fillRect(1,1,100,100, Qt.black)
Correct:
class PaintWidget(QWidget):
def paintEvent(self, event):
qp = QPainter(self)
qp.fillRect(1, 1, 100, 100, Qt.black)
This is what confuses me. In order to create this class, we need to inherint from the super class QWidget, inorder to do so we use the function super().__init__() under __init__(self). We then set up the QPaint object which we will use in our method initUI() which actually does the work. Now this doesn't work when I run it.
The second, correct class, doesn't even seem to inherent, since it has no super().__init__(), even worse, it is setting up a method that is never even called (paintevent(self, event)), which takes an argument that seemingly comes from nowhere. Can someone point out why I'm wrong?
There is absolutely no difference to inheritance between the two cases. In both cases you say class PaintWidget(QWidget), so you are inheriting the QWidget.
The difference is in where you draw. In constructor (__init__), the widget is not yet mapped to the screen, so if you try to draw there, it won't have effect.
When the widget is actually displayed on screen, the system will invoke the paintEvent, which is a virtual method of the QWidget, and that is where you must draw the content. You only define that method in the second example.
Note that you need fresh QPainter in each invocation of the paintEvent. Creating one in the constructor and then using it in paintEvent would not work.
Also, most windowing systems don't remember the content of the widget when it is not actually visible on screen and rely on being able to call the paintEvent whenever the widget becomes visible again. So the method will likely be called many times. In contrast, the constructor, __init__, is only called once when creating the object.

Python object composition - accessing a method from the class that called it

You'll have to forgive me, I am trying to teach myself OO but I have come across this problem with composition and 'has-a' relationships.
class Main(object):
def A(self):
print 'Hello'
def B(self):
self.feature = DoSomething()
class DoSomething(object):
def ModifyMain(self):
#Not sure what goes here... something like
Main.A()
def run():
M = Main()
M.B()
A real world example of the above simplification is a PySide application where Main is a MainWindow, and DoSomething is a dynamically created widget that is placed somewhere in the window. I would like DoSomething to be able to modify the status bar of the mainwindow, which is essentially calling (in Main) self.statusbar().
If there is a shortcut in PySide to do this, Tops!! please let me know! However, I'm actually after the more general Pythonic way to do this.
I think I'm close ... I just can't make it work...
Why don't you use a signal and slot instead? That's a more Qt and OOP way of doing this.
In your dynamically created widget class:
self.modifyMain = QtCore.Signal(str)
In your main class:
#QtCore.Slot(str)
def changeStatusbar(self, newmessage):
statusBar().showMessage(newmessage)
in you main class after creating your widget:
doSomething.modifyMain.connect(self.changeStatusbar)
And in you widget class, where you want to change the statusbar of main, you say:
modifyMain.emit("Hello")
None of this is tested as I don't have a PySide installation handy.
There are two problems with your code:
At no time do you call ModifyMain; and
Main.A() will result in an error, because A is an instance method, but you are calling it on a class.
You want something like:
class Main(object):
def A(self):
print 'Hello'
def B(self):
self.feature = DoSomething() # self.feature is an instance of DoSomething
self.feature.ModifyMain(self) # pass self to a method
class DoSomething(object):
def ModifyMain(self, main): # note that self is *this* object; main is the object passed in, which was self in the caller
#Note case - main, not Main
main.A()
def run():
M = Main()
M.B()
if __name__=="__main__": # this will be true if this script is run from the shell OR pasted into the interpreter
run()
Your names all flout the usual python conventions found in PEP8, which is a pretty good guide to python style. I have left them as they were in your code, but don't copy the style in this example - follow PEP8.

Event - catch only Qt.Key_Delete

I would like to change behavior of Key_Delete for QTableWidget, but because of using Designer I don't want to make any changes in py file with form. So instead of reimplementing QTableWidget like it is this answer: How to implement MousePressEvent for a Qt-Designer Widget in PyQt I do something like this:
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.tableWidget.__class__.keyPressEvent = self.test
def test(self,event):
if event.key() == Qt.Key_Delete:
print "test"
return QTableWidget.keyPressEvent(self, event)
The problem is that I don't know how to keep original behavior of other keys than Qt.Key_Delete.
I have already changed last line like this:
return QtGui.QTableWidget.keyPressEvent(self, event)
return QtGui.QTableWidget.event(self, event)
but it doesn't work.
First: "but it doesn't work" is usually not descriptive enough. What behavior did you expect? What was the behavior you saw instead? Were there any error messages?
I can easily see a few mistakes in here, though.
You're overriding the method QTableWidget.keyPressEvent, which expects 2 arguments: (QTableWidget instance, event). But in the code you show above, the function you are using to override it only takes 1 argument (the first argument, 'self', does not count since it is automatically supplied).
Since you have set QTableWidget.keyPressEvent = self.test, and you are also trying to call this function from within self.test(), you have created an infinitely recursive function.
When you call QTableWidget.keyPressEvent, the first argument you have passed in (self) is a QMainWindow object. However as I mentioned above, this function expects a QTableWidget as its first argument.
Since you are overriding this method at the class level, ALL QTableWidgets will be forced to use the same keyPressEvent function (this would be very problematic). Instead, you should override just your specific widget's method: self.ui.tableWidget.keyPressEvent = self.test (also note that the signature for tableWidget.keyPressEvent is different from QTableWidget.keyPressEvent)
Example:
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
win = QtGui.QMainWindow()
win.show()
table = QtGui.QTableWidget()
win.setCentralWidget(table)
def test(event):
if event.key() == QtCore.Qt.Key_Delete:
print "delete"
return QtGui.QTableWidget.keyPressEvent(table, event)
table.keyPressEvent = test
Finally, another (possibly cleaner) approach would be to create a subclass of QTableWdget and, within Designer, 'promote' the table widget to your new class.

Categories