GUI –Programmierung mit Python 1 Inhaltsverzeichnis 1. Grundlagen der Objektorientierten Programmierung (OOP) ............................................................. 4 1.1. Klassen und Objekte ..................................................................................................................... 4 1.2. Kapselung ..................................................................................................................................... 6 1.3. Vererbung ..................................................................................................................................... 8 2. Erste Oberflächen ................................................................................................................................ 9 2.1. Hallo Welt!.................................................................................................................................. 10 2.1.1. Basisprogramm .................................................................................................................... 10 2.2.1. Dekorative Verfeinerungen ................................................................................................. 10 2.2.2. Programmende .................................................................................................................... 13 2.2.3. Eigenschaftsänderungen zur Laufzeit.................................................................................. 14 2.2.4. Benutzereingaben - Steuerelementvariablen ..................................................................... 15 2.3. Farbmischer ................................................................................................................................ 16 2.3.1. Layout .................................................................................................................................. 16 2.3.2. Eine Farbe – Rot.................................................................................................................. 18 2.3.3. Ein Mischlabel...................................................................................................................... 21 2.4. Zeichnen mit Python .................................................................................................................. 23 2.4.1. Canvas.................................................................................................................................. 23 2.4.1. Noch ein Nikolaus ................................................................................................................ 23 2.4.2. Farbwahl .............................................................................................................................. 24 2.4.3. Kreise und Kreisbögen ......................................................................................................... 24 2.4.4. Dreiecke – Vierecke – Polygone .......................................................................................... 25 2.4.5. Texte .................................................................................................................................... 25 2.4.6. Bewegung ............................................................................................................................ 26 2.4.7. Der springende Punkt. ......................................................................................................... 27 3. Ein Spiel ............................................................................................................................................. 29 3.1. Basics .......................................................................................................................................... 29 3.1.1. Leinwand ............................................................................................................................. 29 3.1.2. Der Ball ................................................................................................................................ 29 3.2. Bewegung für den Ball................................................................................................................ 30 3.2.1. Einfache Bewegung ............................................................................................................. 30 3.2.2. Hin und wieder zurück......................................................................................................... 31 3.2.3. Irgendwo hin........................................................................................................................ 31 3.2.4. Ein Prallbrett ........................................................................................................................ 32 3.2.5. Bewegung mit den Maustasten........................................................................................... 32 3.2.6. Treffer feststellen ................................................................................................................ 33 2 3.2.7 Quelltext ............................................................................................................................... 36 6. Literatur ............................................................................................................................................. 38 3 1. Grundlagen der Objektorientierten Programmierung (OOP) Vor der objektorientierten Programmierung wurden Programme von oben nach unten abgearbeitet. Wollte man an eine bereits abgearbeitete Stelle oder musste eine Stelle im Quelltext übersprungen werden, dann gab es Befehle, mit denen man an diese Sprünge bewerkstelligen konnte. Diese Art der Programmierung war unübersichtlich und fehleranfällig. Insbesondere war ein Programmierer, der das Programm nicht entwickelt hatte, kaum in der Lage, sich schnell in das Programm einzuarbeiten. Mit der objektorientierten Programmierung gab es dann einige Veränderungen. Die OOP wird durch drei Grundelemente bestimmt: Klassen und Objekte Kapselung Vererbung 1.1. Klassen und Objekte Jedes Programm, das man benutzt, besteht aus Klassen mit Objekten. Den Klassen und ihren Objekten sind bestimmte Attribute (Eigenschaften) und bestimmte Methoden (Veränderungsmöglichkeiten) zugeordnet. Ein Beispiel aus der analogen Welt: das Pferd. PFERD Geschlecht Größe Farbe traben schreiten galoppieren Falbe: PFERD Geschlecht: männlich Größe: 2,00m Farbe: falb traben schreiten galoppieren Schimmel: PFERD Geschlecht: männlich Größe: 2,00m Farbe: weiß traben schreiten galoppieren Dies ist die Klassenkarte. Sie beschreibt alle Eigenschaften aller Mitglieder dieser Klasse und alle Methoden aller Klassenmitglieder. Aus der Klasse werden Objekte abgeleitet. Im Gegensatz zur Klasse besitzen hier die Eigenschaften Werte. Grundsätzlich lassen sich unendlich viele Objekte von einer Klasse ableiten. Allerdings besitzen sie nur die Attribute und Methoden der Klasse, aus der sie stammen. Klassen in Python werden über die Module bereitgestellt, lassen sich aber mit dem class-Befehl auch direkt erstellen. class meineklasse(object): pass Nun gibt es eine Klasse meineklasse, die allerdings, festgelegt durch pass, leer ist. 4 Methoden innerhalb einer Klasse sind eigentlich nichts anderes als Funktionen, die als ersten Parameter den Bezug zur Klasse beinhalten. # Definition der Klasse Baum class Baum: wachstum = 0 # Eigenschaft def wachstumstempo(self, wert): # Methode self.wachstum += wert def ausgabe(self): # Methode print ("Wachstum:", self.wachstum) Der Parameter self verweist auf die Klasse Baum. Diese Klasse kann man nun auch nutzen: # Definition der Klasse Baum class Baum: wachstum = 0 # Eigenschaft def wachstumstempo(self, wert): # Methode self.wachstum += wert def ausgabe(self): # Methode print ("Wachstum:", self.wachstum) # Objekte der Klasse Baum erzeugen eiche = Baum() ulme = Baum() # Objektmethoden eiche.ausgabe() eiche.wachstumstempo(200) eiche.ausgabe() #Ursprungswert anzeigen #neuen Wert hinzufügen #neuen Wert anzeigen lassen # Objekt betrachten ulme.ausgabe() #vgl. Theis: Einstieg in Python Die Ausgabe sähe so aus: Wachstum: 0 Wachstum: 200 Wachstum: 0 In komplexeren Programmen werden Objekte initialisiert, d.h. ihnen werden von vornherein bestimmte Attribute zugewiesen, deren Wert bei Programmstart festgelegt wird, damit das Programm korrekt startet. 5 Das obige Beispiel verändert sich dadurch etwas. # Definition der Klasse Baum class Baum: def __init__(self,wachstum): #Objekt initialisieren, Parameter nennen self.wachsen = wachstum #der klasseeigenen Variablen den Wert des Parameters Wachstum geben def wachstumstempo(self, wert): # Methode mit einem Parameter self.wachsen += wert # Variable wachsen wird um Wert des Parameters verändert, d.h. self.wachsen=self.wachsen+wert def ausgabe(self): # Methode print ("Wachstum:", self.wachsen) # Objekte der Klasse Baum erzeugen eiche = Baum(0) #Wert aller Parameter muss in der Klammer stehen ulme = Baum(1) # Objektmethoden eiche.ausgabe() eiche.wachstumstempo(200) eiche.ausgabe() # Objekt betrachten ulme.ausgabe() Bei der Initialisierung wird festgelegt, dass alle Objekte der Klasse Baum einen Parameter wachstum haben. Dieser muss, wenn ein Objekt, z.B. eiche oder ulme, erstellt wird, auch mit einem Wert versehen werden. 1.2. Kapselung (oop_kapselung.py) Objekte besitzen, wie bereits gesagt, nur die Attribute und Methoden, die ihre Klasse bereitstellt. Auf Eigenschaften kann nur mit Hilfe der korrekten Methoden zugegriffen werden. Dieses Konzept innerhalb der Objektorientierung nennt man Kapselung. Anders formuliert: Pferde können nicht fliegen. Dies zwingt zu sauberer Programmierung und Nutzung der für eine Aufgabe passenden Objekte. Bislang sind die Daten in unserem Beispiel nicht gekapselt, d.h. theoretisch kann von außen auf sie zugegriffen werden. Möglich wäre also dies: # Definition der Klasse Baum class Baum: 6 def __init__(self,wachstum): self.wachsen = wachstum def wachstumstempo(self, wert): self.wachsen += wert def ausgabe(self): print ("Wachstum:", self.wachsen) # Objekte der Klasse Baum erzeugen eiche = Baum(0) ulme = Baum(1) eiche.wachsen=100 # Objektmethoden eiche.ausgabe() eiche.wachstumstempo(200) eiche.ausgabe() # Objekt betrachten ulme.ausgabe() In der fett gedruckten Zeile wird auf den Wert des Attributs zugegriffen. Dies ermöglicht Manipulationen und ist nicht gewollt. Deshalb gibt es die Möglichkeit, Attribute mittels Unterstrich als protected oder doppeltem Unterstrich private zu deklarieren, um sie zu schützen. Attribut wachsen _wachsen __wachsen Kapselung Public Protected Private auch von außen les- und schreibbar von außen les- und schreibbar, aber dies ist unerwünscht nicht von außen les- und schreibbar Aufgabe: 1. Erstelle ein Programm mit verschiedenen Klassen und Objekten. 2. Wende die Datenkapselung an und teste ihre Auswirkungen! 7 1.3. Vererbung Die OOP und übrigens auch das wirkliche Leben kennen Ober- und Unterklassen. Dabei gilt: Unterklassen erhalten von der Oberklasse alle Attribute und Methoden. Das Beispiel aus der analogen Welt: An nebenstehendem Schema wird deutlich, dass die Klasse PFERD offensichtlich eine Reihe von Attributen und Methoden von übergeordneten Klassen geerbt hat, z. B. Atmung, Knochen etc. In Python lassen sich dann auch neue Klassen und Objekte generieren, die Attribute und Methoden aus zwei Oberklassen enthalten. Dies nennt man Mehrfachvererbung. In Python hieße das z. B. class Baum(Algen, Moose). Darüber können Klassen auch Methoden erben, die ihnen eigentlich nicht zugedacht wären: Class Pferde(saeugetiere, voegel) In Python können Pferde fliegen. http://www.digitalefolien.de/biologie/tiere/tntierr.gif Aufgabe: Erstelle eine Übersicht zu Ober- und Unterklassen im Fahrzeugbereich. 8 2. Erste Oberflächen Moderne Programme verarbeiten zwar Programmtext, sobald sie aber auch Nutzereingaben einschließen, werden sie mit einer grafischen Benutzeroberfläche versehen, damit die Nutzer nicht nur vor einer Textkonsole sitzen müssen. Python stellt dafür das Modul tkinter (Tool Kit Interface) bereit, das, ähnlich wie z.B. turtle, über den entsprechenden Befehl importiert werden muss. from tkinter import * Hiermit werden alle Klassen des Moduls mit ihren Attributen und Methoden bereitgestellt. TKinter bietet die folgenden Widgets (Fensterkomponenten): Klasse Tk Beschreibung Hauptfenster einer Applikation mit Knöpfen zum lkonisieren, Vergrößern und Schließen Label Feld mit vorgegebenem Text oder Bild Button Schaltfläche Canvas Vektorgrafiken Checkbutton Quadratisches Feld für Mehrfachauswahl (m aus n) Entry Einfaches Eingabefeld für Texte Frame Rechteckiger Bereich (»Oberfläche«), in den man andere Widgets platzieren kann Listbox Liste von Auswahlfeldern (Einfach- und Mehrfachauswahl) Menu Darstellung von Toplevel-, Pulldown- und Popup-Menüs Photoimage Bild (GIF oder PPM /PGM) Radiobutton Rundes Feld für Einfachauswahl (1 aus n) Scale Waagrechte oder senkrechte Schieberegler Scrollbar Scrollleiste, die z.B. in Listbox-, Text- oder Canvas-Widgets eingebaut werden kann, um die Darstellung umfangreicher Inhalte zu ermöglichen Text Textfeld, in dem mehrzeilige formatierte Texte bearbeitet werden können (M. Weigend: Python GE-PACKT. Mitp, Heidelberg 2013, 5. Aufl., S. 458f) 9 2.1. Hallo Welt! Der Klassiker ist natürlich das altbekannte „Hallo Welt!“. 2.1.1. Basisprogramm Zunächst wird das Modul tkinter importiert. from tkinter import * fenster=Tk() Die Benutzeroberfläche des späteren Programms erhält die Bezeichnung fenster. lbl1=Label(fenster,text="Hallo Welt!") Der sog. Packer packt – daher die Bezeichnung - das Objekt auf die Oberfläche. lbl1.pack() Tipp Objekte sollten, ebenso wie Variablen und Funktionen, so benannt werden, dass sie problemlos zugeordnet werden. So erhalten Label z.B. den Vorsatz lbl, Buttons ein btn etc. In komplexeren Programmen ist dies für erfolgreiches Arbeiten unerlässlich. 2.2.1. Dekorative Verfeinerungen Natürlich ist dies nur ein Anfang. Hier wird das Label weiter gestaltet. Die Kommentare erläutern die einzelnen Befehle. Der Backslash wird in Python genutzt, um lange Programmzeilen mit einem Zeilenumbruch zu versehen, ohne dass sie als getrennte Befehlsfolgen interpretiert werden. from tkinter import * fenster=Tk() lbl1=Label(fenster, #Festlegung des Eltern-Elements \ text="Hallo Welt!", #Festlegung des Textes auf dem Label\ bg="yellow", #Hintergrundfarbe(background)\ bd=5, #Randstärke(border)\ relief=GROOVE, #Randform\ fg="Red", #Schriftfarbe (foreground)\ font=('Verdana',25,'bold'), #Schrift(-art, -größe, -stil)\ padx=10, #Innenabstand in x-Richtung\ pady=10, #Innenabstand in y-Richtung\ height=2, #Höhe in Textzeilen(nur Elemente mit Text)\ width=12) #Breite in Pixel\ lbl1.pack() #Positionierung auf dem Fenster\ fenster.mainloop() #Endlosschleife für Useraktionen\ 10 Das ist das Ergebnis des obigen Quelltextes. Die Standard – Optionen der tkinterWidgets sind: Option activebackground activeforeground anchor bd, borderwidth bg, background Bitmap Cursor default disabledforeground fg, foreground font height image justify padx pady relief text textvariable underline width xscrollcommand yscrollcommand Erklärung Hintergrundfarbe, wenn das Widget aktiv ist (z.B. wenn Button »gedrückt« wird) Vordergrundfarbe, wenn das Widget aktiv ist Mögliche Werte: CENTER, E, N, W, S, NE, NW, SE, SW Immer wenn ein Widget kleiner ist als der Platz, der für es vorgesehen ist (Zelle), wird mit diesem Attribut die Platzierung in der Zelle durch »Himmelsrichtungen« festgelegt. Default ist meist CENTER (in der Mitte), NE: rechte obere Ecke, N: mittig an der oberen Seite, E: mittig an der rechten Seite usw. Breite des Rahmens des Widgets, z.B. "lc" oder 10 Hintergrundfarbe Name einer Standard-Bitmap, die auf dem Widget zu sehen sein soll Name eines Standard-Cursors, der über dem Widget verwendet wird (X_cursor, , ...) Voreingestellt ist NORMAL. Mit dem Wert DISABLED wird das Widget deaktiviert. Vordergrundfarbe (Textfarbe), falls das Widget deaktiviert ist Vordergrundfarbe (Textfarbe) Front-Deskriptor für den verwendeten Schrifttyp (Font) Höhe des Widgets (senkrecht), z.B. "lc" oder 100. Bei Elementen mit Text in Zeilen, sonst in Pixeln angegeben. Name eines Bildes (Image-Objekt), das auf dem Widget (z.B. Button) zu sehen ist Ausrichtung von Textzeilen auf dem Widget: CENTER: zentriert LEFT, RIGHT: links- oder rechtsbündig Leerer Raum rechts und links vom Widget oder Text, z.B. "0.5c" oder 10 Leerer Raum über und unter dem Widget oder Text, z.B. "lc" oder 10 Form des Rahmens: SUNKEN, RAISED, GROOVE, RIDGE, FLAT Beschriftung des Widgets (z.B. Button oder Label) Ein Objekt der Klasse StringVar, das den (variablen) Text enthält, der auf dem Widget (z.B. Button oder Label) erscheint Default ist -1. Wenn die Zahl nicht negativ ist, gibt sie die Nummer des Zeichens an, das unterstrichen sein soll. Breite des Widgets (horizontal), z.B. "3c" oder 100 Wenn das Widget scrollbar ist, wird die set( )-Methode des horizontalen Scrollbar-Objektes angegeben. Wenn das Canvas scrollbar ist, wird die set( )-Methode des vertikalen Scrollbar-Objektes angegeben. 11 Der Packer bietet die Möglichkeit, die von ihm gepackten Objekte zu positionieren bzw. Eigenschaften mit Wirkung auf die Position festzulegen. Die wichtigsten Optionen für den Packer Option Erklärung anchor Mögliche Werte: CENTER, E, N, NE, NW, S, SE, SW, W. Widget wird in eine Ecke oder mittig an einer Seite der Zelle platziert, entsprechend der angegebenen »Himmelsrichtung« (z.B. NE = Nordost = rechts oben). expand expand=0: Die Größe des Widgets ändert sich nicht, wenn das Anwendungsfenster vergrößert wird. expand=1: Die Größe des Widgets passt sich an, wenn das Anwendungsfenster vergrößert wird. fill fi 11=X: Das Widget wird in waagrechter Richtung (»x-Achse«) soweit mit leerem Raum gefüllt, dass sich seine Ausmaße der Größe des Masters anpassen. fill=Y: Das Widget wird in senkrechter Richtung (»y-Achse«) an die Größe des Masters angepasst. fill=BOTH: Das Widget passt sich in beiden Richtungen dem Master an. fill=None: Das Widget behält seine Größe unabhängig von den Ausmaßen des Masters. padx Die Zelle wird rechts und links vom Widget in der angegebenen Länge mit leerem Raum gefüllt. Die Option padx=10 bewirkt z.B., dass die Zelle rechts und links um zehn Pixel verbreitert wird. pady Die Zelle wird oberhalb und unterhalb des Widgets mit leerem Raum gefüllt. side LEFT: Das Widget wird an den linken Rand des Masters gesetzt. RIGHT: Das Widget wird an den rechten Rand des Masters gesetzt. TOP: Das Widget wird nach oben gesetzt. BOTTOM: Das Widget wird nach unten gesetzt. Bei komplexeren Benutzungsoberflächen sind an einigen Stellen Widgets nebeneinander und an anderen Stellen untereinander angeordnet. In diesem Fall verwendet man Frames. Für die Planung eines Layouts mit dem Packer kann man den Entwurf durch gerade Schnitte so weit zerlegen, bis jede zusammenhängende Gruppe von Widgets einheitlich orientiert ist. Das heißt, die Widgets der Gruppe sind entweder von rechts nach links oder von oben nach unten aufgereiht. Für jede Gruppe, die aus mehr als einem Widget besteht, erzeugen Sie einen Frame, dem die enthaltenen Widgets als Slaves zugewiesen werden. (in: M. Weigend: Python 3. Mitp, Heidelberg 2013, 5. Aufl., S. 462) 12 2.2.2. Programmende Um ein Programm zu beenden, reicht es zwar, das Fenster zu schließen. Aber auch ein Button wäre schön. Mit dem Befehl meinbutton=Button(fenster, text="Ende") meinbutton.pack() wird ein Button in das Fenster gelegt. Aufgabe: 1. Gestalte Button und Label passend. 2. Positioniere beide mit Hilfe des Packers. Allerdings ist eine Nutzung noch nicht möglich. Dafür muss dem Button noch Befehlstext zugewiesen werden. Dazu wird zunächst eine Funktion erstellt, die das Hauptfenster beendet. def ende(): fenster.destroy() #Beendet das Hauptfenster Dann wird diese Funktion im Button als Parameter verankert. meinbutton=Button(fenster, text="Ende",command=ende) Aufgabe: Ergänze das Programm entsprechend. Hinweis Farben in Tkinter Zwei Standardfarben sind black und white. Dazwischen liegen Grautöne. gray ist ein mittleres Grau. Es gibt aber 101 feine Abstufungen von schwarzem Grau, gray0, bis zu weißem Grau, gray100. color("red") color("red1") color("red2") color("red3") color("red4") ergibt ein strahlendes Rot entspricht color("red ") etwas dunkler noch etwas dunkler ziemlich dunkel Farbtabelle aller Farbnamen Snow LightCyan Seashell PaleTurquoise AntiqueWhite CadetBlue Bisque turquoise Peach Puff cyan NavajoWhite DarkSlateGray LemonChiffon aquamarine cornsilk DarkSeaGreen ivory SeaGreen honeydew PaleGreen LavenderBlush SpringGreen MistyRose green azure chartreuse tan chocolate firebrick brown salmon LightSalmon orange DarkOrange coral tomato OrangeRed red DeepPink LightBlue LightCyan PaleTurquoise LightBlue SkyBlue IndianRed sienna burlywood wheat gold DarkOrchid purple Medium Purple 13 SlateBlue RoyalBlue blue DodgerBlue SteelBlue DeepSkyBlue LightSkyBlue SlateGray OliveDrab DarkOliveGreen khaki LightGoldenrod LightYellow yellow goldenrod HotPink pink LightPink PaleVioletRed maroon VioletRed orchid Thistle magenta plum MediumOrchid DarkGoldenrod RosyBrown LightSteelBlue 2.2.3. Eigenschaftsänderungen zur Laufzeit Bislang kann man sich das Fenster ansehen, aber nichts machen, außer das Fenster zu schließen. Aber natürlich können Attribute auch verändert werden. Die Veränderungen werden zunächst in eine Funktion gelegt und dann über den command-Befehl in den Button-Parametern aufgerufen. def labeltext(): lbl1.config(fg="RED4",font=('Verdana',15,'bold'),\ text="Schöne neue Welt", width=0) btn_neutext=Button(fenster, text="Neuer Text",command=labeltext) Tipp Es ist sinnvoll, seinen Quelltext zu ordnen, um den Überblick zu behalten. Zuerst müssen die Module importiert werden, die man benötigt, dann wird das Fenster initialisiert. Anschließend sollten die Funktionen erstellt werden. Die Objekte werden für die erste Verwendung erstellt. Aufgabe: from tkinter import * fenster=Tk() #Meine Funktionen def ende(): fenster.destroy() #meine Objekte btn_ende=Button(fenster, text="Ende",command=ende) Die Objekte werden dann über den Packer auf dem Fenster platziert werden. #Ins Fenster damit! btn_ende.pack(anchor=S,padx=10, pady=10,expand=0,side=RIGHT) Am Schluss wird das Fenster auf permanente Aktivität gestellt. #Immer auf Eingaben warten fenster.mainloop() 1. Gestalte das Label selbst. 2. Integriere einen weiteren Button, der den Schließen-Button mit neuer Aufschrift anderen Farben etc. versieht. 14 2.2.4. Benutzereingaben - Steuerelementvariablen Natürlich sind auch hier Benutzereingaben möglich. Dazu wird die Klasse entry genutzt, die ein Eingabefeld bereitstellt. Der Inhalt des Text-Attributs kann über sog. Kontroll- oder Steuerelementvariablen genutzt werden. Python stellt in Tkinter drei Variablentypen bereit: DoubleVar() - eine Gleitkommazahl, Standardwert: 0.0. IntVar() - eine Ganzzahl, Standardwert: 0.0 StringVar() - ein String, als Standard leer: „“ Laden des Tkinter-Moduls und initialisieren des Festers. Um das Text-Attribut auszulesen, muss zunächst eine entsprechende Steuerelementvariable initialisiert werden. In den Parametern des Objekts, das den Text enthält, z.B. einem Eingabefeld, wird über das Signalwort Textvariable festgelegt, dass die Steuerelementvariable den Inhalt des Textattributs erhalten soll. from tkinter import * fenster=Tk() eingabetext=StringVar() eingabe_01=Entry(fenster,textvariable=ei ngabetext) def labeltext(): Das Übergeben der Textvariablen wird in eine Funktion gelegt. Ein Label mit der Aufschrift „Hallo Welt!“ wird generiert. Die Objekte werden auf das Hauptfenster gelegt. Die Funktion mit der Übergabe der Variablen an das Label wird aufgerufen. lbl1.config(textvariable=eingabetext) lbl1=Label(fenster,text="Hallo Welt!") eingabe_01.pack() lbl1.pack() labeltext() #Immer auf Eingaben warten fenster.mainloop() Ergebnis: 15 Aufgabe Erweitere dein Programm, so dass ein Klick auf einen Button den Text eines Labels ändert. 2.3. Farbmischer 2.3.1. Layout Ziel dieses Abschnittes ist es, einen Farbmischer mit grafischer Oberfläche zu erstellen, das Farben über Schieberegler einstellt, die Farbwerte anzeigt und die Mischfarbe mit ihrem Hexadezimalwert anzeigt. 2.3.1.1. Frames (farbmischer_01.py) Hier reicht es nicht mehr, Objekte auf dem Fenster zu platzieren. Vielmehr müssen diese über Frames angeordnet werden. Dazu wird das Fenster in einzelne Frames aufgeteilt, die auch verschachtelt sein können. Ein Weg besteht darin, das Fenster in Frames zu teilen. In diesem Beispiel hätte das Fenster vier Frames, die übereinander liegen. Der Quelltext ist für alle drei Frames ähnlich. Hier am Beispiel des Fußbereichs mit dem Ende-Button. 16 Die Ende-Funktion wird erstellt. Das Fuss-Label wird erstellt inkl. Text- und Randformatierung. Der Ende-Button wird im Frame fuss erstellt. Das Frame wird auf das Fenster gepackt. Es soll sich auf die gesamte Fensterbreite ausdehnen und 10px Außenabstand haben. Der Button wird rechts auf das Frame gepackt. Hinweis def ende(): fenster.destroy() fuss=LabelFrame(fenster,text="Farbmischer",width=900,height=50,\ borderwidth=6,font=('Verdana',15,'bold')) btn_ende=Button(fuss, text="Ende", command=ende,\ font=('Verdana',15,'bold')) fuss.pack(fill=X, padx=10,pady=10) btn_ende.pack(anchor=E,padx=10,pady=10,expand=0,side=RIGHT) Untergeordnete Elemente erhalten als ersten Parameter in der Klammer den Namen des übergeordneten Elements, des sog. Masters. Bei komplexeren Programmen sollte dies auch in einem Schema dargestellt werden. 2.3.1.2. Strukturschema Für den Überblick ist ein Strukturschema sinnvoll, das die Nehmen und Über- und Unterordnungen enthält. Dieses Beispiel enthält nur die Objektnamen und den Parameter, der das übergeordnete Element benennt, Fenster fenster=Tk() titel=LabelFrame( fenster) farben=LabelFra me(fenster) mischfarbe=Lab elFrame(fenster) fuss=LabelFram e(fenster) btn_ende= Button(fuss) 17 2.3.1.3. Grids Neben Frames bieten Grids eine gute Möglichkeit zur Layoutgestaltung. Hierbei wird über eine Tabelle festgelegt, wie viele Zeilen und Spalten ein Element haben kann. Die einzelnen Objekte werden dann entsprechenden Spalten zugewiesen und die Zellen passen sich in der Größe den Objekten an. So legt der Nachsatz .grid(column=1,row=1) fest, dass das Objekt sich auf Zeile 1 in Süalte 1 befindet. Dies kann beliebig erweitert werden. lbl_red=Label(fenster,height=10,width=20,bg='grey55').grid(colu mn=1,row=1) Typische Grid-Layouts wären z.B. Taschenrechner. 2.3.1.4. Pack Der Packer ist der älteste und einfachste Layoutmanager in Python. Er legt alle Elemente auf eine Ebene. Er wurde unter 2.2.1. bereits vorgestellt und wird im Folgenden verwendet. Der Farbmischer besteht aus drei Scrollbars (Scale) und vier Labeln (Label) sowie einem Button (Button) zum Schließen des Programms. Diese werden mittels Pack auf der Oberfläche angeordnet. lbl_red.pack(anchor=W, side=LEFT) skala_rot.pack(anchor=W, side=LEFT) lbl_green.pack(anchor=W, side=LEFT) skala_green.pack(anchor=W, side=LEFT) lbl_blue.pack(anchor=W, side=LEFT) skala_blue.pack(anchor=W, side=LEFT) lbl_mix.pack(anchor=W, side=LEFT) btn_ende.pack(anchor=S, side=BOTTOM,padx=10,pady=10,expand=0) Hier werden die Labels und Scrollbars nebeneinander auf die Oberfläche gelegt. Die südliche Ausrichtung des Schließen-Buttons bewirkt, dass dieser unten im Fenster liegt. 2.3.2. Eine Farbe – Rot Nach dem Import des tkinter-Moduls, dem generieren des Fensters und der Änderung des Fenstertitels wird der Schließen-Button konfiguriert. from tkinter import * fenster=Tk() fenster.title('Farbmischer') def ende(): fenster.destroy() btn_ende=Button(fenster,command=ende,text='Ende') Die ende-Funktion ist bereits bekannt. Sie schließt beim Aufruf das aktive Fenster. Nun werden das Label für die rote Farbe und die Scrollbar für die rote Farbe generiert. 18 lbl_red=Label(fenster,height=10,width=20,bg='grey55')\ skala_rot=Scale(fenster, length=150,width=25, \ orient=VERTICAL, from_=1,to=255,\ command=farbanzeige,variable=r_wert) Der Parameter command ruft eine Funktion auf, die für die Übergabe des Scrollwertes genutzt werden wird. Scrollbars können den Scrollwert zurückgeben. Der Parameter variable generiert eine Steuerelementvariable, die diesen Scrollwert der Scrollbar enthält. Über r_wert=IntVar() r_wert.set(200) wird die Scrollwert-Variable als Integer-Variable initiiert und mit einem Startwert von 200 versehen. Die Funktion get() erfasst dann die Werte der Scrollbar. r_str=str(r_wert.get()) Exkurs: Werte konvertieren Scrollbars geben Integer-Werte zurück. Diese müssen, damit sie als Farbangabe genutzt werden, in Hexadezimal-Werte umgeformt werden. Die Funktion hex(x) leistet dies und gibt einen entsprechenden String zurück. Allerdings birgt diese Umwandlung ein paar Probleme, denn für die Farbangabe im Label werden zweistellige Hexadezimalwerte für jeden der drei Farbbereiche Rot, Grün und Blau benötigt. Es muss also sichergestellt werden, dass auch wirklich so konvertiert wird. In der Konsole ergibt >>> hex(9) '0x9' Das vorangestellte 0x ist das Hexadezimal-Kennzeichen, der eigentliche Hexadezimalwert ist also 9. Es muss also dafür gesorgt werden, dass das Hexadezimal-Kennzeichen verschwindet und der Wert zweistellig wird, also ggf. eine Null vorangestellt wird. Über die Methode replace kann man in den drei Argumenten festlegen, welches Element eines Strings ersetzt werden soll, wodurch es ersetzt werden soll und wie oft diese Ersetzung in einem String vorgenommen werden soll. Beispiel >>> a='Otto' >>> a=a.replace('O','Mo',1) >>> print(a) Motto >>> Der Befehl len(x) gibt die Länge eines Strings zurück. >>> len(a) 5 >>> So ergibt sich für die Umwandlung der Steuerelementvariablen folgender Quelltext: r_str=str(r_wert.get()) r=int(r_str) print(r) #Farbwerte in Hex-Werte umrechnen r_hex=hex(r) 19 #Das HEX-Zeichen (0x) entfernen r_hex=r_hex.replace('0x','',1) #korrekte Länge der Hex-Werte herstellen if len(r_hex)<2: r_hex='0'+r_hex Der print-Befehl dient nur der Kontrolle. Die Bedingung am Schluss prüft die Länge und fügt eine Null hinzu, falls nötig. Nun wird der Hexadezimalwert in eine neue Variable integriert und zur Kontrolle auf der Konsole angezeigt. Fir RGB-Abfolge bleibt dabei auch bei der Aneinanderreihung der Hexadezimalwerte erhalten. farbe_red='#'+r_hex+'00'+'00' print('#'+r_hex+'00'+'00') lbl_red['bg']='#'+r_hex+'00'+'00' Jetzt kann das Ganze in eine Funktion gelegt werden. Der Parameter self sorgt, dass die Funktion immer wieder aufgerufen wird, damit Änderungen der Farbwerte auch erfasst werden. Die Funktion wird durch das Bewegen der Scrollbar aufgerufen, muss also nicht gesondert im Programmtext aufgerufen werden. def farbanzeige(self): # Die Farbwerte festlegen r_str=str(r_wert.get()) r=int(r_str) print(r) #Farbwerte in Hex-Werte umrechnen r_hex=hex(r) #Das HEX-Zeichen (0x) entfernen r_hex=r_hex.replace('0x','',1) #korrekte Länge der Hex-Werte herstellen if len(r_hex)<2: r_hex='0'+r_hex #neue Variable für den Farbwert festlegen farbe_red='#'+r_hex+'00'+'00' print('#'+r_hex+'00'+'00') lbl_red['bg']='#'+r_hex+'00'+'00' Aufgabe: 1. Jetzt sollte ein erster Test möglich sein. Teste das Programm! 2. Ergänze das Programm um die Quelltexte für die Farben Grün und Blau. 20 2.3.3. Ein Mischlabel Ohne die Mischfarbe zu sehen, ist ein Farbmischer nicht sehr hilfreich. Das Problem dabei ist, dass die Variablen, die zur Farbgestaltung von den Scrollbars übergeben werden, nur lokal in der Funktion vorhanden sind. Exkurs – Lokal – Global Funktionen nutzen Variablen. Werden diese in der Funktion erstellt, dann sind sie außerhalb der Funktion nicht nutzbar. Wurden die Variablen im Hauptquelltext erstellt, kann die Funktion sie zwar nutzen, aber nicht umschreiben. Dieses Feature sichert ungewolltes Verhalten von Programmen ab, muss aber manchmal übergangen werden. Versucht man, eine lokale Variable global zu nutzen, gibt dies eine Fehlermeldung. >>> def test(): a=22 print(a) >>> test() 22 #lokale Variable lokal genutzt >>> print(a) Traceback (most recent call last): File "<pyshell#31>", line 1, in <module> print(a) NameError: name 'a' is not defined >>> Dementsprechend muss eine Variable global verfügbar, d.h. auf überschreibbar gemacht werden. Möglich macht dies der Befehl global, gefolgt vom Variablennamen. Die Variable kann allerdings nicht lokal generiert und dann übergeben werden. Um sie zu nutzen, muss sie also einmalig global erstellt und mit einem fiktiven Wert versehen werden. >>> def test(): global a #macht eine lokale Variable global a=22 print(a) >>> a=10 #Variable wird einmalig erstellt >>> print(a) 10 >>> test() 22 >>> print(a) 22 >>> Für den Farbmischer bedeutet dies, dass die Variablen für Rot, Grün, Blau und die Mischfarbe global erstellt werden müssen. fenster.title('Farbmischer') r_hex='00' 21 g_hex='00' b_hex='00' farbe_mix='000000' Dann beginnt die Funktion für die rote Farbanzeige mit der globalen Deklaration. def farbanzeige_red(self): global r_hex Noch zeigt das Mischlabel aber keine Farben, Dazu muss dem Label permanent die korrekte Hintergrundfarbe übergeben werden. Am einfachsten erfolgt dies durch eine Funktion in der Funktion für die Farbe Rot. Die Funktion legt die Variable farbe_mix als global fest und erstellt den kompletten Hexadezimalwert aus den drei Farbwerten für Rot, Grün und Blau. Die print-Anweisung dient wieder der Kontrolle und schließlich werden dem Label die Hintergrundfarbe und ein Text mit dem Hexadezimalwert übergeben. def farbanzeige_mix(): global farbe_mix farbe_mix='#'+r_hex+g_hex+b_hex print('Mixfarbe '+'#'+r_hex+g_hex+b_hex) lbl_mix['bg']='#'+r_hex+g_hex+b_hex lbl_mix['text']='#'+r_hex+g_hex+b_hex farbanzeige_mix() Aufgabe 1. Gestalte den Farbmischer für alle drei Farben. 22 2.4. Zeichnen mit Python 2.4.1. Canvas Natürlich lassen sich mit Python auch Elemente auf der Programmoberfläche zeichnen. Nötig ist dazu eine Zeichenfläche (Canvas). Zunächst sind die Standartelemente des Skripts zu erstellen. Die Zeichenfläche wird dann über z_flaeche=Canvas(fenster, width=900, height=900) erstellt. Aufgabe: Erstelle ein Programmfenster mit einem Schließen-Button und einer Zeichenfläche in der Größe 900x900. 2.4.1. Noch ein Nikolaus Auf der Zeichenfläche kann nun gezeichnet werden. Dabei wird ein Koordinatensystem genutzt. Der Nullpunkt liegt links oben, die Maximalwerte für x und y rechts unten. Linien werden über den folgenden Befehl kreiert: z_flaeche.create_line(0,0,900,900) Die ersten beiden ersten Parameter geben den x - und y – Wert des Startpunktes, die beiden anderen den des Endpunktes an. Aufgabe: 1. Erstelle eine Linie, die diagonal von links oben nach rechts unten die Zeichenfläche durchteilt. 2. Zeichne das Haus des Nikolaus in der richtigen Reihenfolge. 3. Ergänze die Linien um die Argumente width= und fill= für Linienbreite und Linienfarbe. Nutze als Farbe die Farbnamen, also Blue, Red etc. 23 2.4.2. Farbwahl Der Nutzer kann auch selbst Farben wählen. Dazu dient die Funktion colorchooser in tkinter. Sie ruft ein Farbwahlfenster aus. Die Funktion colorchooser.askcolor() gibt einen Tupel zurück, der aus zwei Teilen besteht. Teil eins ist ein Tupel, der die drei RGBFarbwerte enthält, der zweite Teil ist ein String, der den Hexadezimalwert enthält. Beispiel: print(colorchooser.askcolor()) ergibt also ((251.98046875, 0.0, 6.0234375), '#fb0006') Rot Green Blue Hex Dementsprechend kann man eine Variable mit dem Hex-Wert dieses Tupels füllen und damit die Farbe des Hauses festlegen: farbe=colorchooser.askcolor() z_flaeche.create_line(200,800,400,800,width=7, fill=farbe[1]) Die in eckige Klammern gibt an, dass der zweite Wert des Tupels, in dem ja mit Null beginnend gezählt wird, für die Farbangabe genutzt werden soll. Aufgabe: Gestalte das Haus so um, dass der Nutzer die Farbe selbst wählen kann. 2.4.3. Kreise und Kreisbögen Kreise und Kreisbögen sind ebenso möglich. Sie werden als Element innerhalb eines Quadrats angesehen. Das bedeutet, dass bei der Koordinatenangabe ein Quadrat angenommen wird, dessen Koordinaten angegeben werden und in dem dann der Kreisbogen bzw. der Kreis liegt. Dementsprechend werden in den Argumenten zunächst die xund y-Werte des Rechtecks angegeben, dann die Koordinaten der rechten unteren Ecke des Rechtecks, anschließend die Gradzahl, bei der negative oder positive Werte erlaubt sind, und der Stil, der das Aussehen des Kreisbogens festlegt. Für nebenstehendes Bild hieße dies: z_flaeche.create_arc(100,100,500,500,extent=359, style=ARC,width=3) z_flaeche.create_rectangle(100,100,500,500,width=3) 24 Mögliche Stile der Kreisbögen sind ARC – Kreisbogen CHORD – Kreisbogen mit Sehne PIESLICE - Tortenstück Kreise und Kreisbögen mit den Stilen PIESLICE und CHORD können gefüllt und mit einer Linienfarbe versehen werden: z_flaeche.create_arc(100,100,500,500,extent=359, style=PIESLICE,width=3, outline='red',fill='blue') Hinweis Hier wird offensichtlich, was aufmerksamen Lesern bereits aufgefallen ist: Kreise mit 360° werden als 0°-Kreise interpretiert. Entweder man beschränkt sich auf 359° oder man zeichnet zwei Kreisbögen mit je 180° und demselben Mittelpunkt, einen in positive, einen in negative Richtung. Die Richtung wird über eine positive oder negative Gradzahl festgelegt. 2.4.4. Dreiecke – Vierecke – Polygone Im Prinzip sind dies für tkinter immer die gleichen Elemente, nur mit verschiedenen Argumenten. Für Rechtecke gibt es zwar den Sonderfall create_rectangle(x1,y1,x2,y2), der aber eigentlich nur der Erleichterung für Programmierer dient. Polygone werden über ihre Eckpunkte definiert. Also ein Dreieck: z_flaeche.create_polygon(50,50,300,50,300,300,\ fill='green', outline='Red') Und ein Rechteck: z_flaeche.create_polygon(50,50,300,50,300,300,50,300,\ fill='green', outline='Red') Und ein Fünfeck: z_flaeche.create_polygon(100,50,200,50,300,300,250,400,50,300,\ fill='green', outline='Red') und so weiter. 2.4.5. Texte Texte lassen sich ebenso einbinden. Der erste Parameter gibt dabei die Position des umschließenden Rechtecks wieder. Dann folgt der Text, die Fülldfarbe und die Angaben zur Schrift, also Schriftart, Schriftgröße und Schriftstil. z_flaeche.create_text(300,200,text='Kaa ist auch ein Python!',\ fill='red', font=('Arial',30,'bold')) Schriftstile: bold – fett italic - kursiv 25 underline – unterstrichen overstrike – durchgestrichen Aufgabe: Generiere ein Gesicht. Gib dem Gesicht einen Namen. (zeichnen_07.py) 2.4.6. Bewegung Bewegung ist auch mit Python möglich. Über die Methode move(id,x,y) kann ein Objekt in jede beliebige Richtung bewegt werden. Die id wird dabei in der Reihenfolge festgelegt, in der die Zeichnungen erstellt werden, beginnend mit 1. z_flaeche.create_polygon(10,10,10,60,50,35) z_flaeche.create_polygon(30,80,30,120,40,85) for x in range (0,60): z_flaeche.move(1,5,0) z_flaeche.update() time.sleep(0.5) for x in range (0,60): z_flaeche.move(2,5,0) z_flaeche.update() time.sleep(0.5) In diesem Skript werden nach den hier weggelassenen Standardelementen, also Fenster und EndeButton, zwei Polygone erstellt, die dann nacheinander von links nach rechts bewegt werden. Welches Polygon als erstes bewegt wird, hängt von der Reihenfolge im Quelltext ab. Der Befehl time.sleep(0.5) sorgt dafür, dass die Bewegung immer wieder unterbrochen wird, denn sonst wäre nur das Ergebnis des Programmverlaufs zu sehen. 26 2.4.7. Der springende Punkt. Etwas Anderes ist es, wenn der Benutzer eingreifen soll. Dafür werden sog. Events genutzt. Ein Event, also ein Ereignis, kann ein Mausklick sein oder ein Tastendruck. Eventtypen sind z. B.: Button ButtonRelease MouseWheel Enter KeyPress KeyRelease Leave Maustaste gedrückt Maustaste losgelassen Mausrad bewegt Mauscursor in sichtbarem Bereich des Objekts Tastendruck Taste losgelassen Maustaste wird aus dem Objekt herausgezogen Eine kleine Auswahl an Tastatur-Attribute der Eventtypen. keysym BackSpac Control_ Delete Down End Escape Fl F2 Left Next Return Right space Up Tipp keysym_num 65288 65507 65535 65364 65367 65307 65470 65471 65361 65366 65293 65363 32 65362 Das Tastensymbol (keysym) ist ein String, die Tastensymbolnummer eine ganze Zahl. Das folgende Skript zeigt Tastensymbole und –nummern der betätigten Tasten an: from tkinter import * def out(event): print(event.keysym, " \ t", event.keysym_num) window = Tk( ) window. bind_all("<Any-KeyPress>" , out) window.mainloop( ) (M. Weigend: Python GE-PACKT, S, 548) Events können modifiziert werden, z.B.: Modifizierer Erklärung Alt Alt-Taste gedrückt Any Generalisierung (Any-Key-Press = irgendeine Taste gedrückt) Double zwei Ereignisse kurz hintereinander Double-Button-3 = Doppelklick mit rechter Maustaste Triple drei Ereignisse kurz hintereinander Control Strg-Taste gedrückt Shift Umschalttaste gedrückt 27 Das Ereignis muss zunächst in einer Funktion mit dem Argument event erstellt werden. def springpunkt(event): if event.keysym == 'Up': z_flaeche.move(1,0,-5) elif event.keysym == 'Down': z_flaeche.move(1,0,5) elif event.keysym == 'Left': z_flaeche.move(1,-5,0) else: z_flaeche.move(1,5,0) Hier wird die Event-Funktion erstellt und dann mit einer Abfolge von Bedingungen geprüft, welche Taste gedrückt wurde. Anschließend muss das Ereignis noch aufgerufen werden. Über die Methode bind_all werden die Ereignisse für alle Objekte der Applikation an den Eventhandler, also hier einen Tastendruck, gebunden. In der Klammer folgt der sog. Event-Pattern. Er ist folgendermaßen aufgebaut: <[Modifizierer-] Typ [-Qualifizierer]> Double-Keypress-Return In diesem Beispiel würde auf ein Doppeltes Drücken der Enter-Taste gewartet. Um den Punkt in Bewegung zu bringen, ergibt sich folgender Quelltext: z_flaeche.bind_all('<KeyPress-Up>',springpunkt) z_flaeche.bind_all('<KeyPress-Down>',springpunkt) z_flaeche.bind_all('<KeyPress-Left>',springpunkt) z_flaeche.bind_all('<KeyPress-Right>',springpunkt) Am Ende der Bindung wird die Funktion aufgerufen, die bei Eintreten des Ereignisses erfolgen soll. Aufgaben 1. Erstelle das Programm mit dem springenden Punkt. 2. Integriere für ein weiteres Objekt eine Steuerung mit Buchstabentasten. 28 3. Ein Spiel 3.1. Basics Nun soll ein ganz einfaches Spiel erzeugt werden, dass einen Ball auf einer Fläche hüpfen lässt. Hierfür sind einige Vorarbeiten nötig. 3.1.1. Leinwand Zunächst wird die Leinwand erzeugt. Dabei werden neben Tkinter die Module time und random importiert, die im Verlauf des Spiels noch gebraucht werden. Zusätzlich erhält das Fenster Festlegungen zur Gestaltung des Rands. from tkinter import * import random import time fenster=Tk() fenster.title('PingPong') fenster.resizable(0,0) #Fenster nicht veränderlich fenster.wm_attributes('-topmost',1) #Fenster immer ganz vorn z_flaeche=Canvas(fenster,width=500,height=400, \ bd=0,highlightthickness=0) #Zeichenfläche ohne Rahmen z_flaeche.pack() fenster.update() 3.1.2. Der Ball Der Ball wird zunächst als Klasse erstellt. Über class Klassenname wird ein Name vergeben, dann folgt der Inhalt der Klassen. In einer Klasse werden Methoden definiert, die ein Objekt dieser Klasse haben kann. Diese Definition erfolgt in Form von Funktionen innerhalb der Klasse. class Spielball: #Die Klasse für den Spielball def __init__(self,z_flaeche,color): #Initialisierung mit #den dazugehörigen Parametern des Balls self.z_flaeche=z_flaeche self.ball_id=z_flaeche.create_oval(10,10,25,25,fill=color) #Id des gezeichneten Objekts erfassen self.z_flaeche.move(self.ball_id,245,100) #Objekt mit Hilfe der ID bewegen def bewegen(self): pass #leeres Schlüsselwort, #Platzhalter für spätere Eingaben 29 Achtung Die init-Anweisung hat je zwei Unterstriche vor und nach init. Damit das Programm startet, also den Ball zeigt und sich immer wieder aktualisiert, wird eine sog. Hauptschleife eingefügt. Sie kontrolliert das Programm. while 1: fenster.update_idletasks() # Alle anstehenden Events abarbeiten fenster.update() time.sleep(0.01) #Bildaufbau für 1 hundertstel Sekunde pausieren lassen 3.2. Bewegung für den Ball 3.2.1. Einfache Bewegung Um den Ball zu bewegen wird zunächst die bisher leere Methode bewegen mit Inhalt gefüllt: def bewegen(self): self.z_flaeche.move(self.ball_id,0,-1) #den Ball nach oben schweben lassen. Damit diese Bewegung auch ausgeführt wird, muss das Objekt mit dieser Methode aufgerufen werden. Dazu wird die Hauptschleife entsprechend ergänzt: while 1: ball.bewegen() #Schweben starten fenster.update_idletasks() # Alle anstehenden Events abarbeiten fenster.update() time.sleep(0.01) #Bildaufbau für 1 hundertstel Sekunde pausieren lassen 30 3.2.2. Hin und wieder zurück Damit der Ball die Fläche nicht verlässt, müssen die x – und y - Position des Balls ausgelesen und die Größe der Leinwand erfasst werden, um dann zu erfassen, ob sich der Ball noch innerhalb des Spielfeldes bewegt. Der Klasse Spielball werden deshalb für diese drei Werte drei Variablen hinzugefügt. self.x=0 self.y=-1 self.z_flaeche_height=self.z_flaeche.winfo_height() # Höhe der Fläche über winfo_height auslesen und #in eine Variable legen In die Bewegen-Methode werden nun Bedingungen eingefügt, die die Position des Balls erfassen. Außerdem wird die Positionierung des Balls auf die beiden o.g. Variablen umgestellt. def bewegen(self): self.z_flaeche.move(self.ball_id,self.x,self.y) #den Ball nach oben schweben lassen. pos=self.z_flaeche.coords(self.ball_id) #Ball-Position über coords erfassen, #gibt x und y - Wert als Liste mit 4 Werten zurück if pos[1]<=0.0: self.y=1 if pos[3]>= self.z_flaeche_height: self.y=-1 print(self.z_flaeche.coords(self.ball_id)) #Position des Balls in der Konsole anzeigen Die Print-Funktion an dieser Stelle dient der Kontrolle. Sie gibt in der Konsole die Werte aus, die mit der coords-Funktion ausgelesen wurden. 3.2.3. Irgendwo hin Nun soll der Ball variabel werden. Dafür binden wir einen kleinen Zufall ein. Zunächst wird eine Variable erstellt, die sechs Werte in einer Liste enthält. Dann wird die Abfolge der Listenelemente durcheinandergewirbelt. Der x-Wert des Balls erhält den ersten Wert in der starts-Variablen. starts=[-3,-2,-1,1,2,3] # Variable starts enthält eine Liste random.shuffle(starts) #der Inhalt von starts wird gemischt self.x=starts[0] # x erhält den ersten Wert in der Liste, #die starts enthält self.y=--3 #Ball wird schneller Nun brauchen wir aber auch die Breite der Leinwand, also 31 self.z_flaeche_width=self.z_flaeche.winfo_width() Außerdem muss die bewegen-Funktion um die Bedingungen für die Breite ergänzt werden. if pos[0]<=0: self.x=3 if pos[2] >= self.z_flaeche_width: self.x=-3 3.2.4. Ein Prallbrett Nun muss der Ball im Spielfeld gehalten werden und wenn er unten auftrifft, soll der Spieler verlieren. Zunächst benötigen wir also ein Prallbrett, von dem der Ball abprallen kann. Es wird ebenso erstellt wie der Kreis, nur natürlich als rectangle. class prallbrett: def __init__(self,z_flaeche,color): self.z_flaeche=z_flaeche self.prall_id=z_flaeche.create_rectangle(0,0,100,10,fill=color) self.z_flaeche.move(self.prall_id,200,300) def bewegen(self): # keine Namenskollision, da einer andern #Klasse zugeordnet Pass #leerer Platzhalter Nun muss das Prallbrett als Objekt erstellt und in die Hauptschleife integriert werden: prallbr=prallbrett(z_flaeche,'blue') while 1: ball.bewegen() prallbr.bewegen() Jetzt sieht man das Brett. 3.2.5. Bewegung mit den Maustasten Über Tastaturereignisse wird nun das Prallbrett bewegt. Da sich das Brett nur innerhalb der Leinwand bewegen darf, muss auch die Leinwandbreite erfasst werden. Dieser Wert muss auch in dieser Klasse wieder erfasst werden, da eine Übergabe des Werts aus einer andern Klasse nicht möglich ist. class Prallbrett: def __init__(self,z_flaeche,color): self.z_flaeche=z_flaeche 32 self.prall_id=z_flaeche.create_rectangle(0,0,100,10,fill=color) self.z_flaeche.move(self.prall_id,200,300) self.x=0 self.z_flaeche_width=self.z_flaeche.winfo_width() self.z_flaeche.bind_all('<KeyPress-\ Left>',self.nach_links) self.z_flaeche.bind_all('<KeyPress-\ Right>',self.nach_rechts) def bewegen(self): # keine Namenskollision, da einer andern Klasse zugeordnet self.z_flaeche.move(self.prall_id,self.x,0) pos=self.z_flaeche.coords(self.prall_id) if pos[0]<=0: self.x=1 if pos[2]>= self.z_flaeche_width: self.x=0 def nach_links(self,event): self.x=-2 def nach_rechts(self,event): self.x=2 Die Methoden nach_links und nach_rechts werden die x-Werte des Prallbretts verändern. Die Bewegung erfolgt analog der des Balls, aber nur in der x-Achse. Die Y-Werte werden grundsätzlich auf null gesetzt. Nun sollten Ball und Prellbrett über die Fläche pendeln. 3.2.6. Treffer feststellen Damit das Prallbrett vom Ball erfasst wird, muss es als Parameter in die Initialisierung der Klasse Spielball geschrieben werden. Außerdem muss das Prallbrett als Objektvariable gespeichert werden. Die ersten Zeilen der Klasse Spielball sehen dann so aus: class Spielball: #Die Klasse für den Spielball def __init__(self,z_flaeche,prallbrett,color): #Initialisierung mit den dazugehörigen Parametern des Balls self.z_flaeche=z_flaeche self.prallbrett=prallbrett: Nun muss erfasst werden, ob denn der Ball das Brett getroffen hat. 33 Der Quellcode sieht so aus: def treffer(self,pos): # der Parameter pos macht die Variable #aus der bewegen-Methode nutzbar prallbrett_pos=self.z_flaeche.coords(self.prallbrett.prall_id) if pos[2] >=prallbrett_pos[0] and pos[0] <= prallbrett_pos[2]: if pos[3] >= prallbrett_pos[1] and pos[3] <= prallbrett_pos[3]: return True return False Zunächst wird die Methode definiert. Der Parameter pos macht die Variable aus der bewegenMethode hier nutzbar. Dann wird die Position des Prallbretts erfasst. Die If-Bedingung ist zunächst etwas verwirrend. Zur Erklärung: Der Ball hat vier Werte, die seine Position beschreiben pos[0] x-Wert links oben pos[1] y-Wert links oben pos[2] x-Wert rechts unten pos[3] y-Wert rechts unten Das Prallbrett hat diese vier Werte auch: prallbrett_pos[0] prallbrett_pos[1] prallbrett_pos[2] prallbrett_pos[3] x-Wert links oben y-Wert links oben x-Wert rechts unten y-Wert rechts unten 34 In der Bedingungsschleife wird zunächst der Vergleich der x-Positionen vorgenommen. Dann wird der Vergleich der y-Positionen vorgenommen, aber nur, wenn die Bedingungen der umschließenden Bedingung wahr sind. Dass hier Ober- und Unterseite des Balls und des Prallbretts mit and verkoppelt geprüft werden, liegt daran, dass bei der Bewegung in Dreierschritten, die das Programm vorsieht, das Prallbrett durchschritten sein könnte, bevor dies vom Programm bemerkt wird. Nun kann diese Methode in der bewegen – Methode aufgerufen werden. Dies geschieht, nachdem die y-Werte in der bewegen-Methode geprüft wurden. Die Änderung der y-Variablen i einen negativen Wert bewirkt, dass der Ball die Flugrichtung umdreht. def bewegen(self): self.z_flaeche.move(self.ball_id,self.x,self.y) #den Ball nach oben schweben lassen. pos=self.z_flaeche.coords(self.ball_id) #Ball-Position über coords erfassen, gibt x und y Wert als Liste mit 4 Werten zurück if pos[1]<=0.0: 35 self.y=1 if pos[3]>= self.z_flaeche_height: self.y=-1 if self.treffer(pos) == True: self.y=-3 if pos[0]<=0: self.x=3 if pos[2] >= self.z_flaeche_width: self.x=-3 3.2.7 Quelltext Hier der gesamte Quelltext ohne Kommentare: from tkinter import * import random import time class Spielball: def __init__(self,z_flaeche,prallbrett,color): self.z_flaeche=z_flaeche self.prallbrett=prallbrett self.ball_id=z_flaeche.create_oval(10,10,25,25,fill=color) self.z_flaeche.move(self.ball_id,245,100) starts=[-3,-2,-1,1,2,3] random.shuffle(starts) self.x=starts[0] self.y=--3 self.z_flaeche_height=self.z_flaeche.winfo_height() self.z_flaeche_width=self.z_flaeche.winfo_width() def bewegen(self): self.z_flaeche.move(self.ball_id,self.x,self.y) pos=self.z_flaeche.coords(self.ball_id) if pos[1]<=0.0: self.y=1 if pos[3]>= self.z_flaeche_height: self.y=-1 if self.treffer(pos) == True: self.y=-3 if pos[0]<=0: self.x=3 if pos[2] >= self.z_flaeche_width: self.x=-3 print(self.z_flaeche.coords(self.ball_id)) def treffer(self,pos): prallbrett_pos=self.z_flaeche.coords(self.prallbrett.prall_id) 36 if pos[2] >=prallbrett_pos[0] and pos[0] <= prallbrett_pos[2]: if pos[3] >= prallbrett_pos[1] and pos[3] <= prallbrett_pos[3]: return True return False class prallbrett: def __init__(self,z_flaeche,color): self.z_flaeche=z_flaeche self.prall_id=z_flaeche.create_rectangle(0,0,100,10,fill=color) self.z_flaeche.move(self.prall_id,200,300) self.x=0 self.z_flaeche_width=self.z_flaeche.winfo_width() self.z_flaeche.bind_all('<KeyPressLeft>',self.nach_links) self.z_flaeche.bind_all('<KeyPressRight>',self.nach_rechts) def bewegen(self): self.z_flaeche.move(self.prall_id,self.x,0) pos=self.z_flaeche.coords(self.prall_id) if pos[0]<=0: self.x=1 if pos[2]>= self.z_flaeche_width: self.x=0 def nach_links(self,event): self.x=-4 def nach_rechts(self,event): self.x=4 fenster=Tk() fenster.title('PingPong') fenster.resizable(0,0) fenster.wm_attributes('-topmost',1) z_flaeche=Canvas(fenster,width=500,height=400, bd=0,highlightthickness=0) z_flaeche.pack() fenster.update() prallbrett=prallbrett(z_flaeche,'blue') ball=Spielball(z_flaeche,prallbrett,'red') while 1: ball.bewegen() prallbrett.bewegen() fenster.update_idletasks() fenster.update() time.sleep(0.01) 37 6. Literatur J. Ernesti / P. Kaiser: Python 3. Das umfassende Handbuch. Galileo Computing, Bonn 2012, 3. Aufl. T. Theis: Einstieg in Python. Galileo Computing, Bonn 2014, 4. Aufl. M. Weigend: Python 3. Mitp, Heidelberg 2013, 5. Aufl. M. Weigend: Python GE-PACKT. Mitp, Heidelberg 2013, 5. Aufl. B. Klein: Einführung in Python. Hanser Verlag, München 2013 J. Briggs: Python kinderleicht. dpunkt.verlag, Heidelberg 2013 G. Lingl: Python für Kids. Bhv, Heidelberg, 2010, 4. Auflage W. u. C. Sande: Hello World. Hanser Verlag, München 2014 38