Python GUI mit PyQt6 Tutorial Teil 29

Grafische Benutzeroberflächen (engl. Graphical User Interface, GUI) kommen immer da zu Einsatz, wo eine dedizierte Benutzergruppe ein Programm benutzen. Im Gegensatz zu Webanwendungen benötigen sie keine Serverinfrastruktur, denn sie werden direkt auf dem Rechner des Anwenders ausgeführt.

Python Logo (CC-BY-SA The people from the Tango! project / Wikipedia)
Python Logo (CC-BY-SA The people from the Tango! project / Wikipedia)

Der Nachteil ist dann allerdings, dass du einen Updatemechanismus benötigst, um eine neue Programmversion auf alle Rechner zu übertragen.

1Python Programmierkurs
2Python: Methoden
3Kontrollstrukturen
4Strings in Python
5Container
6Objekte in Python
7Module
8Exceptions in Python
9Typkonvertierung
10Python und Dateien
11Datum und Zeit mit Python verarbeiten
12Multithreading
13Netzwerk in Python
14Logging in Python
15GPIO
16Automatische Tests
17Datenbanken mit Python
18Python: Generatoren und List Comprehension
19Python: Webseiten mit Flask
20Python virtuelle Umgebungen
21Interrupts & Signale
22NumPy
23Matplotlib
24match
25Reguläre Ausdrücke
26lambda Funktionen
27logging.config
28Decorators
29GUI

GUI

Mit dem Fortschritt der Computerentwicklung forschte Douglas Engelbart in den 1950er und 1960er Jahren an der Mensch-Maschinen-Interaktion. Darauf baute Ivan Sutherland auf und entwickelte 1962 mit Sketchpad eine grafische Benutzeroberfläche, die mit einem „Lightpen“ bedient wurde.

Die Maus als Eingabegerät wurde dann von Engelbert vorgestellt. Der Prototyp bestand aus einem Holzkasten mit zwei Rädern.

Bei Xerox PARC stellte 1972 mit dem Xerox Alto den ersten Computer mit grafischer Benutzeroberfläche vor, der schon über die uns heute vertrauten Dinge wie Icons, Fenster und Menüs verfügte.

Auf dem Massenmarkt setzten sich die GUIs ab 1984 durch, als Apple den ersten Macintosh herausbrachte. 1985 folgte die erste Version von Windows und auch Heimcomputer wie der Commodore Amiga und der Atari ST wurden mit einem grafischen Desktop ausgeliefert.

Mein persönlicher erster Kontakt mit einem GUI war auf dem C64C, bei dem als alternatives OS GEOS auf einer eigenen Diskette beilag. Allerdings war die Bedienung per Joystick eher anstrengend.

Heutzutage begegnen uns GUIs überall; auf dem Smartphone, Fahrscheinautomaten, Bestellterminals. Neben GUIs im klassischen Sinn werden in den letzten Jahren immer häufiger Webanwendungen benutzt, die im Browser laufen.

PyQt6

Mit PyQt6 steht uns ein Framework zur Verfügung, relativ einfach GUI Oberflächen zu entwickeln. Dies will ich dir in diesem Tutorial Teil näher bringen.

Projekt einrichten

Zunächst legen wir uns eine Arbeitsumgebung an und installieren dahinein die benötigten Module.

virtualenv pyqt6
cd pyqt6
source bin/activate
pip install pyqt6

Auf meinem Debian Rechner musste ich noch mit

sudo apt install libxcb-cursor0 -y

ein zusätzliches Paket installieren, umPyQt6 vollständig zum Laufen zu bringen.

Qt und Widgets

Ein Widget ist ein Bedienelement im GUI. Es gibt ein PushButtonWidget für klickbare Buttons, ein LineEditWidget für die Texteingabe. Alles, was du für ein Userinterface benötigst, findest du als Widget in der PyQt6 Library.

QApplication

Die zentrale Klasse in PyQt6 ist QApplication,

Fenster erzeugen

Jetzt können wir direkt starten und bauen unser erstes Fenster. Am einfachsten ist dies mit der QT6Applikation. In ihr werden die Einstellungen verwaltet und die Event-Loop bearbeitet. Deshalb startet jede PyQt6 Anwendung mit einer Instanz von QApplication

app = QApplication(sys.argv)

Der Klasse kann eine Liste von Parametern übergeben werden. Dies können die Kommandozeilenparameter des Pythonprogramm sys.argv oder auch einfach nur eine leere Liste [] sein.

einfaches Fenster.

Die QApplication allein zeigt aber auf dem Bildschirm noch nicht an. Sie wartet nur auf Ereignisse eines Widgets, um dies zu bearbeiten. Deshalb erzeugen wir mit

window = QWidget()
window.show() 

Ein einfaches Widget, dass ein Fenster mit Rahmen und dessen Dekorationselementen („Schließen“,“Verkleinern“ und „Vergrößern“) aufspannt. Das Fenster öffnet sich nicht von allein., deshalb müssen wir dies mit der show() Methode veranlassen.

Das erste Fenster mit Rahmen und Standardelementen, aber ansonsten leer
Olli Graf - raspithek.de
firstwindowCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . loading=
Das erste Fenster mit Rahmen und Standardelementen, aber ansonsten leer

Hier ist das komplette Programm. Es läuft so lang, bis du das [X] zum Schließen des Fensters drückst.

#! /usr/bin/python
#Datei: window.py

# Die benötigten Qt Widgets
from PyQt6.QtWidgets import QApplication, QWidget

# Für die Kommandozeilenparameter
import sys

# QTApplication instanziieren. Die Kommandozeilenparameter geben wir
# mit.
app = QApplication(sys.argv)

# Window Widget erzeugen
window = QWidget()
window.show()  # Das Fenster muss immer manuell angzeigt werden.

# Wvent-Loop starten.
app.exec()


# So lang die Event-Loop läuft kommen wir hier nicht hin,
# sie kann durch den "Schließen" Button des Fensters unterbrochen werden.

PushButton

Als erstes Erfolgserlebnis ist das leere Fenster ganz nett, wird aber schnell uninteressant, da du nicht viel damit machen kannst. Daher ersetzen wir im nächsten Schritt QWidget durch QPushButton.

#! /usr/bin/python
#Datei: pushbutton.py

import sys
from PyQt6.QtWidgets import QApplication, QPushButton

app = QApplication(sys.argv)

window = QPushButton("Bitte klicken")
window.show()

app.exec()

Das Fenster ist dann genau so groß wie der PushButton, dessen Abmessung sich am enthaltenen Text orientiert.

klickbarer Button
Olli Graf - raspithek.de
pushbuttonCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . loading=
klickbarer Button

Fenstergröße

Das ganze sieht unschön aus, da das Fenster den PushButton eng umschließt, daher werden wir jetzt eine eigene Klasse für das Fenster benutzen.

#! /usr/bin/python
# Datei: sizewindow.py

import sys
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton


# abgeleitet von  QMainWindow können wir unser GUI besser einstellen und
# z.B. die Dimensionen des Fensters ändern.
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Fenstergröße")

        button = QPushButton("Bitte klicken")

        self.setFixedSize(QSize(400, 300))

        # der Button sitzt als zentrales Widget im Fenster.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Unsere Klasse MainWindow leitet von QMainWindow ab und erbt dadurch alle seine Werte und Methoden. Mit

self.setFixedSize(QSize(400, 300))

setzen wir die Ausdehnung des Fensters auf 400 * 300px. Nebenbei setzen wir mit self.setWindowTitle("Fenstergröße") noch einen vernünftigen Titel in die Titelzeile des Fensterrahmens.

Olli Graf - raspithek.de
windowsizeCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . loading=
Button und Fenster sind jetzt 400 * 300px groß.

Auf Klick reagieren

Was nutzt uns ein Buttonklick, wenn das Programm nicht darauf reagiert. Das werden wir als nächstes angehen.

Jedes Widget verfügt über verschiedene Signale, die bei einer Interaktion angestoßen werden. Dort können wir eine eigene Methode

einklinken. Daher schreiben wir in unsere MainWindow Klasse eine Methode, die genau das tut

def handle_button_click(self):
        print("Button geklickt")

Diese Methode gibt nur auf der Console aus, dass der Button geklickt wurde. Hier könnte aber auch ein weiteres Fenster geöffnet werden oder die Hälfte von Springfield vom Strom getrennt werden. 🙂

Wir können jetzt diese Methode in den PushButton einklinken, damit sie beim Klick auf den Button angestossen wird.

button.clicked.connect(self.handle_button_click)

Das Signal dazu heißt hier clicked und an die connect Methode übergeben wir den Zeiger auf unsere Handler Methode.

nach Klick auf den Button druckt unsere Methode den Text in die Console.
Olli Graf - raspithek.de
handle_clickedCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . loading=
nach Klick auf den Button druckt unsere Methode den Text in die Console.

Texteingabe

Im nächsten Schritt wollen wir die Möglichkeit schaffen, einen Text mit einem QLineEditWidget einzugeben und bei jedem Tastendruck den Text in ein darunterliegendes QLabel zu setzen.

#! /usr/bin/python
#Datei: lineEdit.py

from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QVBoxLayout, QWidget
import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Eingabe mit LineEdit")

        self.label = QLabel()

        self.input = QLineEdit()
        self.input.textChanged.connect(self.label.setText)

        layout = QVBoxLayout()
        layout.addWidget(self.input)
        layout.addWidget(self.label)

        container = QWidget()
        container.setLayout(layout)

        # Set the central widget of the Window.
        self.setCentralWidget(container)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

Neu ist hier das QVBoxLayout, dies ist ein LayoutManager, der die Widgets, die zu ihm da zugefügt werden vertikal untereinander anordnet. Wir haben ein QLineEdit und ein QLabel in ihm untereinander angeordnet. Beim Signal textChanged lassen wir direkt die Methode setText des Labels aufrufen.

Wenn ich jetzt einen Text in das QLineEdit eingebe, wird er sofort in das Label darunter gesetzt.

Der eingegebene Text erscheint sofort im Label.
Olli Graf - raspithek.de
lineditCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . loading=
Der eingegebene Text erscheint sofort im Label.

Layoutmanager

Über die Layoutmanager will ich noch ein paar Worte verlieren. Sie organisieren deine Widgets innerhalb des Fensters, jeder auf seine spezifische Weise.Es ist möglich, verschiedene Layoutmanager innerhalb eines Fensters verschachtelt zu benutzen. So kannst in QVBoxLayout ein QFormLayout für Formulardaten und ein QHBoxLayout für die Buttonleiste schachteln. Dadurch stehen die Eingabefelder und die Buttons untereinander, während mehrere PushButtons nebeneinander angeordnet werden. Mit ein bisschen Übung bekommst du ein Gefühl dafür, in welcher Situation du einen bestimmten Layoutmanager verwenden solltest.

KlasseDarstellungsformBeschreibung
QVBoxLayoutvertikalOrdnet Widgets von oben nach unten in einer Spalte an
QHBoxLayouthorizontalPlatziert Widgets nebeneinander in einer Reihe
QGridLayoutRasterOrdnet Widgets tabellenartig in einem Raster mit Zeilen und Spalten an.
QFormLayoutFormularIdeal für Formulare, da es Widgets in zwei Spalten (Beschriftung + Eingabefeld) organisiert
QStackedLayoutgestapeltVerwaltet mehrere Layouts übereinander, zeigt aber nur eines gleichzeitig an (z. B. für Tabs oder Seitenwechsel).

Login-dialog

Zum Abschluss wollen wir noch etwas anwendungsbezogenes machen. Ein Fenster zum Login in ein System. dazu benötigen wir jeweils ein QLineEdit für den Usernamen und das Passwort. Sowie zwei Buttons für den Login und die Neuanmeldung. Ich verwende hier QGridLayout, um das zu demonstrieren. Außerdem möchte ich dein Augenmerk noch auf den „Passwort vergessen“ Button lenken, der sich mit CSS-Styles farbig von den anderen Buttons absetzt. Als Bonus zeige ich noch, wie du ein Bild im Fenster anzeigen kannst.

#! /usr/bin/python
#Datei: lineEdit.py

from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QPushButton, QGridLayout, QWidget
from PyQt6.QtGui import QPixmap

import sys


class MainWindow(QMainWindow):
  def __init__(self):
    super().__init__()

    self.setWindowTitle("Login")

    layout = QGridLayout()
    layout.setContentsMargins(20, 20, 20, 20)
    layout.setSpacing(10)

    self.user_logo_pixmap =QPixmap('./user.jpg')
    self.user_logo_label = QLabel()
    self.user_logo_label.setPixmap(self.user_logo_pixmap)

    layout.addWidget(self.user_logo_label,1,1)



    self.user_label = QLabel('Username:')
    self.password_label = QLabel('Passwort:')
    self.username_input = QLineEdit()
    self.password_input = QLineEdit()
    self.password_input.setEchoMode(QLineEdit.EchoMode.Password)

    layout.addWidget(self.user_label,2,0)
    layout.addWidget(self.username_input,2,1,1,2)

    layout.addWidget(self.password_label,3,0)
    layout.addWidget(self.password_input,3,1,1,2)

 #Buttons
    self.register_button = QPushButton("Register")
    layout.addWidget(self.register_button, 4, 1)

    self.login_button = QPushButton("Login")
    self.login_button.clicked.connect(self.handle_login_button)
    self.register_button.clicked.connect(self.handle_register_button)

    layout.addWidget(self.login_button, 4, 2)

# Password vergessen
    self.forgot_pw_button = QPushButton('Passwort vergessen')
    self.forgot_pw_button.setStyleSheet('QPushButton {background-color: #A3C1DA; color: blue;}')    
    layout.addWidget(self.forgot_pw_button,5,2)
    container = QWidget()
    container.setLayout(layout)

     # Set the central widget of the Window.
    self.setCentralWidget(container)

  def handle_register_button(self):
    print('Register Button')

  def handle_login_button(self):
    print(f'Login mit {self.username_input.text()} and {self.password_input.text()}')

  def handle_forgot_pw_button(self):
    print('Forgot PW')
Olli Graf - raspithek.de
loginwindowCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . loading=

Den gesamten Code des Pythontutorials kannst du dir mit

git clone https://raspithekgit.srv64.de/raspithek/pythonkurs.git

auf deinen Rechner laden.

Schreibe einen Kommentar

Insert math as
Block
Inline
Additional settings
Formula color
Text color
#333333
Type math using LaTeX
Preview
\({}\)
Nothing to preview
Insert
Creative Commons License
Except where otherwise noted, the content on this site is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Olli Graf - raspithek.de
WordPress Cookie Hinweis von Real Cookie Banner