39.4 Signale und Slots 

Beim Schreiben eines Programms mit grafischer Benutzeroberfläche wird das Prinzip der ereignisgesteuerten Programmierung angewandt. Dieses Prinzip sieht nicht vor, dass ein Programm sequenziell von oben nach unten abgearbeitet wird, sondern führt beim Auftreten bestimmter Ereignisse einen Code-Abschnitt aus, der vom Programmierer für dieses Ereignis vorgesehen wurde. Die Anwendung der ereignisgesteuerten Programmierung ist im Falle einer grafischen Benutzeroberfläche notwendig, da hier der Benutzer das Programm steuert und nicht das Programm den Benutzer, wie es bei einer Konsolenanwendung der Fall war. Der Benutzer steuert das Programm durch seine Eingaben, die im Programm in Form von Ereignissen ankommen. Wann und in welcher Reihenfolge der Benutzer seine Eingaben macht, ist durch das Programm nicht vorgegeben.
In Qt finden sich zwei Techniken der ereignisgesteuerten Programmierung: zum einen Events und zum anderen Signale und Slots. Beide Techniken werden Sie im Folgenden kennenlernen.
Jedes Widget in der grafischen Benutzeroberfläche wird programmintern durch eine Instanz einer entsprechenden Qt-Klasse repräsentiert. Jede dieser Klassen bietet Eventhandler an. Das sind Methoden, die der Programmierer in einer abgeleiteten Klasse überschreiben kann, um beim Eintreten eines speziellen Ereignisses (engl. event) eigenen Code ausführen zu können. Events werden nur für wenige Ereignisse verwendet, die aber häufig eintreten. Ein Beispiel für ein solches Ereignis ist das paintEvent, das immer dann eintritt, wenn der Inhalt eines Widgets neu gezeichnet werden muss. Das Widget reagiert auf das Event durch Ausführung seines Eventhandlers. Dies kann unter Umständen sehr häufig passieren. Ein Beispiel für die Implementation eines Eventhandlers finden Sie in Abschnitt 39.6 im Zusammenhang mit der Zeichenfunktionalität von Qt.
Neben den Events bietet das Qt-Framework Signale und Slots für die Behandlung von Ereignissen an. Dieses zentrale Konzept zur Kommunikation von Qt-Objekten ist womöglich das größte Unterscheidungsmerkmal zwischen Qt und anderen GUI-Toolkits.
Ein Signal wird von einem Widget gesendet, wenn ein bestimmtes Ereignis, beispielsweise eine Benutzereingabe, eingetreten ist. Es gibt für jedes Widget in Qt vordefinierte Signale für die meisten Anwendungsfälle. Zusätzlich ist es möglich, eigene Signale zu selbst bestimmten Ereignissen zu senden.
Um ein Signal zu empfangen, muss ein Slot (dt. »Steckplatz«) eingerichtet werden. Ein Slot ist eine Funktion oder Methode, die immer dann aufgerufen wird, wenn ein bestimmtes Signal gesendet wird. Dazu muss ein Slot mit einem Signal verbunden werden. Es ist möglich, einen Slot mit mehreren Signalen zu verbinden.
Im Folgenden wird das Beispiel des letzten Kapitels zu einer sinnvollen Anwendung erweitert. Diese Anwendung soll die Daten, die der Benutzer in den Dialog eingibt, in das parallel geöffnete Konsolenfenster ausgeben, sofern der Benutzer die Eingaben durch Anklicken der OK-Schaltfläche bestätigt. Beim Klick auf Abbrechen sollen keine Daten ausgegeben werden.
import sys
from PyQt5 import QtWidgets, QtCore, uic
class MeinDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = uic.loadUi("hauptdialog.ui", self)
# Slots einrichten
self.ui.buttonOK.clicked.connect(self.onOK)
self.ui.buttonAbbrechen.clicked.connect(self.onAbbrechen)
def onOK(self):
# Daten auslesen
print("Vorname: {}".format(self.ui.vorname.text()))
print("Nachname: {}".format(self.ui.nachname.text()))
print("Adresse: {}".format(self.ui.adresse.toPlainText()))
datum = self.ui.geburtsdatum.date().toString("dd.MM.yyyy")
print("Geburtsdatum: {}".format(datum))
if self.ui.agb.checkState():
print("AGBs akzeptiert")
if self.ui.katalog.checkState():
print("Katalog bestellt")
self.close()
def onAbbrechen(self):
print("Schade")
self.close()
Im Konstruktor der Dialogklasse MeinDialog werden die clicked-Signale für den OK- und den Abbrechen-Button mit den dafür vorgesehenen Slots verbunden. Die clicked-Signale werden immer dann ausgelöst, wenn der Benutzer die Schaltfläche anklickt. Die Signale, die ein Widget bereitstellt, sind als Attribute in der entsprechenden Widget-Instanz enthalten. Um ein Signal mit einem Slot zu verbinden, wird die Methode connect des Signals aufgerufen und der Slot als Parameter übergeben. Im Beispielprogramm werden die clicked-Signale der Schaltflächen ui.buttonOK und ui.buttonAbbrechen mit den Slots onOK und onAbbrechen verbunden. Diese werden ab jetzt immer dann aufgerufen, wenn der Benutzer die assoziierte Schaltfläche anklickt.[ 193 ](Dieser komfortable Weg, Signale und Slots zu verbinden, ist eine Neuerung von PyQt und im C++-Framework Qt so nicht möglich. Sollten Sie mit Qt bereits vertraut sein, sei Ihnen gesagt, dass die klassische Variante des Verbindens von Signalen und Slots auch mit PyQt funktioniert, allerdings den Nachteil mit sich bringt, dass die C++-Schnittstelle der Signale angegeben werden muss. )
[»] Hinweis
In diesem Fall sind die verbundenen Signale parameterlos. Es gibt komplexere Signale, die einen oder mehrere Parameter übergeben. In einem solchen Fall muss auch der verbundene Slot eine entsprechende Anzahl von Parametern erwarten.
In der Methode onOK sollen die Eingaben des Benutzers aus den verschiedenen Widgets des Hauptdialogs ausgelesen werden. Jedes dieser Widgets wird durch eine Instanz einer entsprechenden Qt-Klasse repräsentiert. Die Namen dieser Instanzen haben wir zuvor im Qt Designer festgelegt. Welches Widget dabei welchen Namen bekam, können Sie in der Tabelle in Abschnitt 39.3.3, »Entwicklungsprozess«, nachlesen.
Über die angesprochenen Attribute können wir den Inhalt der Steuerelemente auslesen. Wie dies geschieht, ist von Widget zu Widget verschieden. So kann beispielsweise auf den Inhalt eines Line-Edit-Widgets über die Methode text zugegriffen werden. Erwähnenswert ist noch, dass die Methode date der Date-Edit-Instanz geburtsdatum das gespeicherte Datum nicht direkt in Form eines Strings, sondern in Form einer QDate-Instanz zurückgibt. Diese muss durch Aufruf der Methode toString in einen String konvertiert werden. Zum Schluss, nachdem alle Daten ausgelesen und ausgegeben wurden, wird der Dialog durch Aufruf der Methode close geschlossen.
Im zweiten Slot, onAbbrechen, sind, abgesehen vom Schließen des Dialogs, keine weiteren Operationen notwendig.
app = QtWidgets.QApplication(sys.argv)
dialog = MeinDialog()
dialog.show()
sys.exit(app.exec_())
Bei dem Code, der die Applikations- und Dialogklasse instanziiert und die Main Event Loop startet, handelt es sich um denselben, der schon im letzten Beispielprogramm seinen Dienst getan hat.
[»] Hinweis
Wie das Beispiel demonstriert, öffnet auch ein Python-Programm mit grafischer Benutzeroberfläche unter Windows immer noch ein Konsolenfenster, in das mittels print geschrieben werden kann.
Das mag in einigen Fällen wünschenswert sein, ist jedoch häufig störend, wenn die Kommunikation mit dem Benutzer vollständig über die grafische Oberfläche ablaufen soll. Wenn Sie nicht möchten, dass ein Konsolenfenster geöffnet wird, können Sie die Dateiendung der Python-Programmdatei von .py nach .pyw ändern. Dann werden alle Ausgaben in die Konsole unterdrückt, und es wird kein Konsolenfenster geöffnet.