Python: Tkinter Canvas
Willemers Informatik-Ecke
Kontrollelemente Python-Kurs Python

Ein Canvas bildet die Grundlage für die Grafik in Python Tkinter. Es ist ein Widget und kann darum wie ein normaler Button in ein TkInter-Fenster integriert werden.

Innerhalb eines Canvas werden Linien, Rechtecke oder Kreisausschnitte gezeichnet. Dabei werden die grafischen Elemente als Items behandelt, die auch nachdem sie gezeichnet wurden, manipuliert werden können.

Grafik-Primitive

Es können weitere benannte Parameter angehängt werden:

Ein Beispiel-Raster

Das folgende Canvas der Höhe 240 und der Breite 160 wird durch Linien in ein Raster von acht Spalten und zwölf Zeilen aufgeteilt.
import tkinter, sys

fenster = tkinter.Tk()
# Lege die Dimensionen fest
breite = 160
hoehe  = 240
spalten = 8
zeilen = 12
# Das Canvas wird im Fenster definiert
anzeige = tkinter.Canvas(fenster, width=breite, height=hoehe)
anzeige.pack()
# Zunächst die senkrechten Linien. Der Abstand in xdiff
xdiff = breite/spalten
for x in range(1,spalten+1):
    anzeige.create_line(x*xdiff,0,x*xdiff, hoehe)
    
# Nun die waagerechten Linien. Der Abstand in ydiff
ydiff = hoehe/zeilen
for y in range(1,zeilen+1):
    anzeige.create_line(0,y*ydiff,breite, y*ydiff)
# Starte das Fenster und erzeuge alle Inhalte
fenster.mainloop()

Mausereignis empfangen

Klickt der Benutzer mit der Maus in das Canvas ist das ein Ereignis. Das Programm kann dieses Ereignis durch den Aufruf von bind an sich binden. Dazu werden zwei Parameter übergeben.

Treffer mit Kreisen markieren

Die Callback-Funktion (hier klickCallback) erfährt in ihrem Parameter alle Details über das Ereignis. Der spannenste Wert ist natürlich, wo das Ereignis eingetroffen ist und der befindet sich in den Attributen x und y.
def klickCallback(event):
    print(event.x)
    print(event.y)
    anzeige.create_oval(event.x-2, event.y-2,event.x+2,event.y+2)

anzeige.bind("<Button-1>", klickCallback)
# Starte das Fenster und erzeuge alle Inhalte
fenster.mainloop()
Zur Kontrolle wird der Punkt auf der Konsole ausgegeben und ein kleiner Kreis um den Einschlagort gemalt.

Getroffene Felder markieren

Sollen die Felder markiert werden, muss die Einschussstelle erst einmal durch die Breite der Spalten bzw. durch die Höhe der Zeilen geteilt werden. Dann werden sie in ganze Zahlen umgewandelt.

Damit kann man nun das Rechteck berechnen, indem die Spaltenbreite bzw. die Zeilenhöhe wieder aufmultipliziert wird.

def klickCallback(event):
    x = int(event.x/xdiff)
    y = int(event.y/ydiff)
    anzeige.create_rectangle(x*xdiff,y*ydiff,(x+1)*xdiff, (y+1)*ydiff, fill="red")

anzeige.bind("<Button-1>", klickCallback)
Mit dem fill-Parameter wird das Rechteck rot gefüllt.

Alles zusammen

#!/usr/bin/python
import tkinter, sys
    
fenster = tkinter.Tk()
# Lege die Dimensionen fest
breite = 160
hoehe  = 240
spalten = 8
zeilen = 12
# Das Canvas wird im Fenster definiert
anzeige = tkinter.Canvas(fenster, width=breite, height=hoehe)
anzeige.pack()

# Zunächst die senkrechten Linien. Der Abstand in xdiff
# Zunächst die senkrechten Linien. Der Abstand in xdiff
xdiff = breite/spalten
for x in range(1,spalten+1):
    anzeige.create_line(x*xdiff,0,x*xdiff, hoehe)

# Nun die waagerechten Linien. Der Abstand in ydiff
ydiff = hoehe/zeilen
for y in range(1,zeilen+1):
    anzeige.create_line(0,y*ydiff,breite, y*ydiff)

def klickCallback(event):
    print(event.x)
    print(event.y)
    x = int(event.x/xdiff)
    y = int(event.y/ydiff)
    anzeige.create_rectangle(x*xdiff,y*ydiff,(x+1)*xdiff, (y+1)*ydiff, fill="red")

anzeige.bind("<Button-1>", klickCallback)
# Starte das Fenster und erzeuge alle Inhalte
fenster.mainloop()

Nach drei Klicks sieht das Programm dann so aus:

Resize des Canvas bei Fenservergrößerung

In vielen Fällen soll die Zeichnung innerhalb des Canvas beim Aufziehen des Fensters vergrößert bzw. beim Zusammenziehen verkleinert werden.

Um dies zu erreichen, müssen folgende Schritte durchgeführt werden:

Als Beispiel wird im Fenster eine Olive dargestellt, die durch das Verändern der Fenstergröße ihre Form verändert.

Hier das passende Listing:

import tkinter as tk

class FlexCanvas(tk.Canvas): # FlexflexCanvas beerbt Canvas
    def __init__(self,parent,**kwargs):
        tk.Canvas.__init__(self,parent,**kwargs)
        self.bind("<Configure>", self.on_resize)
        # Merke Dir die Ausgangsgroesse
        self.height = self.winfo_reqheight()
        self.width = self.winfo_reqwidth()

    # EventHandler bei Resize
    def on_resize(self,event):
        # Berechne die eingetretene Vergrößerung
        wscale = event.width/self.width
        hscale = event.height/self.height
        # Merke Dir die jetzige Groesse
        self.width = event.width
        self.height = event.height
        # Setze die Groesse des Canvas um
        self.config(width=self.width, height=self.height)
        # Veraendere die Groesse aller Elemente des canvas
        self.scale("all", 0, 0, wscale, hscale)

    def paint(self): # Zeichne eine Olive
        self.create_oval(0, 0, self.width, self.height, fill="green")

def main():
    fenster = tk.Tk()
    rahmen = tk.Frame(fenster)
    rahmen.pack(fill=tk.BOTH, expand=True)
    flexCanvas = FlexCanvas(rahmen, bg="white", highlightthickness=0)
    flexCanvas.pack(fill=tk.BOTH, expand=True)
    flexCanvas.paint()
    # tag all of the drawn widgets
    flexCanvas.addtag_all("all")
    fenster.mainloop()

if __name__ == "__main__":
    main()

Canvas-Methoden