Kapitel 1 Programmieren für Einsteiger

Hier sind Sie richtig, wenn Sie noch nie programmiert haben. Erst einmal haben Sie die richtige Sprache erwischt, aus meiner Sicht natürlich das richtige Buch und vor allem nun auch noch den richtigen Abschnitt.

Sollten Sie allerdings schon Programmiererfahrung haben, werden Sie sich hier vielleicht langweilen. Und bevor Sie noch einschlafen und mir den Abschnitt verschnarchen, blättern Sie lieber zum nächsten Abschnitt weiter.

1.1 Was tut eigentlich ein Programmierer?

Programmieren ist eine schöne Beschäftigung. Sie können herumkommandieren, ohne dass es Ihnen irgendjemand übel nimmt. Anstatt selbst zu arbeiten, überlassen Sie dies dem Computer. Sie tun Dinge, die andere Leute nicht können. Und Sie erschaffen kreativ eine virtuelle Welt.

Ein Programm besteht aus Anweisungen, die Sie dem Computer vorlegen. Und er wird sie geduldig genau so ausführen, wie Sie sie formuliert haben, also auch mit den Fehlern, die Sie einbauen. Also werden Sie irgendwann nach diesen Fehlern suchen müssen. Der frustrierende Teil der Arbeit ist, dass Sie immer auf Ihre eigenen Fehler stoßen werden.

Da ein Computer von Haus aus nicht fantasiebegabt ist, sind die Anweisungen sehr einfach und klar. Man merkt an dieser Stelle, dass der Computer ursprünglich geschaffen wurde, um mathematische Probleme zu lösen. Weiß der Computer, wie er ein Problem lösen kann, dann kann er das immer wieder tun. Und er tut es eben sehr schnell. Sie werden noch sehen, dass man mit einem Computer neben der Mathematik noch anderen Unsinn machen kann.

Rechne du doch!


Hinweis
Damit Sie ein Bild über das Programmieren an sich gewinnen, werde ich Sie nun im Schnelldurchgang mit einigen Python-Listings belästigen. Geraten Sie nicht in Panik. Alles wird an anderer Stelle ausführlich beschrieben. Hier geht es nur um den Überblick. Entspannen Sie sich und lehnen Sie sich zurück!

Nehmen wir an, Sie wollen für die nächste Demo Tomaten kaufen. Sie wissen, dass fünf Tomaten 1,35 Euro kosten. Sie wollen aber 17 Tomaten, weil Ihr Patronengurt genau so viele aufnimmt. Sie erkennen sofort: Das ist der klassische Dreisatz. Sie zücken vielleicht schon Ihren Taschenrechner. Aber halt! Anstatt den Dreisatz jedes Mal selbst durchzurechnen, schreiben Sie ein Programm. Das bedeutet, Sie erklären dem Computer einmal, wie er mit dem Dreisatz umzugehen hat, und danach kann er das alleine. Leider müssen Sie dazu einmal darüber nachdenken, wie so ein Dreisatz funktioniert. Er besteht aus folgenden Schritten. Es sind drei. Logisch. Sonst hieße das Verfahren auch nicht Dreisatz.

1. Teile den Preis des Tomatenpakets durch die Anzahl der Tomaten.
2. Multipliziere diesen Preis mit der Zahl der gewünschten Tomaten.
3. Gib das Ergebnis auf dem Bildschirm aus.

Dies ist ein Programm. Allerdings ist die Programmiersprache für den Computer schwer nachvollziehbar. Der hätte es nämlich gern ein wenig formaler. Nehmen wir also irgendeine Programmiersprache. Wie wäre es mit Python?

PreisTomatenPaket = 1.35 
Anzahl = 5 
GewuenschteAnzahl = 17 
PreisEinerTomate = PreisTomatenPaket / Anzahl 
Ergebnis = PreisEinerTomate * GewuenschteAnzahl 
print(Ergebnis)

Listing 1.1 Python im Tomatendreisatz

Wenn der Computer das Programm durchläuft, ermittelt er als Ergebnis 4,59. Wollen Sie eine andere Anzahl an Tomaten, ändern Sie im Programm den entsprechenden Wert und starten das Programm noch einmal. Ändert sich der Preis, ändern Sie ihn.

Natürlich gibt es auch eine Anweisung, mit der der Computer den Benutzer auffordert, Zahlen einzugeben. So müssen Sie nicht für jede Wertänderung das Programm anpassen. Aber lassen Sie mir dieses süße Geheimnis für später.

Eine Beschreibung zur Lösung eines Problems nennt man Algorithmus. Ein gut gegliederter Algorithmus kann leicht in ein Programm überführt werden.

Von Fall zu Fall

Das Programm könnte aber noch mehr. Wenn Sie ab 10 Tomaten Rabatt von 10 Prozent bekommen, würde dies der Computer auch berechnen können. Dazu schieben Sie an geeigneter Stelle den folgenden Algorithmus ein.

10 Prozent abzuziehen ist dasselbe wie 90 Prozent vom Preis. 90 Prozent von irgendetwas errechnet man einfach, indem man irgendetwas mit 0,9 multipliziert. Das vereinfacht den Programmiercode etwas. Die Abfrage wird dann an passender Stelle in unser Listing 1.1 eingebaut.

PreisTomatenPaket = 1.35 
Anzahl = 5 
GewuenschteAnzahl = 17 
PreisEinerTomate = PreisTomatenPaket / Anzahl 
if GewuenschteAnzahl >= 10: 
    PreisEinerTomate = PreisEinerTomate * 0.90 
Ergebnis = PreisEinerTomate * GewuenschteAnzahl 
print(Ergebnis)

Listing 1.2 Tomatenberechnung mit Rabattabfrage

Sie könnten jetzt noch weitere Sonderfälle in das Programm einbauen. So könnte es sein, dass ein Beutel mit 7 Tomaten mit 1,40 billiger ist als die einzelnen Tomaten. Also soll der Computer doch bitte errechnen, wie viele Beutel ich kaufen muss, wie viele einzelne Tomaten und was das alles zusammen kostet.


Spicker
Das Prinzip ist, dass Sie dem Computer erklären, wie es gemacht wird, und der Computer erledigt die Rechnerei für Sie. Das gilt für Tomateneinkäufe wie für Börsengeschäfte oder Sparbriefe. Lassen Sie den Computer arbeiten!

Wiederholungstäter

Ihre Freundin erzählt Ihnen, dass der Nachbar seit einem Jahr mit dem Rauchen aufgehört hat und dadurch 1.000 Euro gespart hat, die er nun auf der Bank in einem Sparbrief angelegt hat. Sie rauchen zwar nicht. Diese Einnahmequelle fällt flach. Aber Sie beschließen, das Tomatenwerfen einzustellen, und wollen nun auch 1.000 Euro anlegen.

Bei Sparbriefen werden die Zinsen immer wieder am Ende des Jahres hinzugefügt und das führt uns zu einem weiteren Element der Programmierung, nämlich der Wiederholung. Der Programmierer spricht auch von Schleifen. Sie bekommen für einen Sparbrief jedes Jahr 6 Prozent Zinsen, die auf die Einlagen aufgeschlagen werden, also in den Folgejahren mitverzinst werden. Der Vertrag läuft über 7 Jahre. Wenn Sie das dem Computer vorlegen, können Sie ihm einfach sagen: Wiederhole diese Berechnung bitte 7 Mal.

Damit das Beispiel nicht zu trivial wird, nehmen wir noch an, dass Sie ab dem 3. Jahr sogar 9 Prozent Zinsen bekommen. Ich kann bei den Zinsen großzügig sein. Ich muss sie Ihnen ja nicht auszahlen.

Sie sehen, dass die Abfrage in die Schleife eingebaut wurde. Diese Art der Kombination der Programmierelemente ist zunächst vielleicht verwirrend, aber bei längerem Nachdenken logisch. Diese Art, Aufgaben zu formalisieren, damit der Computer es versteht, ist die Aufgabe eines Programmierers.

Der Vollständigkeit halber zeige ich Ihnen, wie das Programm aussieht, das aus dem Algorithmus hervorgeht. Vielleicht werden Sie die Befehle nicht verstehen. Keine Sorge, die werden in den nächsten Kapiteln im Detail erläutert. Wichtig ist nur, dass Sie sehen, wie sich aus dem Algorithmus das fertige Programm entwickelt.

Einlage = 1000 
Jahr = 1 
while Jahr <= 7: 
    if Jahr < 3: 
        Einlage = Einlage + Einlage * 0.06 
    if Jahr >= 3: 
        Einlage = Einlage + Einlage * 0.09 
    Jahr = Jahr + 1 
print(Einlage)

Listing 1.3 Sparbriefe

Tatsächlich kann der Computer gar nicht viel mehr als Abläufe, Abfragen und Schleifen. Programmiersprachen kommen nur deshalb nicht mit fünf Befehlen aus, weil es noch einige Mechanismen gibt, die dem Programmierer helfen, Programmteile zu strukturieren, um sie mehrfach verwenden zu können.

1.2 Wie der Computer mit Daten umgeht

Wenn Computer und Menschen über Zahlen reden, meinen sie nicht ganz dasselbe. Der Mensch verwendet das Dezimalsystem, vermutlich, weil er zehn Finger hat. Computer dagegen kennen nur zwei Zustände. Entweder es ist Strom auf der Leitung oder nicht. Aber genauso wie die meisten Menschen mit größeren Zahlen als 10 umgehen können, kann auch der Computer nicht nur bis 2 zählen. Durch das Verwenden mehrerer Stellen können beinahe beliebige Zahlen dargestellt werden, ob im Zehner- oder im Binärsystem.

In den meisten Fällen kann es Ihnen egal sein, wie Ihr Computer seine Zahlen intern darstellt. Ich will Sie damit auch gar nicht länger belästigen. Aber Sie werden feststellen, dass die 2 und ihre Potenzen magische Zahlen für Computer sind. Acht Stellen sind ein Byte. Und da 28 256 ist, können darin 256 Zustände abgespeichert werden. Ein Kilobyte sind 1024 Byte, weil 1024 210 sind. Zur leichteren Unterscheidung vom Kilo Zucker wird das Kilo für Bytes mit einem großen K abgekürzt.

Die unterschiedlichen Codierungen haben für die Nachkommazahlen fatale Folgen. Sie kennen vielleicht das Ergebnis, wenn Sie in Ihrem Taschenrechner 1 durch 3 teilen. Das erwartete Drittel erscheint als eine endlose Kolonne von Dreien hinter der 0 vor dem Komma. Multiplizieren Sie diese Zahl dann mit 3, werden die meisten Taschenrechner spontan eine Reihe von Neunen auf dem Display haben. Wenn das ein Außerirdischer sähe, der mit drei Fingern geboren ist, würde er sehr verblüfft sein. Denn vermutlich hätte er ein Zahlensystem, das auf der 3 basiert, und kann sich nicht vorstellen, dass ein Drittel mal 3 irgendetwas anderes als 1 ergibt. Und ganz ähnlich geht es dem Computer mit dem menschlichen Zehntel, das binär codiert ebenfalls ein endloser Bruch ist.


Spicker
Für den Computer mit seinen zwei Ziffern ist ein Zehntel ein richtig fieser endloser Bruch. Dadurch kommt der Computer beim Rechnen mit Dezimalstellen manchmal zu unsauberen Ergebnissen.

Der Computer und seine Texte

Der Computer speichert die Buchstaben natürlich auch in Bytes, die wie erwähnt 256 Zustände kennen. Die englischen Buchstaben umfassen 26 Zeichen. Hinzu kommen die Großbuchstaben und die zehn Ziffern. Das sind also 62 verschiedene Zeichen. Wenn noch ein paar Satzzeichen wie Punkt, Komma und Fragezeichen hinzukommen, sind es schnell mehr als 64 Zeichen, so dass 6 Bits für die Aufnahme aller Zeichen nicht ausreichen. Darum wurden zunächst 7 Bits verwendet, die einen Zeichenvorrat von 128 ermöglichen. Das letzte Bit wurde erst einmal auf 0 belassen. Diese Codierung wurde als ASCII-Zeichensatz (American Standard Code for Information Interchange) genormt.

Wenn Sie dieses Buch lesen, werden Sie vermutlich die deutsche Sprache verwenden und ahnen, dass die Amerikaner beim ASCII nicht viel Aufwand um die Umlaute getrieben haben. Darum wird das Thema wiederkommen.


Spicker
Buchstaben sind aus Sicht des Computers durchnummeriert. Erst bei der Ausgabe werden aus den Zahlen dann Buchstaben. Da aber Buchstaben für uns Menschen so wichtig sind, werden Buchstaben von Computersprachen besonders behandelt.

1.3 Sprachbarrieren zwischen Mensch und Computer

>Entgegen anderslautenden Gerüchten verstehen Computer uns Menschen gar nicht. Das Herz des Computers, oder besser das Gehirn des Computers, ist der Prozessor, der auch gern CPU (Central Processing Unit) genannt wird. Dieser Prozessor kennt eine übersichtliche Zahl von Befehlen, die einfach durchnummeriert sind. Hinter jedem Befehl steht, mit welcher Adresse oder Wert gearbeitet werden soll, und auch das ist nichts als eine Zahl. Solch einen Zahlensalat kann sich aber kein Mensch merken.

Um dieses gegenseitige Unverständnis zu überbrücken, sind Programmiersprachen entstanden, die etwas dichter an die Vorstellung des Menschen gerückt sind. Ihre Befehle bestehen aus englischen Wörtern. Damit sie auch der Computer versteht, müssen sie in seine Zahlenbefehle übersetzt werden. Dazu gibt es zwei Techniken:

Alles so englisch hier!

Ja, die Computerei ist voller Anglizismen. Die Befehle sind englisch, die Bezeichnungen sind englisch, selbst das Wort Computer ist englisch. Wenn Ihr Englisch nicht so toll ist, muss Sie dies nicht beunruhigen. Sie müssen genauso wenig Englisch lernen wie Ihr Apotheker Latein. Denn obwohl die meisten Präparate in der Apotheke lateinische Namen tragen, wird sich Ihr Apotheker schwertun, Julius Caesars Schriften über den gallischen Krieg im Original zu lesen. Und wozu auch? Seit Gründung der Europäischen Gemeinschaft hat es keine kriegerischen Auseinandersetzungen zwischen Franzosen und Italienern mehr gegeben, wenn wir mal von den Stadienkämpfen während der verschiedenen Fußballturniere absehen.

Viele Computerbegriffe sind längst übersetzt, sodass man sich damit nicht wehtut. Ein paar angelsächsische Besonderheiten sollten Sie aber im Hinterkopf behalten.

1. Das Dezimalkomma bei Zahlen ist ein Punkt. Wenn im Programm eine 1.2 steht, ist damit eine deutsche 1,2 gemeint.
2. Beim Datum nennen die Amerikaner den Monat vor dem Tag.
3. Die Amerikaner können sich nicht vorstellen, wozu jemand Umlaute gebrauchen kann, der kein Fan von Heavy Metal ist.
Aber das sind keine Gründe, ein Anglistik-Studium als Voraussetzung für das Erlernen einer Programmiersprache anzusehen.

Kapitel 10 Grafische Oberfläche (Tkinter)


Inhalt

Bisher waren die Programme auf die Textkonsole beschränkt. Aber es ist doch viel netter, wenn das Programm ein Fenster öffnet. Leider ist die Programmierung der GUI (Graphical User Interface) nicht trivial. Und die größte Gemeinheit ist, dass jedes System eine eigene Art hat, seine GUI zu programmieren. So unterscheidet sich ein grafisches Programm für den Mac auch in der Programmierung sehr deutlich von dem für Windows.

Es ist etwas frustrierend, wenn man die Programmierumgebung für jede grafische Oberfläche neu lernen muss, und darum gibt es einige Bibliotheken, die die Eigenheiten der verschiedenen Oberflächen unter einer gemeinsamen Schale verbergen. Python unterstützt mehrere dieser Bibliotheken. Wenn Ihnen einer der folgenden Kandidaten schon von einer anderen Programmiersprache bekannt ist, können Sie diese Kenntnisse unter Python wiederverwenden.

Tkinter ist wohl die populärste Bibliothek für Python. Darum wird sie bei einem Download eines Python-Pakets von www.python.org bereits mitgeliefert. Da sie auch am schnellsten zu schicken Programmen führt, wird dieses Kapitel sich damit befassen. Unter Linux ist zwar Python bereits vorinstalliert. Da Python dort aber auf mehrere kleine Pakete aufgeteilt ist, kann es sein, dass auf Ihrem System Tkinter gar nicht vorhanden ist. Dann müssen Sie das entsprechende Paket python-tk beziehungsweise python3-tk nachinstallieren.

Tkinter oder tkinter?

Um auf Tkinter zugreifen zu können, müssen Sie die Bibliothek erst importieren. Als kleine Gemeinheit erweist sich, dass die Bibliothek unter Python 2 noch als Tkinter einzubinden war, also mit einem großen T. Python 3 wollte die Namen der Bibliotheken vereinheitlichen und legte fest, dass alle Bibliotheksnamen aus Kleinbuchstaben bestehen. So wurde das T klein.

Wenn Sie nun ein Programm schreiben wollen, das sowohl mit dem Interpreter von Python 2 als auch mit Python 3 kompatibel ist, können Sie sich mit einem schmutzigen Trick helfen. Sie laden einfach Tkinter mit einem großen T und geben ihm den Alias von tkinter. Klappt das, handelt es sich um Python 2, das im weiteren Verlauf des Skripts nun aber mit dem Modulnamen tkinter hausieren geht.

Wird das Skript von Python 3 interpretiert, erhalten Sie eine Exception. Diese fangen Sie mit dem Befehl try ab. Nun können Sie im except-Fall einfach tkinter importieren.

try: 
    import Tkinter as tkinter 
except: 
    import tkinter

Listing 10.1 tkinter. Egal ob Python 2 oder 3

Auf diese Art heißt das Modul nach dem Import auf alle Fälle tkinter mit kleinem T. Sollte Tkinter gar nicht zur Verfügung stehen, wird das Programm beim ersten Versuch eines Zugriffs auf die Bretter gehen.

10.1 Eine kleine Anwendung

Zuerst soll ein einfaches kleines Fenster enstehen. Das Programm soll eine Beschriftung haben und einen Button.

IMG

Abb. 10.1 Das erste Fensterprogramm

Bei den meisten grafischen Programmen benötigt das Programm dazu einigen Vorlauf. Wie Sie es unter Python gewohnt sind, ist das Programm erfrischend kurz.

import tkinter 
fenster = tkinter.Tk() 
fenster.title("Mein Programm") 
anzeige = tkinter.Label(fenster, text="Guten Tag") 
knopf = tkinter.Button(fenster, text="Ende") 
anzeige.pack() 
knopf.pack() 
fenster.mainloop()

Listing 10.2 Einfaches Tkinter-Fenster

Auf Anhieb erschließen sich diese Zeilen sicher nicht. Aber die folgenden Bemerkungen sollten Ihnen ein Gefühl vermitteln, wie tkinter-Programme ticken.

1. Zunächst muss die Bibliothek tkinter eingebunden werden. Für Python 2 muss diese Tkinter heißen.
2. Das Hauptfenster wird durch den Aufruf von Tk() erzeugt.
3. Das Fenster bekommt einen Titel. Das dürfen Sie aber auch weglassen.
4. Es können weitere Elemente erzeugt werden, die als Parameter immer das Eltern-Fenster erwarten. Hier ist es ein Label (siehe Abschnitt 10.4.2) und ein Button (siehe Abschnitt 10.4.3).
5. Durch die Methode pack() werden diese im Eltern-Fenster angeordnet (siehe Abschnitt 10.3) und sichtbar.
6. Die Methode mainloop() startet den Ablauf des Fensters.

Das Programm ist nicht gerade spektakulär. Eine freundliche Meldung und ein Button, allerdings ohne Wirkung. Wenn Sie das Programm beenden wollen, müssen Sie die Schließbox einsetzen. Schade, eigentlich.

10.2 Das Programm ist Event-gesteuert

Grafische Programme tun so lange gar nichts, bis von außen ein Ereignis auf sie einschlägt, wie ein Tastendruck oder ein Mausklick. Dazu muss sich das Programm für den Empfang solcher Ereignisse mit einer Funktion anmelden, die diese Ereignisse dann behandelt.

10.2.1 Kontrollelemente lösen Ereignisse aus

Leider passiert nichts, wenn Sie auf den Knopf drücken. Um das zu ändern, muss das Programm eine Information bekommen, wenn der Anwender auf den Knopf drückt.

1. Sie müssen die Aktion festlegen, die ablaufen soll, wenn der Button gedrückt wird. Dazu schreiben Sie eine Funktion.
2. Sie müssen dem Button mitteilen, wie die Funktion heißt, die aufgerufen werden soll, wenn er gedrückt wird. Dies wird dem Button über die Option command mitgegeben.

Die Funktion ende() erweitert das Programm. Diese Funktion soll der Button aufrufen. Sie beendet das Programm durch einen Aufruf von sys.exit(0) (siehe Abschnitt 19.5). Dass die Funktion ende() beim Drücken des Buttons aufgerufen werden soll, erfährt der Button über seinen Konstruktor-Parameter command. Nun sollte das Programm enden, sobald der Button gedrückt wird.

import tkinter, sys 
 
def ende(): 
    sys.exit(0) 
 
fenster = tkinter.Tk() 
anzeige = tkinter.Label(fenster, text="Guten Tag") 
knopf = tkinter.Button(fenster, text="Ende", command=ende) 
anzeige.pack() 
knopf.pack() 
fenster.mainloop()

Listing 10.3 Tkinter-Fenster mit Callback

Eine Funktion für mehrere Buttons

Falls Ihr Programm mehrere Buttons verwendet, die durch eine einzige Funktion bedient werden sollen, wird es für diese etwas schwierig, den Absender zu erkennen. Hier hilft ein Aufruf über lambda (siehe Abschnitt 5.5). Dazu versehen Sie die Rückruffunktion des Buttons mit einem Parameter. Anstatt nun die Funktion direkt anzugeben, verwenden Sie den Befehl lambda für die Definition eines anonymen Funktionsrumpfes, der nichts anderes tut, als die Rückruffunktion mit einem eindeutigen Parameter aufzurufen.

import tkinter 
 
def klick(nummer): 
    print(nummer) 
 
fenster = tkinter.Tk() 
eins = tkinter.Button(fenster, text=’eins’, 
        command= lambda: klick(1)) 
eins.pack() 
zwei = tkinter.Button(fenster, text=’zwei’, 
        command= lambda: klick(2)) 
zwei.pack() 
drei = tkinter.Button(fenster, text=’drei’, 
        command= lambda: klick(3)) 
drei.pack() 
vier = tkinter.Button(fenster, text=’vier’, 
        command= lambda: klick(4)) 
vier.pack() 
fenster.mainloop()

Listing 10.4 Eine Funktion, viele Buttons

Die Nummer wird im Beispielprogramm einfach per print() auf dem Bildschirm ausgegeben. Aber wo befindet sich dieser bei einem grafischen Programm? Tatsächlich wird die Ausgabe von print() auf keinen Fall im Fenster erscheinen. Aber wenn Sie das Programm von einer Konsole aus gestartet haben, dann sehen Sie die Ausgabe dort. Wenn Sie es von IDLE aus starten, finden Sie die Ausgabe dort. Eclipse hat für diese Zwecke extra ein Konsolenfenster. Wurde das Programm von einem Desktop gestartet, verschwinden die Ausgaben von print().


Spicker
Ausgaben per print() können bei grafischen Programmen für die Kontrolle des Programms während der Erstellung verwendet werden.

Die übergebene Nummer kann von der Funktion klick() für die Identifizierung des Ausgangsbuttons verwendet werden. In Abbildung 10.2 sehen Sie die Buttons harmonisch in einer Reihe stehen. An den Schließknopf kommen Sie mangels Breite nicht heran. Dazu müssen Sie das Fenster etwas breiter ziehen.

IMG

Abb. 10.2 Vier Buttons und eine Funktion

10.2.2 Ereignisse und Reaktionen

Eine grafische Oberfläche ist ereignisreich. Ein typisches Programm baut einmal sein Fenster auf, meldet sich für einige Events an und freut sich, wenn ein solches stattfindet. Neben den oben beschriebenen Ereignissen, die durch die Kontrollelemente ausgelöst werden, können Sie darüber hinaus auch direkt die Ereignisse für Maus, Tastatur oder Fensterveränderungen fangen und verarbeiten.

Das direkte Abfangen vor allem von Mausereignissen per bind() kommt beim Canvas (siehe Kapitel 11) sehr viel häufiger vor als bei einem Fenster. Darum finden Sie die Beschreibung in Abschnitt 11.2.8. Das Canvas ist eine freie Zeichenfläche, das die Ereignisse komplett selbst fangen muss. Bei einem normalen Fenster übernehmen zumeist die Kontrollelemente die Ereignisse.

Tastaturereignisse

Die Tastaturereignisse werden allerdings nicht von einem Canvas, sondern nur von einem Fenster gefangen. Das Ereignis wird mit der Methode bind() an das Fenster gebunden. Der erste Parameter ist eine Zeichenkette, die den Ereignistyp bezeichnet. Die normalen Tasten werden über das Ereignis ’<Key>’ gebunden. Der zweite Parameter enthält den Namen der Funktion, die das Ereignis bearbeitet. Diese hat immer einen Parameter, der das Event beschreibt. Die gedrückte Taste befindet sich im Attribut char der Event-Variablen.

def fangtaste(event): 
    print("char:", event.char) 
... 
fenster.bind(’<Key>’, fangtaste)

Wenn nur einzelne, spezielle Tasten interessant sind, können Sie auch einfach den Buchstaben als Ereignis anmelden. Das Programm in Listing 10.5 fängt nur den kleinen Buchstaben a.

fenster.bind(’a’, fangtaste)

Listing 10.5 Fange nur ’a’

Für die Pfeiltasten (Cursortasten) gibt es die Ereignisse ’<Left>’, ’<Up>’, ’<Right>’ und ’<Down>’. Sie können diese sogar mit den Wörtern Shift, Alt und Control beispielsweise zu ’<Control-Left>’ kombinieren. Darüber hinaus sind noch die Sondertasten aus Tabelle 10.1definiert.

Bezeichnung Taste
’<Return>’ Die Return- oder Enter-Taste
’<BackSpace>’ Die Backspace-Taste (Korrektur)
’<Tab>’ Die Tabulatortaste
’<Shift_L>’ Die linke Großstelltaste
’<Shift_R>’ Die rechte Großstelltaste
’<Control_L>’ Die linke Strg-Taste
’<Control_R>’ Die rechte Strg-Taste
’<Alt_L>’ Die linke Alt-Taste
’<Alt_R>’ Die rechte Alt-Taste
’<Pause>’ Die Pause-Taste
’<Caps_Lock>’ Dauerhafte Großstelltaste
’<Escape>’ Die ESC-Taste
’<Prior>’ Seite nach oben (Page Up)
’<Next>’ Seite nach unten (Page Down)
’<End>’ Die Ende-Taste
’<Home>’ Die Pos1-Taste (Home)
’<Print>’ Die Druck-Taste
’<Insert>’ Die Einf-Taste
’<Delete>’ Die Entf-Taste
’<F1>’ bis ’<F12>’ Die Funktionstasten
’<Num_Lock>’ Die Nummernblock-Taste

Tabelle 10.1Ereignisse spezieller Tasten

Sie können sich auch für das Ereignis anmelden, dass das Fenster den Fokus erhält oder verliert. Wenn mehrere Fenster aktiv sind, empfängt nur eines davon die Tastaturereignisse. In den meisten Fällen ist es dasjenige Fenster, das oben liegt. Kommt ein anderes Fenster nach oben, verliert es den Fokus (’<FocusOut>’), kommt es wieder nach oben, erhält es den Fokus wieder (’<FocusIn>’).

10.3 Kontrollelemente im Fenster anordnen: Layout

Vor der Erforschung weiterer Kontrollelemente möchte ich Sie noch einen Moment mit dem Thema Layout aufhalten. Es ist zwar schön, wenn Sie mit Buttons, Listen und anderen Kontrollelementen umgehen können, aber sehr frustrierend, wenn die alle übereinandergestapelt in einem immer länger werdenden Fenster angeordnet sind. Das sieht doof aus und wirkt nicht sehr professionell.

Das Layout ist die Kunst, Kontrollelemente in den Fenstern so anzuordnen, dass der Benutzer sich darin zurechtfindet. Das Layout ist ein dynamischer Vorgang, der vor allem dann sichtbar wird, wenn ein Fenster in der Größe verändert wird. Die Kontrollelemente melden bei ihren Eltern-Frames Platz an, die diese gegebenenfalls wieder an deren Eltern weiterreichen. Die lachen dann herzlich darüber, verteilen den zur Verfügung stehenden Platz an die Kinder und die versuchen, unter den gegebenen Platzverhältnissen eine hübsche Darstellung zu erreichen.

Zwischen Eltern und Kindern steht jeweils ein Layout-Manager, der vorgibt, wie der verfügbare Platz aufgeteilt werden soll. Für verschiedene Situationen gibt es aber unterschiedliche Strategien. Darum müssen Sie das Layout auswählen, das für die Darstellung der Elemente am besten geeignet ist.

Um verschiedene Layouts kombinieren zu können, stellt Ihnen Tkinter Frames zur Verfügung. Das sind Rahmen, in die Sie Kontrollelemente, aber auch weitere Rahmen einsetzen können. Keine Angst: Der Rahmen ist unsichtbar. So kann niemand erkennen, wie Sie tricksen.

Da sich innerhalb eines Layouts Kontrollelemente und Frames gleich verhalten, werden sie auch als Widgets bezeichnet. Ein Widget ist ganz allgemein ein Fensterelement.

10.3.1 Pack alles aufeinander!

Im ersten Beispiel haben wir die Elemente mit der Methode pack() einfach in das Hauptfenster hineingepackt. Die Methode pack() ordnet die hinzukommenden Elemente standardmäßig einfach untereinander an.

Das erfordert einen hohen Monitor. Dumm nur, dass heutige Monitore eher immer breiter als immer länger werden. Aber Sie können über die Option side auch eine horizontale Anordnung erreichen, indem Sie ihr den Wert LEFT zuweisen.

meinLabel.pack(side=tkinter.LEFT)

Neben LEFT sind auch TOP, BOTTOM und RIGHT zulässig. Die Packerei geschieht nacheinander. Wenn Sie also einmal von dem Standard TOP auf LEFT wechseln, wird das obere Element nicht mehr angefasst. Das hört sich kompliziert an. Einfacher geht es mit einem Beispiel.

import tkinter 
fenster = tkinter.Tk() 
eins = tkinter.Label(fenster, text=’eins’) 
zwei = tkinter.Label(fenster, text=’zwei’) 
drei = tkinter.Label(fenster, text=’drei’) 
vier = tkinter.Label(fenster, text=’vier’) 
fuenf = tkinter.Label(fenster, text=’fuenf’) 
sechs = tkinter.Label(fenster, text=’sechs’) 
eins.pack() 
zwei.pack(side=tkinter.LEFT) 
drei.pack(side=tkinter.RIGHT) 
vier.pack(side=tkinter.LEFT) 
fuenf.pack() 
sechs.pack(side=tkinter.TOP) 
fenster.mainloop()

Listing 10.6 Pack in verschiedene Richtungen

Wenn Sie das Programm starten, erscheint ein Fenster wie in Abbildung 10.3. Vermutlich wird dies nicht die Anordnung sein, die Sie sich für Ihr Programm wünschen. Aber es zeigt die Möglichkeiten von pack(). Kombinert mit anderen Layouts in anderen Frames kann daraus etwas werden.

IMG

Abb. 10.3 Wild gepacktes Fenster mit verschiedenen Pack-Optionen

Verschachtelte Frames

Eine typische Anordnung von Kontrollelementen sieht so aus, dass links eine Beschriftung und rechts das passende Eingabefeld stehen soll. Darunter sollen weitere Eingaben folgen und am Ende des Fensters sitzen die Bestätigungsbuttons, wie es in Abb. 10.4 für die Eingabe einer Adresse zu sehen ist. Auch solche Anordnungen können Sie gestalten. Sie werden aber wohl noch den einen oder anderen Rahmen, also einen Frame verwenden müssen. In einem Frame können Sie Kontrollelemente, aber auch weitergehende Frames einsetzen und anordnen.

import sys 
import tkinter 
 
def ende(): 
    sys.exit(0) 
 
fenster = tkinter.Tk() 
frEingabe = tkinter.Frame(fenster) 
frEingabe.pack() 
frLabel = tkinter.Frame(frEingabe) 
frLabel.pack(side=tkinter.LEFT) 
frEntry = tkinter.Frame(frEingabe) 
frEntry.pack(side=tkinter.LEFT) 
nameLbl = tkinter.Label(frLabel, text = "Name: ") 
nameLbl.pack() 
nameEntry = tkinter.Entry(frEntry) 
nameEntry.pack() 
strasseLbl = tkinter.Label(frLabel, text = "Straße Nr: ") 
strasseLbl.pack() 
strasseEntry = tkinter.Entry(frEntry) 
strasseEntry.pack() 
ortLbl = tkinter.Label(frLabel, text = "PLZ Ort: ") 
ortLbl.pack() 
ortEntry = tkinter.Entry(frEntry) 
ortEntry.pack() 
knopf = tkinter.Button(fenster, text = "Ok", command=ende) 
knopf.pack() 
fenster.mainloop()

Listing 10.7 Verschachtelte Frames ordnen die Elemente.

Achten Sie darauf, dass die Elemente im ersten Parameter des Konstruktors nun nicht mehr alle das Hauptfenster erhalten. Stattdessen gibt es nun mehrere Frames.

1. fenster bildet den äußeren Rahmen und damit auch einen Frame.
2. Im Frame fenster werden die Frames frEingabe und der Button knopf untereinander angeordnet.
3. Im Frame frEingabe werden zwei Frames waagerecht nebeneinander angeordnet: frLabel und frEntry.
4. Im Frame frLabel werden untereinander alle Beschriftungen aufgenommen.
5. Im Frame frEntry werden alle Eingabefelder untereinander geordnet.

Zuletzt werden einfach die Kontrollelemente je nach Art in den linken (frLabel) oder rechten Frame (frEntry) einsortiert. Das Ganze wirkt deshalb so schön gleichmäßig, weil Entry und Label glücklicherweise gleich hoch sind. Wäre das nicht der Fall, würde eine von beiden Seiten immer mehr heruntersacken. Dann müssten Sie beispielsweise auf das Layout Grid zurückgreifen, das im nächsten Abschnitt beschrieben wird.

IMG

Abb. 10.4 Mehrfach geschachtelte Frames ermöglichen eine Eingabemaske.

Vielleicht missfällt Ihnen, dass NAME und PLZ ORT zentriert sitzen. Das könnten Sie ändern, wenn Sie bei den Pack-Aufrufen der Label-Felder in die Option anchor den Wert tkinter.W für Westen eintragen.

nameLbl.pack(anchor=tkinter.W)

Die folgende Aufstellung zeigt weitere Optionen, die die Methode pack() verarbeiten kann.

10.3.2 Hinter Gitter verteilen: Grid

Wenn Sie Eingabe-Elemente gitterförmig anordnen wollen, wie beispielsweise in Abbildung 10.4 zu sehen ist, verhält sich das Grid-Layout etwas angenehmer als pack(). Grid bildet ein Raster aus Spalten (column) und Zeilen (row). Diese sind durchnummeriert. Und wie Sie vermutlich bereits ahnen, beginnt auch hier die Zählung bei 0.

Ein Widget wird in dem Grid positioniert, indem dessen Methode grid() aufgerufen wird. Die Parameter row und column übergeben die Position des Widgets im Raster. Das folgende Beispiel zeigt, wie die Beschriftung und das Eingabefeld jeweils nebeneinander und Name, Straße und Ort schön untereinander angeordnet werden.

import sys 
import tkinter 
 
def ende(): 
    sys.exit(0) 
 
fenster = tkinter.Tk() 
frEingabe = tkinter.Frame(fenster) 
frEingabe.pack() 
nameLbl = tkinter.Label(frEingabe, text = "Name: ") 
nameLbl.grid(row=0, column=0) 
nameEntry = tkinter.Entry(frEingabe) 
nameEntry.grid(row=0, column=1) 
strasseLbl = tkinter.Label(frEingabe, text = "Straße Nr: ") 
strasseLbl.grid(row=1, column=0) 
strasseEntry = tkinter.Entry(frEingabe) 
strasseEntry.grid(row=1,column=1) 
ortLbl = tkinter.Label(frEingabe, text = "PLZ Ort: ") 
ortLbl.grid(row=2, column=0) 
ortEntry = tkinter.Entry(frEingabe) 
ortEntry.grid(row=2, column=1) 
knopf = tkinter.Button(fenster, text = "Ok", command=ende) 
knopf.pack() 
fenster.mainloop()

Listing 10.8 Positionierung der Adresse im Raster

Sie sehen im Vergleich zu Listing 10.7, dass Sie für den Frame frEingabe nun keine weiteren Unterteilungen mehr benötigen. So wird das Programm kürzer und übersichtlicher. Außerdem bleiben die Elemente auch dann im Raster, falls sich die Höhen der Widgets unterscheiden. Die Frames frEingabe und der Button werden nach wie vor mit einem Pack untereinander angeordnet. Das ist auch völlig unproblematisch, solange Pack und Grid nicht im gleichen Frame angewendet werden.


Warnung

Sie dürfen pack() und grid() niemals im selben Frame benutzen. Aber Sie können natürlich in einem Frame pack() und in einem anderen grid() verwenden.


Anstatt row oder column explizit auf 0 zu setzen, könnten Sie den entsprechenden Parameter auch weglassen, da 0 der Vorgabewert ist. Die möglichen weiteren Parameter von grid() heißen:

10.3.3 Pixelweise positionieren: Place

Wenn Sie eher für die Diktatur des Programmierers schwärmen, können Sie auch das Layout komplett selbst in die Hand nehmen und Ihre Widgets pixelgenau in das Eltern-Fenster platzieren. Dazu verwenden Sie die Methode place(), deren Anwendung Sie in der folgenden Beispielzeile sehen.

meinLabel.place(x=10, y=20, width=50, height=24)

Damit nageln Sie Ihr Label an der Position 10,20 mit der Breite 50 und der Höhe 24 fest. Das folgende Beispiel zeigt eine Maske, in der Beschriftung und Eingabefelder von Hand gesetzt worden sind.

nameLabel.place(x=10, y=20, width=50, height=24) 
nameEntry.place(x=70, y=20, width=50, height=24) 
strasseLabel.place(x=10, y=50, width=50, height=24) 
strasseEntry = tkinter.Entry(fenster) 
strasseEntry.place(x=70, y=50, width=50, height=24) 
ortLabel.place(x=10, y=80, width=50, height=24) 
ortEntry.place(x=70, y=80, width=50, height=24)

Listing 10.9 Eingabemaske per place() positioniert

Der Vorteil dieses Verfahrens ist, dass Sie die Kontrolle über die Positionierung Ihrer Kontrollelemente komplett selbst übernehmen. Sie können auch ungewöhnliche Anordnungssituationen damit lösen.

IMG

Abb. 10.5 Selbst gemurkste Eingabemaske per Place-Layout

Der Nachteil ist aber, dass Sie wirklich alles unter Kontrolle haben müssen. Das Beispiel in Abbildung 10.5 sieht grausig aus. Um ein ordentliches Layout zu erstellen, müssten Sie beispielsweise auch prüfen, ob die Schriften so groß sind, dass sie gut in den von Ihnen freigehaltenen Raum passen. Schnell sieht ein Fenster völlig überlaufen aus oder die Beschriftung verliert sich in leeren Fenstern, deren Freiraum danach schreit, als Werbefläche vermietet zu werden.

Die Positionen können allerdings auch als Verhältnis angegeben werden. Dazu gibt es spezielle Optionen, denen die Silbe rel für relativ vorangestellt werden. Die Fließkommawerte liegen zwischen 0 und 1 und geben das Verhältnis in Bezug auf das Eltern-Fenster an.

Und wenn Sie eh schon alles von Hand festlegen wollen, dann können Sie auch gleich noch die Größe und Position des Eltern-Fensters über die Methode geometry() setzen. Die Methode erwartet als Parameter eine Zeichenkette, in der Größe und Position codiert werden.

fenster = tkinter.Tk() 
fenster.geometry("{}x{}+{}+{}".format(300, 400, 190, 500))

Listing 10.10 Positionierung des Eltern-Fensters

Das Fenster ist nun 300 Pixel breit und 400 Pixel hoch. Es steht 190 Pixel vom linken Rand und 500 Pixel vom oberen Rand entfernt auf dem Bildschirm.

10.4 Kontrollelemente und ihre Optionen

Einige Kontrollelemente haben sich in den bisherigen Abschnitten bereits kurz vorgestellt: der Button, das Entry und das Label. Alle Kontrollelemente besitzen Attribute und Methoden. Beim Layout haben wir bereits gesehen, dass alle Widgets die Methoden pack(), grid() und place() besitzen. Darüber hinaus haben die Kontrollelemente auch weitere individuellere Methoden.

Hinzu kommen Optionen. Dazu zählen beispielsweise Beschriftungen, Farben und Größen. Diese können bereits beim Erzeugen der Widgets als zusätzliche Parameter über ihren Namen gesetzt werden. Alternativ ist es möglich, diese mit der Methode configure() anzusprechen. Darüber hinaus können Sie ein Kontrollelement wie ein Dictionary behandeln und die Optionen über rechteckige Klammern setzen.


Spicker
Der erste Parameter des Konstruktors eines Kontrollelements benötigt keine Namensangabe. Hier wird immer das Eltern-Widget angegeben. Alle weiteren Parameter werden per Namen übergeben. Die Optionen können aber auch nach der Erzeugung noch verändert werden.

In der folgenden Zeile wird ein Button erzeugt, dessen Eltern-Widget elternfenster heißt. Die Optionen text und command werden vorbelegt.

knopf = Button(elternfenster, text="Ende", command=ende)

Die folgende Übersicht zeigt verschiedene Optionen, die bei den meisten Widgets gesetzt werden können.

Farbangaben

Bei allen Farbangaben können Sie die Standardfarben als Zeichenkette übergeben, also ’black’, ’white’, ’yellow’, ’blue’, ’red’ und ’green’ verwenden.

Alternativ können Sie die Farben als hexadezimale RGB-Zeichenkette eingeben, wie Sie es vielleicht von HTML, der Beschreibungssprache von Websites her kennen. Diese wird durch ein Doppelkreuz eingeleitet. Dann folgen sechs Ziffern, jeweils zwei für den Rot-, Grün- und Blau-Anteil.

farbe=’#45E01A’

Je höher der jeweilige Wert, desto intensiver die Farbe. ’#000000’ ist Schwarz. Falls Ihnen hexadezimale Werte nicht liegen, können Sie die Zeichenkette auch leicht über einen format()-Aufruf (siehe Abschnitt 8.4.1) erstellen.

farbe=’#{:2x}{:2x}{:2x}’.format(88, 255, 199) # ergibt ’#58ffc7’

Da für jeden der RGB-Anteile ein Byte zur Verfügung steht, können die drei Werte zwischen 0 und 255 liegen. Python erlaubt auch, drei Stellen pro Farbanteil zu verwenden und erreicht damit eine 12-Bit-Codierung, die einen deutlich weiteren Farbraum bietet. Die Farbe Grün wäre dann mit ’#000FFF000’ codiert.

Zugriffe auf die Optionen

In Listing 10.3 haben Sie gesehen, wie zwei Optionen des Buttons bei seiner Initialisierung gesetzt wurden.

knopf = Button(fenster, text=’Ende’, command=ende)

Die Beschriftung (text) wurde auf ’Ende’ gesetzt und die Eventbehandlung (command) auf die Funktion ende() gelenkt. Wollen Sie die Optionen nachträglich verändern, können Sie die Methode configure() aufrufen.

knopf.configure(text = ’Ende’)

Sie können Optionen auch über eine Dictionary-Funktionalität des Buttons verändern. Dazu setzen Sie die Option als Zeichenkette in rechteckigen Klammern ein. Damit wird die Option zum Schlüsselwert.

knopf[’text’] = ’Ende’

Sie können über die rechteckigen Klammern den Inhalt der Optionen auch auslesen.

print(knopf[’text’]) 
print(knopf[’bg’])

Wollen Sie alle Optionen eines Kontrollelements kennenlernen, können Sie seine Methode keys() aufrufen.

print(knopf.keys())

10.4.1 Tkinter-Variablen

Einige Kontrollelemente korrespondieren mit Datenvariablen. So können Sie dem Eingabefeld eine Textvariable zuweisen, die ständig mit dem Widget abgeglichen wird. Änderungen an der Variablen schlagen sich im Eingabefeld nieder und Änderungen im Eingabefeld wirken sich sofort auf die Variable aus.

Leider können Sie nicht einfach die normalen Python-Variablen verwenden, sondern müssen auf die Tkinter-Variablen zugreifen. Davon gibt es vier Typen.

Auch das Zuweisen und Auslesen funktioniert nicht einfach per Zuweisung, sondern über die Methoden get() zum Auslesen und set() zum Zuweisen von Inhalten.

10.4.2 Label: Aufkleber mit Schriftart

Ein Label ist ein statisches Element ohne Funktion. Es dient in erster Linie dazu, Beschriftungen zu präsentieren.

Wie jedes Kontrollelement braucht auch das Label als ersten Parameter das Eltern-Fenster. Meist ändert das Label seine Beschriftung im Programmverlauf nicht. Insofern erhält es seinen Text meist bereits bei der Erstellung.

anzeige = Label(fenster, text="Guten Tag")

Listing 10.11 Ein Label wird erzeugt.

Sie können einem Label aber auch nachträglich jederzeit einen anderen Text zukommen lassen. Das funktioniert durch Zuweisung an das Options-Dictionary oder über die Methode configure().

anzeige["text"] = "Guten Abend" 
anzeige.configure(text= "Guten Abend")

Symbole

Mit der Option bitmap erhält das Label keinen Text, sondern ein Symbol. Python liefert einige Symbole wie ’error’, ’hourglass’, ’info’, ’questhead’, ’question’ und ’warning’. Hinzu kommen zwei Symbole, die lediglich ein dunkleres Rechteck darstellen: ’gray25’ und ’gray50’. Die Zeichenkette wird einfach der Option bitmap zugewiesen.

hinweis = tkinter.Label(fenster, bitmap="error")

IMG

Abb. 10.6 Tkinter-Bitmaps mit Namen

Schriftarten

Eine Schriftart wird als Tupel (siehe Abschnitt 9.2) übergeben. Es enthält folgende Informationen:

1. Die Schriftfamilie. Die vielleicht bekannteste Schriftfamilie ist Arial, die allerdings nur unter Windows verfügbar ist. Die Standardschrift, die von Arial imitiert wurde, heißt Helvetica.
2. Die Schriftgröße in pt.
3. Das Gewicht. Hier sind bold für Fettdruck und normal für Normalgewichtige möglich.
4. Die Schrägheit. Mit italic wird die Schrift kursiv und mit roman wird sie normal gesetzt.

Es ist möglich, alles in einer Zeichenkette abzulegen. Dazu werden die Angaben durch Leerzeichen getrennt. Die folgenden Zeilen liefern das gleiche Ergebnis.

Label(fenster, text=’abc’, font=(’Times’,’16’,’bold’,’italic’)) 
Label(fenster, text=’abc’, font=’Times 16 bold italic’)

Es ist auch möglich, ein Font-Objekt anzulegen. Leider ist das wieder zwischen Python 2 und Python 3 ein wenig unterschiedlich gelöst.

Auch hier können wir mit den Exceptions eine Portabilität schaffen.

try: 
    import tkinter 
    import tkinter.font as font 
except: 
    import Tkinter as tkinter 
    import tkFont as font 
 
fenster = tkinter.Tk() 
meinfont = font.Font(family=’Times’, size=16, weight=’bold’, 
        slant=’italic’, underline=1, overstrike=1) 
eins = tkinter.Label(fenster, text="Gestrichen und kursiert", 
        font=meinfont) 
eins.pack() 
fenster.mainloop()

Listing 10.12 Ein Font als Objekt

Zu guter Letzt sehen Sie hier noch einmal die Aufzählung der wichtigsten Optionen eines Labels.

10.4.3 Druckknopf: Button

Ein Button ist eigentlich nur ein bewegliches Label. Seine Hauptfunktionalität besteht darin, dass der Anwender ihn drücken kann, also quasi ein Schmuse-Widget.

Nach dem Drücken sollte aber auch etwas passieren. Was passieren soll, schreiben Sie zunächst in eine Funktion und geben deren Name der Option command an. Dann wird Ihre Funktion jedes Mal aufgerufen, wenn jemand den Button drückt. Das folgende Beispiel zeigt, wie es geht.

import tkinter
def rufMich(): 
    anzeige.configure(text="Jaaaa")

fenster = tkinter.Tk() 
anzeige = tkinter.Label(fenster, text="Hallo") 
knopf = tkinter.Button(fenster, text="Mach Druck!", command=rufMich) 
anzeige.pack() 
knopf.pack() 
fenster.mainloop()

Listing 10.13 Ein Knopfdruck verändert das Dasein eines Labels.

Ein Button kennt alle Optionen eines Labels, darüber hinaus aber noch einige, die sich aus der Besonderheit eines Buttons ergeben.