Tkinter


Canvas Widget

Python snake in Canvas
Widget třídy Canvas (plátno) poskytuje grafické objekty pro Tkinter. Mezi těmito objekty jsou čáry, kružnice, obrázky a další. V tomto widgetu je možné kreslit diagramy a grafy, vytvářet grafické editory a používat různé druhy uživatelských objektů.

V našem prvním příkladě si ukážeme jak kreslit přímku.

Pro kreslení přímky použijeme metodu create_line(coords, options). Souřadnice "coords" jsou dány čtyřmi celými čísly: x1, y1, x2, y2. To znamená, že přímka vychází z bodu (x1, y1) do bodu (x2, y2).

Po těchto souřadnicích následuje seznam dalších parametrů, který může být prázdný. Nastavíme na příklad barvu čáry na speciální zelenou naší webové stránky: fill="#476042".

Tento první příklad je záměrně velmi jednoduchý. Vytvoříme plátno a nakreslíme do něho vodorovnou přímku. Tato přímka dělí plátno na dvě oblasti.

Zadání (casting) celočíselné hodnoty v přiřazení "y = int(canvas_height / 2)" je nadbytečné, protože metoda create_line umí pracovat i s hodnotami "float". Následuje kód našeho prvního jednoduchého skriptu:

from tkinter import *

master = Tk()

canvas_width = 80
canvas_height = 40
w = Canvas(master, 
           width=canvas_width,
           height=canvas_height)
w.pack()

y = int(canvas_height / 2)
w.create_line(0, y, canvas_width, y, fill="#476042")


mainloop()


Když tento program spustíme, dostaneme následující okno:

straight horizontal line in canvas

Pro kreslení obdélníků máme metodu create_rectangle(coords, options). Obdélník je definován souřadnicemi jeho rohů a to levého horního a pravého dolního.

Canvas with rectangles and lines

Obrázek, který vidíte nahoře, je vytvořen následujícím kódem:
from tkinter import *

master = Tk()

w = Canvas(master, width=200, height=100)
w.pack()

w.create_rectangle(50, 20, 150, 80, fill="#476042")
w.create_rectangle(65, 35, 135, 65, fill="yellow")
w.create_line(0, 0, 50, 20, fill="#476042", width=3)
w.create_line(0, 100, 50, 80, fill="#476042", width=3)
w.create_line(150,20, 200, 0, fill="#476042", width=3)
w.create_line(150, 80, 200, 100, fill="#476042", width=3)

mainloop()
Následující obrázek se souřadnicemi nám pomůže pochopit použití instrukcí create_line a create_rectangle v našich předchozích příkladech.

Canvas with rectangles and lines plus coordinates

Text na plátně

Nyní si ukážeme, jak dostat text do widgetu canvas. Za tím účelem rozšíříme a upravíme předchozí příklad.
Použijeme metodu create_text(x,y, options). První dva parametry jsou souřadnice textového objektu na plátně. Implicitně je text umisťován do středu. Tuto volbu lze přepsat použitím parametru anchor="w, nw, n, ne, e, se, s, sw". Pravý dolní roh má orientaci NW! Zamýšlený text zavedeme parametrem text="něco"
from tkinter import *

canvas_width = 200
canvas_height = 100

colours = ("#476042", "yellow")
box=[]

for ratio in ( 0.2, 0.35 ):
    box.append( (canvas_width * ratio,
                canvas_height * ratio,
                canvas_width * (1 - ratio),
                canvas_height * (1 - ratio) ) )

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

for i in range(2):
    w.create_rectangle(box[i][0], box[i][1],
	               box[i][2], box[i][3],
	               fill=colours[i])

w.create_line(0, 0,                 # origin of canvas
              box[0][0], box[0][1], # left upper corner of the box[0]
              fill=colours[0], 
              width=3)
w.create_line(0, canvas_height,     # lower left corner of canvas
              box[0][0], box[0][3], # lower left corner of box[0]
              fill=colours[0], 
              width=3)
w.create_line(box[0][2],box[0][1],  # right upper corner of box[0] 
              canvas_width, 0,      # right upper corner of canvas
              fill=colours[0], 
              width=3)
w.create_line(box[0][2], box[0][3], # lower right corner pf box[0]
              canvas_width, canvas_height, # lower right corner of canvas
              fill=colours[0], width=3)

w.create_text(canvas_width / 2,
              canvas_height / 2,
              text="Python")
mainloop()

I když se kód našeho ukázkového programu drasticky změnil, grafický výstup je stejný až na text "Python":

Canvas with Text

Užitečnost nového kódu nahlédnete, když např. změníte výšku plátna na 190 a šířku na 90 a změníte poměr pro první rámeček na 0.3. Představte si, že byste to dělali v našem prvním příkladě. Bylo by to mnohem těžší. Výsledek vypadá takto:

Easily adaptable after code modifications

Oválné objekty

Ovál (nebo ovoid) je jakákoliv křivka, která připomíná vejce (ovum je v Latině vejce). Připomíná elipsu ale není to elipsa. Oválné tvary se špatně definují. Různé křivky, označené jako ovály mají však řadu společných rysů:
  • Jsou to křivky derivovatelné, jednoduché (neprotínající se), konvexní, uzavřené a rovinné.
  • Tvarem se podobají elipse
  • Mají alespoň jednu osu symetrie
Ovál se sestrojuje ze dvou párů oblouků se dvěma rozdílnými poloměry. Zvláštní případ oválu je kružnice.

Eclipses in a Canvas

Na plátně "C" můžeme vytvořit ovál pomocí následující metody:

ov = C.create_oval ( x0, y0, x1, y1, option, ... )


Následující skript nakreslí kružnici kolem bodu (75,75) s poloměrem 25:

from tkinter import *

canvas_width = 190
canvas_height =150

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

w.create_oval(50,50,100,100)

mainloop()


Můžeme definovat jednoduchou funkci, která kreslí kružnici pomocí metody create_oval():
def circle(canvas,x,y, r):
    ov = canvas.create_oval(x-r,y-r,x+r,y+r)
    return ov


Interaktivní kreslení

Chceme napsat aplikaci pro kreslení nebo psaní do plátna. Žel, nakreslit jen jeden bod do plátna nejde. Můžeme však tento problém překonat s použitím malého oválu:
from tkinter import *

canvas_width = 500
canvas_height = 150

def paint( event ):
    python_green = "#476042"
    x1, y1 = ( event.x - 1 ), ( event.y - 1 )
    x2, y2 = ( event.x + 1 ), ( event.y + 1 )
    w.create_oval( x1, y1, x2, y2, fill = python_green )

master = Tk()
master.title( "Painting using Ovals" )
w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack(expand = YES, fill = BOTH)
w.bind( "", paint )

message = Label( master, text = "Press and Drag the mouse to draw" )
message.pack( side = BOTTOM )
    
mainloop()


Painting / Writing Python on Canvas

Kreslení polygonů

Chcete-li nakreslit polygon, musíte zadat souřadnice alespoň tří bodů:
create_polygon(x0,y0, x1,y1, x2,y2, ...)

Pomocí této metody nakreslíme v následujícím příkladě trojúhelník:

from tkinter import *

canvas_width = 200
canvas_height =200
python_green = "#476042"

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

points = [0,0,canvas_width,canvas_height/2, 0, canvas_height]
w.create_polygon(points, outline=python_green, 
            fill='yellow', width=3)

mainloop()

Vypadá takto:

Polygon on a canvas

V následující ukázce si nakreslíme hvězdičku, která by se nám mohla hodit třeba o vánocích. Mnoho programovacích zkušeností k tomu potřebovat nebudeme:

from tkinter import *

canvas_width = 200
canvas_height =200
python_green = "#476042"

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

points = [100, 140, 110, 110, 140, 100, 110, 90, 100,
          60, 90, 90, 60, 100, 90, 110]

w.create_polygon(points, outline=python_green, 
                 fill='yellow', width=3)

mainloop()

Star created with create_polygon of tkinter

Jak jsme již uvedli, tento přístup je poněkud začátečnický. Co kdybychom potřebovali měnit velikost nebo obrys hvězdy? Museli bychom všechny body ručně změnit, což je pracné a náchylné k chybě. Předkládáme novou verzi předchozího skriptu, která představuje "více potu na cvičišti, méně krve na bojišti". Vytvoření hvězdy vložíme do funkce, kde polohu a tvar odvodíme od počátečního bodu a dvou parametrických délek p a t:

star notation

Náš vylepšený program vypadá takto:

from tkinter import *

canvas_width = 400
canvas_height =400
python_green = "#476042"

def polygon_star(canvas, x,y,p,t, outline=python_green,
                 fill='yellow', width = 1):
    points = []
    for i in (1,-1):
        points.extend((x,	      y + i*p))
        points.extend((x + i*t, y + i*t))
        points.extend((x + i*p, y))
        points.extend((x + i*t, y - i * t))

    print(points)

    canvas.create_polygon(points, outline=outline, 
                         fill=fill, width=width)

master = Tk()

w = Canvas(master, 
           width=canvas_width, 
           height=canvas_height)
w.pack()

p = 50
t = 15

nsteps = 10
step_x = int(canvas_width / nsteps)
step_y = int(canvas_height / nsteps)

for i in range(1, nsteps): 
    polygon_star(w, i*step_x, i*step_y, p, t,
            	outline='red' ,fill='gold', width=3)
    polygon_star(w, i*step_x, canvas_height - i*step_y, 
		 p, t, outline='red', fill='gold', width=3)

mainloop()


Hvězdičky přímo vánočně cinkají a jsme si jisti, že nikdo nepochybuje o tom, že by to bylo peklo, kdybychom museli určovat body polygonů tak, jak jsme to dělali v první verzi příkladu:

stars

Bitmapy

Pro vložení bitmapového obrázku do plátna použijeme metodu create_bitmap().
Pro všechny platformy jsou dostupné tyto standardní bitmapy
"error", "gray75", "gray50", "gray25", "gray12", "hourglass", "info", "questhead", "question", "warning"

Následující skript vloží všechny tyto bitmapy do plátna:

from tkinter import *

canvas_width = 300
canvas_height =80

master = Tk()
canvas = Canvas(master, 
                width=canvas_width, 
                height=canvas_height)
canvas.pack()

bitmaps = ["error", "gray75", "gray50", "gray25", "gray12",
           "hourglass", "info", "questhead", "question", "warning"]
nsteps = len(bitmaps)
step_x = int(canvas_width / nsteps)

for i in range(0, nsteps):
    canvas.create_bitmap((i+1)*step_x - step_x/2,50, bitmap=bitmaps[i])

mainloop()


Výsledek vypadá takto:

Bitmap on Canvas

Prvek Image třídy Canvas

Pro vykreslení obrázku na plátno se používá metoda create_image(x0,y0, options ...), která ale nepřijímá obrázek přímo. Místo toho používá objekt, vytvořený metodou PhotoImage(). Tato metoda umí číst jenom obrázky v souborech s příponou GIF a PGM/PPM.

from tkinter import *

canvas_width = 300
canvas_height =300

master = Tk()

canvas = Canvas(master, 
                width=canvas_width, 
                height=canvas_height)
canvas.pack()

img = PhotoImage(file="rocks.ppm")
canvas.create_image(20,20, anchor=NW, image=img)

mainloop()

Okno, vytvořené tímto skriptem, vypadá takto:

Rocks on Canvas

Cvičení

Napište funkci, která vytvoří čtverečkovaný rastr v plátně. Volání této funkce bude mít tvar: checkered(canvas, line_distance). Line_distance je vzdálenost horizontálních a vertikálních přímek.

Explaining the parameter line distance

Solution


from tkinter import *

def checkered(canvas, line_distance):
    # vertical lines at an interval of "line_distance" pixel
    for x in range(line_distance, canvas_width, line_distance):
        canvas.create_line(x, 0, x, canvas_height, fill="#476042")

    # horizontal lines at an interval of "line_distance"  pixel
    for y in range(line_distance, canvas_height, line_distance):
        canvas.create_line(0, y, canvas_width, y, fill="#476042")


master = Tk()
canvas_width = 200
canvas_height = 100 
w = Canvas(master, 
           width=canvas_width,
           height=canvas_height)
w.pack()

checkered(w,10)

mainloop()


Výsledek skriptu vypadá následovně:

Checkered canvas