iX 10/2018
S. 130
Praxis
Desktop-Programmierung
Aufmacherbild

Oberflächen mit Python und dem Framework Qt5 erstellen

Mit Außenwirkung

Eine Anwendung mit Python ist schnell gemacht. Sollen andere damit arbeiten, braucht man dafür eine Bedienoberfläche, die auf unterschiedlichen Plattformen läuft. Hier bietet sich das Framework Qt an.

Bereits 1991 haben Haavard Nord und Eirik Chambe-Eng in ihrer Firma Trolltech das Framework Qt entwickelt, später hat Nokia es gekauft. Heute gehört es der Qt Group. Eigentlich ist Qt ein plattformübergreifendes C++-Framework, aber da sich solche Bibliotheken relativ gut in Python einbinden lassen, gingen die Leute der britischen Firma Riverbank Computing Limited ans Werk und erstellten mit PyQt eine Integration für Python.

In diesem Artikel geht es um Qt5 mit Python 3. Es gibt ältere Versionen, die sich in einigen Details von der aktuellen Release unterscheiden. Für PyQt existieren unterschiedliche Lizenzmodelle – von der kostenlosen GNU General Public License (GPL) bis hin zu einer kommerziellen Lizenz. Mehr dazu findet sich auf dem Webserver der Firma Riverbank (URL unter ix.de/ix1810130). Der Eigentümer des Frameworks Qt hat ebenfalls den Markt für Python erkannt und eine eigene Integration unter der Bezeichnung PySide in Arbeit.

Von C++ nach Python

Tabelle
Tabelle: Häufig genutzte Qt-Module

Mehr denn je ist es im mobilen Zeitalter erforderlich, dass man aus einer Codebasis performante Anwendungen für unterschiedliche Hardware erzeugen kann. Das plattformübergreifende C++-Framework leistet genau das – Embedded-Applikationen eingeschlossen. Qt enthält die üblichen, von Desktop-Anwendungen bekannten Oberflächenelemente. Darüber hinaus gibt es unzählige weitere Module, angefangen vom Datenbankzugriff über Netzwerkkommunikation bis hin zu Web- oder 3D-Oberflächen (siehe Tabelle). Dieser Artikel konzentriert sich auf gängige Desktop-Oberflächen, da diese die Basis für komplexere Benutzerschnittstellen bilden.

PyQt lässt sich wie gewohnt mit Pip

pip install pyqt5

oder Anaconda installieren:

conda install pyqt5

Auf einigen Linux-Plattformen, etwa Raspberry Pi, gibt es eigene Pakete für die Installation:

sudo apt-get install python3-pyqt5

Normalerweise verwendet ein Python-Programmierer die Namenskonvention, die die Entwickler der Programmiersprache in PEP8 vorgeschlagen haben. Da Qt aus der C++-Welt stammt und PyQt die Namen aller Objekte eins zu eins übernommen hat, kommt man bei der Namenskonvention um etwas „Mischmasch“ zwischen beiden Welten nicht herum.

Funktional und objektorientiert

Listing 1: first.py

 1 from PyQt5.QtWidgets import *
 2 import sys

 3 app = QApplication(sys.argv)
 4 
 5 window = QWidget()
 6 window.setGeometry(0,0,500,500)
 7 window.setWindowTitle('First')
 8 
 9 window.windowTitle()
10 
11 window.show()
12 
13 sys.exit(app.exec_())

Listing 1 zeigt das Grundgerüst einer Qt-Desktop-Anwendung in Python. Sie besteht lediglich aus ein paar Funktionsaufrufen, deren zentrales Objekt von der Klasse QApplication abgeleitet und im Modul PyQt5.QtWidgets zu finden ist. Es repräsentiert die gesamte Anwendung.

Beim Erzeugen bekommt das neue QApplication-Objekt als Parameter eine Liste der Argumente (sys.argv), mit denen der Anwender das Python-Programm gestartet hat und die vor allem das Erscheinungsbild der Anwendung beeinflussen. QApplication enthält die Hauptschleife, die sich um die Abarbeitung der Ereignisse kümmert, die es in einer Oberfläche geben kann – etwa ein Mausklick-Event in einem Fenster –, und sucht den möglichen Empfänger. Die Basisklasse für alle Oberflächenelemente in Qt ist QWidget.

Alle Methoden, die wie setGeometry mit „set“ beginnen, ändern die Eigenschaften eines Objekts, wie hier in Zeile 7 den Titel in der oberen Fensterleiste. Diese Namenskonvention hält Qt in allen Modulen durch. Um im Gegenzug den Wert eines Attributs zu ermitteln, gibt es allerdings keine Methode „getWindowsTitle“, sie heißt einfach nur window.windowTitle().

Da die Methode exec_ der Klasse QApplication verhindert, dass das Fenster nur kurz aufflackert, bleibt es so lange sichtbar, bis der Anwender die Applikation beendet oder ein Fehler zum Abbruch führt. Den Rückgabewert, der anzeigt, ob ein Fehler aufgetreten ist oder das Programm normal beendet wurde, sollte das Programm an die Methode sys.exit weitergeben, die ihn ihrerseits an das Betriebssystem weiterreicht. Hat ein Skript das Programm gestartet, kann dieses auf Fehler reagieren. In der C++-Bibliothek von Qt heißt diese gerade beschriebene Methode nur exec – ohne den Unterstrich am Schluss. Der kam hinzu, weil dieser Name in Python bereits vergeben war.

Listing 2: first_obj.py

 1 from PyQt5.QtWidgets import *
 2 from PyQt5.QtGui import *
 3 
 4 import sys
 5 
 6 class FirstWindow(QWidget):
 7 
 8     def __init__(self):
 9         super().__init__()
10 
11         button = QPushButton('Close', self)
12         button.move(50, 50)
13         button.setToolTip("Fenster schließen")
14         button.clicked.connect(self.close)
15 
16         self.setGeometry(400,400,200,200)
17         self.setWindowTitle('FirstWindow')
18         self.setWindowIcon(QIcon("testicon.png"))
19 
20         self.show()
21 
22     def closeEvent(self, event):
23         reply = QMessageBox.question(self, 'Nachricht', "Soll die Anwendung geschlossen werden?",
24                                      QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
25 
26         # Variante 1
27         if reply == QMessageBox.Yes:
28             event.accept()
29         else:
30             event.ignore()
31 
32 
33 if __name__ == "__main__":
34     app = QApplication(sys.argv)
35     mainWindow = FirstWindow()
36     app.quit
37     sys.exit(app.exec_())
Qt stellt die Oberfläche an die jeweilige Plattform angepasst dar – hier von Raspberry über Windows bis macOS (Abb. 1).

Bei größeren Anwendungen empfiehlt Qt natürlich eine objektorientierte Vorgehensweise (Listing 2). Für ein eigenes Fenster mit Bedienungselementen leitet man dafür eine eigene Klasse von QWidget ab (Zeile 6 bis 9). Beim Initialisieren eines neuen Fensters ist es sinnvoll, über super().__init__() zunächst das Elternobjekt der Klasse QWidget zu initialisieren. Darauf folgt die Hauptaufgabe der Methode __init__ – das Erzeugen aller im Fenster zu sehenden Oberflächenelemente. Die Methode QPushButton liefert einen Befehlsknopf zurück, der ebenfalls von der Klasse QWidget abgeleitet ist, wie in diesem Fall der Push-Button, der zum Schließen des Fensters dient.

Alle Objekte der Klasse QWidget haben grundsätzlich ein Parent-Element. Sollte man vergessen, es anzugeben (in diesem Fall self für den QPushButton), würde es nirgends erscheinen. Die einzige Ausnahme davon sind eigenständige Fenster wie FirstWindow.

Ereignisse lösen Signale aus

Wenn es um die Verarbeitung von Ereignissen geht, findet man in der Qt-Dokumentation die Begriffe Signale und Slots. Das Framework erzeugt ein bestimmtes Signal, in diesem Fall button.clicked, wenn dieses Ereignis eintritt (Zeile 14). Unter einem Slot versteht Qt eine Methode beziehungsweise irgendetwas, das es aufrufen kann, in Python Callable genannt. Verbindet der Entwickler ein Signal mit einem Slot, ruft Qt die zugehörige Methode auf, sobald das Ereignis (Signal) eintritt. Der Entwickler definiert dies einmal, um den Rest kümmert sich Qt. Wichtig ist allerdings, dass nach dem connect die Methode self.close ohne Klammern steht, da hier der Verweis auf die Methode und nicht ihr Aufruf benötigt wird.

Neben der Verarbeitung von Events mit connect kennt das Framework vordefinierte Methoden, die es aufruft, wenn ein bestimmtes Ereignis eintritt. Soll das eigene Programm an dieser Stelle etwas tun, kann der Entwickler die vorhandene Methode überschreiben. So erlaubt es die vordefinierte Methode closeEvent der Klasse QWidget in den Zeilen 22 bis 25, dass der Anwender vor dem Schließen des Fensters bestätigt, dass er das wirklich will.