Nqsir
Nqsir

Reputation: 899

Redefine (override) methods in PyQt5 with uic.loadUI()

I'm actually developping a small soft using PyQt5 and QtDesigner since few weeks now. I've watched a lot of tutos and looked for many answers on SO but I can't find the one concerning the way to override a QWidget's method using uic.loadUI().

Note that I've simplify as much as I could my testing code to point out precisely where my problem is:

1/ This one does not work, it loads my file.ui correctly but clicking doesn't do anything:

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QWidget

class Window(QWidget):
    def __init__(self):
        super().__init__() # Or QWidget.__init__(self)
        self.dlg = uic.loadUi("file.ui")
        self.dlg.show()

    def mousePressEvent(self, event):
        print("click !")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    app.exec_()

2/ But I've figured out that that one is actually working:

class Window(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.setWindowTitle("My window")
        self.show()

    def mousePressEvent(self, event):
        print("click !")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    app.exec_()

So as I can see it seems to be because I can't override the event's method. Due to the fact that mousePressEvent is not a method attached to my QWidget subclass self.dlg.

Now that I know why, I'd like to know how can I override the mousePressEvent method and use it. I was wondering about 'loading' the method after overrided it, or calling the QWidget method to redefining it, or simply create my own method to catch any event, but everything I tried completely failed.

Anyhow, thanks in advance for your answers / tips / prayers.

Upvotes: 1

Views: 2253

Answers (2)

ssokolow
ssokolow

Reputation: 15355

My approach is to use the Promote to... context menu entry in the Qt Designer object inspector.

That allows you to specify in the designer that specific widgets should use subclasses of what you're pushing around in the graphical builder. uic.loadUi will obey this.

Suppose you have a MyGraphicsView in my_graphics_view.py. You can place a QGraphicsView, then select Promote to... and enter the following:

  • Base class name: (pre-filled to QGraphicsView)
  • Promoted class name: MyGraphicsView
  • Header file: my_graphics_view.h (loadUi will adjust the extension for you)

...then click "Add" and then "Promote" and you're done.

There are only two caveats I've noticed:

  1. As far as I can tell, there's no setupUi method when using uic.loadUi and __init__ returns before the tree of child widgets has been built, so if you want to run any custom code after __init__, you have to put it in a method with some name like widget.configure_children() and call it yourself after calling widget = uic.loadUi(...).

  2. For reasons unknown to me, the copy of Qt Designer 5.5.1 I'm using from Kubuntu Linux 16.04 LTS doesn't offer QMainWindow in the Promote dialog's dropdown of known widgets.

    However, if you edit the .ui file, neither Qt Designer nor loadUi will have a problem with it. It appears to be exactly what it seems... a mysterious omission from the dropdown widget and nothing more.

Here's what it looks like in the raw .ui file:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
  <class>MainWindow</class>
  <widget class="MyMainWindow" name="MainWindow">
    <!-- ... --->
  </widget>
  <customwidgets>
    <customwidget>
      <class>MyMainWindow</class>
      <extends>QMainWindow</extends>
      <header>my_main_window.h</header>
    </customwidget>
  </customwidgets>
  <resources/>
  <connections/>
</ui>

It's also important to note that, according to this page, the PySide2 equivalent to uic.loadUi takes a parent as a second argument instead of a custom subclass, so you're basically forced into using this approach under PySide2.

Upvotes: 0

eyllanesc
eyllanesc

Reputation: 244282

Assuming that when creating file.ui you are using the Widget template:

enter image description here

Then the solution is the following:

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QWidget

class Window(QWidget):
    def __init__(self):
        super().__init__()
        uic.loadUi("file.ui", self)
        self.show()

    def mousePressEvent(self, event):
        print("click !")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    app.exec_()

Why did not it work when using self.dlg = uic.loadUi("file.ui")? Because as you point out the widget created is self.dlg which is still an attribute of the win window that was never shown, in the case of uic.loadUi("file.ui", self) you are pointing out that it does not create a window but that fills the existing window

Update:

According to the error you get:

TypeError: ('Wrong base class of toplevel widget', (<class '__main__.Window'>, 'QMainWindow'))

It allows me to deduce that you have not used a QWidget as a basis, but a QMainWindow, so Window must inherit from QMainWindow:

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi("file.ui", self)
        self.show()

    def mousePressEvent(self, event):
        print("click !")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    app.exec_()

Upvotes: 3

Related Questions