Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Easiest way to subclass a widget in Python for use with Qt Designer

I'm building an app using PyQt5, with (most of) the GUI being built using Qt Designer. I have a few UI elements that do mostly the same thing and it seems sensible to move their code into classes that inherit from a base class (which in turn inherits from QTableWidget).

This seems really straightforward except that I'm not sure how to get Qt Designer to use these classes when generating its UI files. I've found a guide for doing this in PyQt4 and another one that's for Qt 5 but with native C++ code and yet another one that's for PyQt5 but doesn't deal with the (most complicated) aspect of making the subclasses available in Qt Designer.

How do I make a subclass of a widget created using PyQt5 available for use in Qt Designer?

Alternatively, is there an easier way to do this? I don't need to change the appearance or functionality of the widgets—I'm mostly just looking for an organized place to store some code that makes their data easier to set and access.

like image 724
intuited Avatar asked Dec 11 '22 01:12

intuited


2 Answers

While creating plugins for Qt Designer is possible, it's not an easy task: the official documentation is outdated and sometimes incomplete, and some functions have not been fully ported for a transparent PyQt usage.

The easiest way is to use promoted widgets.

Let's say you have a widget that displays the current time in a fancy way:

from PyQt5 import QtCore, QtGui, QtWidgets, uic

class TimeFrame(QtWidgets.QFrame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.updateTimer = QtCore.QTimer(interval=60000, timeout=self.update)
        self._time = None

    @QtCore.pyqtProperty(QtCore.QTime)
    def time(self):
        return self._time

    @time.setter
    def time(self, time):
        self._time = time
        self.update()

    def paintEvent(self, event):
        super().paintEvent(event)
        size = min(self.width(), self.height()) - 10
        rect = QtCore.QRect(0, 0, size, size)
        rect.moveCenter(self.rect().center())
        qp = QtGui.QPainter(self)
        qp.setRenderHints(qp.Antialiasing)
        qp.drawEllipse(rect)
        qp.setPen(QtCore.Qt.NoPen)
        qp.setBrush(QtCore.Qt.blue)
        time = self._time or QtCore.QTime.currentTime()
        minuteAngle = time.minute() * 6
        qp.drawPie(rect, 1440, -minuteAngle * 16)
        qp.setBrush(QtCore.Qt.red)
        hourAngle = (time.hour() % 12 + time.minute() / 60) * 30
        qp.drawPie(rect.adjusted(30, 30, -30, -30), 1440, -hourAngle * 16)

This is a simple clock widget that shows a pair of "pies", the bigger for minutes, the smaller for hours (in this screenshot, the time is 2:03):

fancy clock widget


Now, to be able to add that widget from Designer, you'll need to promote a widget. Promoting widgets is a way for Qt to expand the features and functionalities of a widget added to a UI.
This is very useful, because you can expand any standard widget class in your own way: for example, if you add a QTableView to the UI and promote it, it allows you to set the standard QTableView properties from the Designer interface, and then implement other features from your code.

The procedure is pretty simple, once you know how it works.

  1. Create the UI and add a widget based on the class you want to expand

In this case, I'm using a QFrame, to show how that class properties can be expanded. Just create your interface, and add a QFrame to the layout.

basic GUI

As you can see, the class reported from the Object Inspector is still a QFrame.

  1. Promote the widget

Right click on the widget you want to use for your class, and select Promote to..., then set the "Promoted class name" to the widget class name created before, and the "Header file" to the module name that contains that class. I saved the python file as promoted.py, so the field value will be promoted. Consider that header file paths are relative to the path from where the UI file will be loaded.

promote widget in Designer

Then click "Add" to create the new promoted widget "class", and finally "Promote" to officially promote it. After that, you can promote any widget to a previously set promoted class, as long as the base class is compatible: if you have another QFrame in your interface, you can right click on it and select the promoted class from the "Promote to" submenu.

Now the class displayed in the Object Inspector is TimeFrame.

The object class has changed

  1. Set object properties, save, create the code and run

Since we're using a QFrame, we can set its frame (this is because we also called the base class implementation in the paintEvent(event) method before).

Watch, a raised frame!

Now you just have to implement the base class for the main widget. Remember that each time a promoted widget is loaded, its "header file" will be loaded, which means that its code will always be run. That's why it's important that an if __name__ == '__main__': code block is placed in the file as long as it contains both the main "program" and the promoted widget class.

promoted.py code:

class Test(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        uic.loadUi('test.ui', self)

        self.timeEdit.timeChanged.connect(self.updateTimeFrame)

        self.timeEdit.setTime(QtCore.QTime.currentTime())

    def updateTimeFrame(self):
        self.timeFrame.time = self.timeEdit.time()


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Test()
    w.show()
    sys.exit(app.exec())

Now, obviously there won't be anything shown in Designer anyway, but as soon as we run our code, the promoted widget will be displayed as expected!

The promoted widget at work! Cool!

And now you can also know the actual time it took me to create this answer, cool ;-)

like image 66
musicamante Avatar answered Dec 22 '22 00:12

musicamante


To use a custom widget in Qt Designer there are the following options:

  • Qt Designer Plugin:

    You must create a class that inherits from QPyDesignerCustomWidgetPlugin that indicates the characteristics of how it will be displayed in Qt Designer. The advantage of this method is that you can modify the qproperties besides being able to create dialogs and menus that allow you to make the modification in a friendly way. There is an example in the PyQt5 source code in the "examples/designer/plugins" folder.

  • Promote widgets

    The advantage is that it is not necessary to create anything and the disadvantage is that it does not allow customization beyond what the base class plugin provides. In SO there are several examples: 1, 2, 3.

If you only want to position the widgets then just promote the widget, if instead you want to use Qt Designer to modify properties then a plugin is necessary.

like image 38
eyllanesc Avatar answered Dec 22 '22 00:12

eyllanesc