9. Graphische Benutzeroberfläche
In diesem Kapitel werden wir uns mit GUI-Programmen beschäftigen.
GUI steht für Graphical User Interface.
Es handelt sich also um Programme,
welche nicht nur mit Texteingabe und -ausgabe arbeiten,
sondern auch eine graphische Benutzeroberfläche haben.
In anderen Worten einfach Programme, so wie wir uns das heutzutage gewohnt sind.
Wir benutzen dazu das tkinter
-Modul,
welches in Python integriert ist.
Das Problem bei graphischen Oberflächen ist, dass wir das Programm nicht mehr einfach von oben nach unten durcharbeiten können. Wir haben ja keine Kontrolle darüber, auf welche Schaltfläche der Benutzer zu welchem Zeitpunkt gerade klickt. Darum benutzen wir für graphische Oberflächen sogenanntes ereignisbasiertes Programmieren. Dies bedeutet, dass das Programm nicht linear durchlaufen wird, sondern der Programmablauf durch bestimmte Ereignisse (bei uns werden das Klicks und Eingaben des Benutzers sein) beeinflusst wird.
Für unsere Programme heisst das, dass sie sich meistens in einer Endlosschleife
befinden, welche nur dafür zuständig ist, darauf zu warten,
dass der Benutzer etwas anklickt. Sobald dies geschieht
(also ein Ereignis eintrifft) wird ein bestimmter Teil des Programms
ausgeführt, welcher für die Aktion des Benutzers zuständig ist.
Sobald dies geschehen ist, geht das Programm wieder in die Endlosschleife
zurück und wartet auf weitere Aktionen.
Diese Schleife heisst in tkinter
mainloop()
.
Im Kapitel Einführung ins Programmieren bei der
letzten Aufgabe haben wir bereits ein erstes GUI-Beispiel gesehen.
Im folgenden werden wir Stück für Stück ein GUI erstellen. Beginnen werden wir mit einem einfachen Fenster und dann kontinuierlich weitere Komponenten kennenlernen.
9.1. Fenster
Um eine graphische Benutzeroberfläche zu gestalten,
brauchen wir als allererstes ein Fenster. Um ein solches zu erstellen
benutzen wir, wie bereits erwähnt, dass tkinter
-Modul von Python,
welches uns das nötige Werkzeug zur Verfügung stellt.
Dies geschieht wie gewohnt mit dem from-import
Befehl:
from tkinter import *
Nun erstellen wir mit Tk()
ein Fenster:
1from tkinter import * 2 3# Ein Fenster erstellen 4fenster = Tk() 5# Den Fenstertitle erstellen 6fenster.title("Nur ein Fenster") 7 8# In der Ereignisschleife auf Eingabe des Benutzers warten. 9fenster.mainloop()
Lassen wir das Programm laufen, bekommen wir ein leeres Fenster wie z.B.
Das ist natürlich noch nicht so interessant, denn dieses Fenster wartet nur, bis es geschlossen wird. Aber nun können wir beginnen, dieses Fenster mit weiteren Komponenten zu bepacken.
Bemerkung
Je nach Betriebssystem, welches man benutzt, wird das Fenster anders aussehen. Das obige Programm wurde auf einem Rechner mit Ubuntu/Linux ausgeführt und hat somit ein Fenster mit obigem Design erstellt. Auf einem Windows oder OS X Rechner wird es anders aussehen. Dies wird auch bei den folgenden Themen so sein und bleiben.
9.3. Geometrie-Manager
Wir haben oben gesehen, dass mit der Funktion pack()
die
Komponenten dem Fenster hinzugefügt wurden.
Doch wie sollen die einzelnen Komponenten auf dem Fenster
angeordnet werden?
Für solche Angelegenheiten bietet tkinter
drei verschiedene Geometrie-Manager an:
pack
grid
place
Die drei Layout-Manager pack, grid und place ordnen die verschiedenen Komponenten auf dem Fenster an, jeder auf seine Weise. Sie sollten jedoch nie im gleichen Fenster gemischt werden. Wie sie genau funktionieren und was die Unterschiede zwischen diesen drei Layout-Managern sind, sehen wir gleich.
9.3.1. pack
Pack ist der am einfachsten zu benutzende der drei Geometrie-Manager. Statt dass man präzise erklären muss, wo eine Komponente auf dem Bildschirm erscheinen soll, werden sie relativ zueinander positioniert. Die Details werden von pack automatisch bestimmt. Man kann nur wenig selber bestimmen und ist deshalb in seinen Möglichkeiten im Vergleich zu den anderen Geometrie-Managern eingeschränkt. Hier eine Möglichkeit:
Option:
Beschreibung:
side
Bestimmt auf welcher Seite die Komponente gepackt werden soll: TOP (default), BOTTOM, LEFT, or RIGHT.
Auf das obige Beispiel angewandt, können wir nun unsere Buttons und Labels nebeneinander statt untereinander platzieren:
# Die Komponenten nebeneinander platzieren anweisungs_label.pack(side=LEFT) change_button.pack(side=LEFT) info_label.pack(side=LEFT) exit_button.pack(side=LEFT)
Die führt zu folgendem Layout:
Möchte man die Komponenten aber nur etwas anders platzieren, wird es mit dem pack-Manager schon recht mühsam. Da ist der grid-Manager um einiges angenehmer.
9.3.2. grid
Der grid-Geometrie-Manager platziert die Komponenten in einer
2-dimensionalen Tabelle, die in Reihen und Spalten angeordnet ist.
Die Position einer Komponente wird durch einen row
und einen column
-Wert
bestimmt. Komponenten mit der selben column-Zahl und verschiedenen row-Zahlen
werden übereinander angeordnet. Entsprechend werden Komponenten mit der
selben row-Zahl und verschiedenen column-Zahlen in der selben Zeile platziert,
d.h. sie stehen nebeneinander, also rechts und links voneinander.
Mit der grid-Methode übergibt man den row- und den column-Wert, wo die Komponente platziert werden soll. Die Grösse braucht nicht definiert zu werden, da der Grid-Manager automatisch die Ausdehnungen für die benutzten Komponenten berechnet.
Option:
Beschreibung:
row
Bestimmt in welcher Zeile man die Komponente setzen möchte.
column
Bestimmt in welcher Spalte man die Komponente setzen möchte.
padx
Diese Option kann man gebrauchen, wenn man in der Horizontalen noch zusätzlich etwas Abstand an die jeweilige Komponente einbauen möchte.
pady
Analog wie padx, einfach in der Vertikalen
Mit dem Grid-Manager lässt sich nun einfach definieren, wo man die einzelnen Komponenten platzieren möchte.
# Label und entsprechender Button nebeneinander # mit etwas Abstand zur anderen Label-Button-Gruppe anweisungs_label.grid(row=0, column=0, pady = 20) change_button.grid(row=0, column=1, pady = 20) info_label.grid(row=1, column=0) exit_button.grid(row=1, column=1)
Und dies sieht dann so aus:
9.3.3. place
Der Place-Geometrie-Manager erlaubt das explizite Setzen der Position und der Grösse eines Fenster, entweder in absoluten Werten oder relativ zu anderen Komponenten.
Option:
Beschreibung:
x, y
Absolute Positionierung: Horizontale und vertikale Koordinate (in Pixel), in welcher die Komponente gesetzt wird.
relx, rely
Relative Positionierung: Horizontale und vertikale Platzierung bezüglich des Fensters, in welches die Komponente gepackt wird. Der Wert muss zwischen 0.0 und 1.0 liegen.
height
Höhe der Komponente bestimmen (in Pixel)
width
Breite der Komponente bestimmen (in Pixel)
Dieser Manager ist im Verlgeich zu den anderen beiden der aufwendigste. Man sollte ihn nur dann benutzen, wenn es nicht anders geht. Hier ein Beispiel:
# Zuerst definieren wir die Grösse des Fensters fenster.geometry("450x400") # Wir benutzen die absoluten Koordinaten um die Komponenten zu # setzen und definieren deren Grösse anweisungs_label.place(x = 0, y = 0, width=200, height=150) change_button.place(x = 220, y = 0, width=200, height=150) info_label.place(x = 100, y = 160, width=300, height=100) exit_button.place(x = 100, y = 260, width=300, height=100)
Ausgeführt sieht es dann folgendermassen aus:
Natürlich kann man noch viel mehr machen, doch das würde den Rahmen dieses Tutorials sprengen. Im Internet finden sich zu allen drei Geometrie-Managern noch viele weitere interessante Beispiele und Einstellungsmöglichkeiten.
9.4. Eingabefeld
Bis jetzt hatte der Benutzer nur die Möglichkeit, über Button-Klicks mit dem Programm zu interagieren. Nun werden wir sehen, wie der Benutzer über ein Feld eine Eingabe machen kann, welche das Programm danach verarbeiten kann. Die Syntax, um ein solches Eingabefeld zu erstellen, sieht folgendermassen aus:
eingabefeld = Entry(fenster, option, ... )
Wir sehen nun gleich ein Programm, welches im Fenster auf die Eingabe des Benutzers wartet und danach eine entsprechende Ausgabe generiert:
1from tkinter import * 2 3# Die folgende Funktion soll ausgeführt werden, wenn 4# der Benutzer den Button Klick me anklickt 5def button_action(): 6 entry_text = eingabefeld.get() 7 if (entry_text == ""): 8 welcome_label.config(text="Gib zuerst einen Namen ein.") 9 else: 10 entry_text = "Welcome " + entry_text + "!" 11 welcome_label.config(text=entry_text) 12 13fenster = Tk() 14fenster.title("Ich warte auf eine Eingabe von dir.") 15 16# Anweisungs-Label 17my_label = Label(fenster, text="Gib deinen Namen ein: ") 18 19# In diesem Label wird nach dem Klick auf den Button der Benutzer 20# mit seinem eingegebenen Namen begrüsst. 21welcome_label = Label(fenster) 22 23# Hier kann der Benutzer eine Eingabe machen 24eingabefeld = Entry(fenster, bd=5, width=40) 25 26welcom_button = Button(fenster, text="Klick me", command=button_action) 27exit_button = Button(fenster, text="Beenden", command=fenster.quit) 28 29 30# Nun fügen wir die Komponenten unserem Fenster hinzu 31my_label.grid(row = 0, column = 0) 32eingabefeld.grid(row = 0, column = 1) 33welcom_button.grid(row = 1, column = 0) 34exit_button.grid(row = 1, column = 1) 35welcome_label.grid(row = 2, column = 0, columnspan = 2) 36 37mainloop()
Wird das Programm ausgeführt, so bekommen wir folgendes Fenster:
In Zeile 7 im obigen Programm wird überprüft, ob der Benutzer etwas in das Feld geschrieben hat oder nicht. Dementsprechend sieht dann die Ausgabe im Fenster nach dem Klick auf den Klick me-Button jeweils anders aus:
Ohne Eingabe: |
|
Mit Eingabe: |