Einbindung von Custom Widgets
PyQt6, die aktuelle Version der Qt-Bibliothek für Python, lässt sich ohne Probleme mit dem QtDesigner zusammen nutzen.
Mit dem QtDesigner lässt sich die Bedienoberfläche einer Python-Applikation in einem grafischen Tool relativ einfach zusammensetzen. Es hakt zwar an der einen oder anderen Stelle, aber notfalls bleibt immer noch das händische Editieren der vom Designer erzeugten *.ui
Datei.
In Python lässt sich dann die Beschreibungsdatei der Oberfläche (hier: myGui.ui
) mit einer einzigen Anweisung (unten fett dargestellt) laden:
from PyQt6 import QtWidgets, uic
from myWidgets import myWidget # Custom Widget
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi(join(cwd(), 'myGui.ui'), self)
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec()
Auch benutzerdefinierte Bedienelemente, in Qt „Custom Widgets” genannt, lassen sich relativ einfach nutzen.
Der Designer „kennt” aber diese Widgets natürlich zunächst nicht. Deswegen platziert man an die Stelle, die später das eigene selbst programmierte Widget einnehmen soll, ein möglichst ähnliches Standard-Widget.
Im Kontextmenü des Widgets klickt man dann auf „Als Platzhalter für benutzerdefinierte Klasse festlegen
” und trägt den Namen der Custom-Klasse unter „Klassenname
” und „Include-Datei
” ein.

Nach einem Klick auf „Hinzufügen” und „Anwenden” wird in der Übersicht schon die neue Klasse für dieses Widget angezeigt. Damit Python die Custom-Widget-Klasse beim Laden der .ui-Datei findet, muss sie in der Datei mit dem uic.loadUi
Aufruf importiert werden (siehe oben in Zeile 2).
Beim späteren Laden der Designer-Datei im Python-Programm versucht der Qt-Lader, alle Attribute, die man im Designer gesetzt hat, mit den entsprechenden getattr/setattr
Methoden der Custom-Klasse zu setzen. Diese Attribute müssen in der Custom-Klasse existieren, sonst bricht das Laden mit Fehlern ab.
Dynamic Properties
Etwas verwirrend ist, dass im Designer weiterhin die Attribute der Platzhalterklasse angezeigt werden, obwohl die Custom-Klasse diese Eigenschaften eventuell gar nicht besitzt. Setzt man beispielsweise im Designer die readOnly
-Eigenschaft, erwartet der Qt-Lader, dass die Custom-Klasse auch eine setReadOnly
-Methode besitzt.
Den anderen Fall, nämlich dass die Platzhalter-Klasse Eigenschaften des Custom Widget nicht besitzt, kann man mit den „Dynamic Properties” lösen.

In der Objektanzeige findet man neben dem Filter für die Eigenschaften ein Plus-Zeichen. Nach einem Klick darauf muss zunächst der Datentyp der Eigenschaft gewählt werden, dann öffnet sich ein Fenster, in dem man den Namen der Eigenschaft eintragen kann.

Nach dem Klick auf „OK” findet man die neuen dynamischen Eigenschaften am Ende der Objektanzeige.

In Python gestaltet sich allerdings der Zugriff auf diese Eigenschaften schwieriger als erwartet. Es reicht nicht, diese Eigenschaften als Python-Properties des Custom Widgets anzulegen; Qt findet diese beim Anlegen der Objektinstanz nicht.
Stattdessen muss man das pyqtProperty
Makro bemühen, um PyQt die Objekteigenschaften bekannt zu machen. Dem Makro gibt man den Datentyp und die Referenzen auf Getter und Setter der Property als Parameter mit (weitere Parameter sind möglich, vollständige Beschreibung siehe PyQt-Dokumentation).
from PyQt6.QtCore import pyqtProperty
...
class myCustomWidget(QWidget):
def get_read_only(self):
return self._readOnly
def set_read_only(self, ro):
self._readOnly = ro
readOnly = pyqtProperty(bool, get_read_only, set_read_only)
...
Auf diese Weise kann man die Eigenschaft wie eine normale Objekteigenschaft nutzen, während im Hintergrund der Getter den Inhalt der im QtDesigner definierten Eigenschaft ausliest oder der Setter sie ändert.
Vorsicht: der Wert der Dynamic Property wird gnadenlos auf den in pyqtProperty angegebenen Wert gecastet. Aus einem nicht leeren String wird also z.B. immer True
, wenn bool
angegeben wird.
Spezielle Typen wie die enums
der Qt::Alignment
Flags lassen sich im Designer nicht direkt eingeben, da sie in der Liste der auswählbaren Typen fehlen. Dafür müsste man ein Designer-Plugin für das Custom Widget erzeugen. Als Ausweg bleibt die Nutzung eines Strings und das Mapping in der Setter-Methode.
alignment_flag = {
'right': Qt.AlignmentFlag.AlignRight,
'left': Qt.AlignmentFlag.AlignLeft,
'center': Qt.AlignmentFlag.AlignCenter,
'top': Qt.AlignmentFlag.AlignTop,
'bottom': Qt.AlignmentFlag.AlignBottom,
'middle': Qt.AlignmentFlag.AlignVCenter,
}
...
def get_alignment(self):
return self._alignment
def set_alignment(self, align):
if align in alignment_flag:
self._alignment = align
self.input.setAlignment(alignment_flag[align])
alignment = pyqtProperty(str, get_alignment, set_alignment)