Hlavní navigace

Grafické uživatelské rozhraní v Pythonu: kouzla s kreslicí plochou (2. část)

5. 9. 2017
Doba čtení: 23 minut

Sdílet

Budeme pokračovat v popisu vlastností kreslicí plochy (canvasu) v knihovně Tkinter. Popíšeme si tvorbu složitějších tvarů, nastavení vlastností při vykreslování, tvorbu uzavřených obrazců i problematiku „aktivních“ prvků.

Obsah

1. Grafické uživatelské rozhraní v Pythonu: kouzla s kreslicí plochou (2. část)

2. První demonstrační příklad: geometrické tvary, které je možné vložit (vykreslit) na plátno

3. Oblouky

4. Kruhové výseče a kruhové úseče

5. Styly liniových tvarů

6. Čárkované a čerchované úsečky

7. Kresba šipek

8. Uzavřené obrazce

9. Styly vykreslení uzavřených obrazců

10. Rámce (Frame) aneb kontejner pro další widgety uložený přímo na plátnu

11. Repositář s demonstračními příklady

12. Odkazy na Internetu

1. Grafické uživatelské rozhraní v Pythonu: kouzla s kreslicí plochou (2. část)

V předchozí části seriálu o knihovnách určených pro tvorbu grafických uživatelských rozhraní v programovacím jazyku Python jsme se seznámili s takzvanou kreslicí plochou neboli canvasem. Připomeňme si, že canvas slouží jako kontejner, na nějž je možné vkládat různé geometrické tvary, rastrové obrázky nebo text. Jednotlivé tvary jsou v Pythonu reprezentovány plnohodnotnými objekty, takže je možné měnit jejich vlastnosti, naprogramovat reakci na různé události, přemisťovat je nebo je i mazat (u textů je možné změnit i zobrazený řetězec). Canvas je tak možné použít jako základ pro různé systémy typu CAD, editory map, editory schémat atd. Canvas je tak pravděpodobně tou nejlepší technologií, kterou knihovna Tkinter programátorům nabízí, a to i přesto, že není založena na žádné moderní sadě widgetů, ale „pouze“ na knihovně Tk s rozšířením Ttk.

Obrázek 1: Canvas s několika objekty – kruhem, dvojicí úseček a textem.

V následující tabulce (již jsme se s ní ostatně seznámili minule) je ukázáno, jaké objekty je možné na canvas pokládat:

Jméno objektu Význam
arc kruhový nebo eliptický oblouk, též kruhová výseč a úseč
bitmap bitmapový obrázek
image obecně vícebarevný rastrový obrázek
line úsečka, lomená úsečka nebo dokonce hladká křivka (spline) (!)
oval uzavřená kružnice nebo elipsa
polygon uzavřený polygon či tvar vytvořený ze spline křivek
rectangle čtverec nebo obdélník
text textový řetězec
frame(window) vnořené okno (rámec) se samostatným řízením

Z těchto objektů můžeme vybrat geometrické tvary, které mohou být buď otevřené (liniové) nebo uzavřené (potenciálně vyplněné):

Geometrický tvar Typ
line liniový
arc liniový (oblouk) i uzavřený (kruhová výseč a úseč)
rectangle uzavřený
polygon uzavřený
oval uzavřený

Poznámka: u všech uzavřených objektů je možné zakázat jejich výplň. To má dva významy – změní se způsob vykreslení takového geometrického tvaru a navíc bude objekt odlišně reagovat například na přejezd kurzoru myši (neexistující výplň již nebude součástí objektu).

2. První demonstrační příklad: geometrické tvary, které je možné vložit (vykreslit) na plátno

V dnešním prvním demonstračním příkladu je ukázán způsob vykreslení základních geometrických tvarů, ať již otevřených (liniových), tak i uzavřených. Kromě toho je na plátno vykreslena i jednoduchá mřížka; tu použijeme i v příkladech následujících. Význam některých nastavení bude popsán v dalším textu:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height,
                            background='#ccffcc')
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_line(0, 0, 100, 100, fill='red', width=2, dash=8)
 
canvas.create_arc(100, 1, 200, 100, outline='blue', start=45,
                  extent=180, style=tkinter.ARC, width=2)
 
canvas.create_oval(200, 1, 300, 100)
 
canvas.create_oval(325, 25, 375, 75, fill="#a0a0ff")
 
canvas.create_rectangle(50, 125, 150, 175, fill="#a0a0ff")
 
canvas.create_text(300, 150, text="Hello world!", font="Helvetica 20")
 
canvas.create_polygon(50, 225, 200, 300, 50, 375, fill="#80ff80")
 
canvas.create_polygon(250, 225, 400, 300, 250, 375, fill="black",
                      outline="red", width="5")
 
root.mainloop()

Obrázek 2: Screenshot prvního demonstračního příkladu s geometrickými tvary a textem vloženými na canvas.

3. Oblouky

Poněkud zvláštní postavení mezi grafickými entitami má objekt pojmenovaný arc, pomocí kterého lze, jak již ostatně jeho název napovídá, vytvářet několik geometrických tvarů, konkrétně oblouky a také kruhové či eliptické výseče. S využitím arc je tedy možné vytvořit jak liniový objekt, tak i objekt plošný (a potenciálně vyplněný). Příkaz pro vytvoření a zobrazení oblouku vypadá následovně:

canvas.create_arc(x1, y1, x2, y2, další nepovinné volby)

popř.:

canvas.create_arc(seznam souřadnic, další nepovinné volby)

V případě, že skutečně potřebujeme vytvořit oblouk a nikoli kruhovou výseč nebo úseč, musí se použít nepovinný (pojmenovaný) parametry style, jehož hodnota musí být nastavena na konstantu tkinter.ARC:

canvas.create_arc(0, 0, 100, 100,
                  outline='red',
                  style=tkinter.ARC,
                  width=2)

Pomocí souřadnic [x1, y1] a [x2, y2] se specifikuje obalový obdélník oblouku (jedná se o dva protilehlé vrcholy). Pokud se zadá obdélník se stejně dlouhými hranami, je vytvořen kruhový oblouk, v opačném případě se jedná o oblouk eliptický. Nejdůležitějšími nepovinnými volbami jsou start=hodnota a extent=hodnota (nikoli extend, sám tuto chybu dělám velmi často). Těmito volbami se udává počáteční a koncový úhel – mezi zadanými úhly bude oblouk vytvořen. Pomocí voleb outline, fill, stipple atd. je možné nastavit způsob zobrazení obrysů i výplně oblouků.

Obrázek 3: Screenshot druhého demonstračního příkladu s různými oblouky. Povšimněte si vlivu parametrů width a dash na tvar oblouku.

Opět se podívejme na demonstrační příklad, po jehož spuštění se na kreslicí plochu vloží několik oblouků s různými geometrickými a grafickými vlastnostmi:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height,
                            background='#ccffcc')
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_arc(0, 0, 100, 100, outline='red', style=tkinter.ARC,
                  width=2)
 
canvas.create_arc(100, 100, 200, 200, outline='blue', start=45,
                  style=tkinter.ARC, width=2)
 
canvas.create_arc(200, 0, 300, 100, outline='brown', extent=180,
                  style=tkinter.ARC, width=2)
 
canvas.create_arc(300, 100, 400, 200, outline='green', start=45, extent=270,
                  style=tkinter.ARC, width=2)
 
canvas.create_arc(0, 200, 100, 300, outline='red', style=tkinter.ARC, dash=8,
                  width=2)
 
canvas.create_arc(100, 300, 200, 400, outline='blue', start=45,
                  style=tkinter.ARC, dash=8, width=10)
 
canvas.create_arc(200, 200, 300, 300, outline='green', start=45, extent=270,
                  style=tkinter.ARC, dash=3, width=50)
 
canvas.create_arc(290, 290, 390, 390, outline='brown', extent=270,
                  style=tkinter.ARC, dash=80, width=20)
 
root.mainloop()

4. Kruhové výseče a kruhové úseče

Pokud při použití metody canvas.create_arc() nebudeme specifikovat hodnotu pojmenovaného parametru style, popř. mu přiřadíme hodnotu tkinter.PIESLICE, vykreslí se kruhová výseč:

Obrázek 4: Několik kruhových výsečí (pro oblouk o 180° není rozdíl mezi výsečí a úsečí patrný).

Poznámka: výseč ve skutečnosti nemusí být pouze kruhová, protože se, podobně jako u oblouku, tvar specifikuje s využitím souřadnic protilehlých vrcholů obalového obdélníku, takže lze tvořit i eliptickou výseč.

Čtvrtý obrázek byl vykreslen následujícím skriptem:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height,
                            background='#ccffcc')
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_arc(0, 0, 100, 100, fill='#ff8080')
canvas.create_arc(100, 100, 200, 200, fill='#8080ff', start=45)
canvas.create_arc(200, 0, 300, 100, fill='#80ffff', extent=180)
canvas.create_arc(300, 100, 400, 200, fill='#ffff80', start=45, extent=270)
 
canvas.create_arc(0, 200, 100, 300, fill='#ff8080', start=90, extent=270)
canvas.create_arc(100, 300, 200, 400, fill='#8080ff', start=90+45, extent=270)
canvas.create_arc(200, 200, 300, 300, fill='#80ffff', start=180, extent=180)
canvas.create_arc(300, 300, 400, 400, fill='#ffff80', start=-45, extent=90)
 
root.mainloop()

Knihovna Tkinter umožňuje, aby metoda canvas.create_arc() alternativně vykreslila i kruhovou úseč. Postačuje použít pojmenovaný nepovinný parametr style=tkinter.CHORD:

canvas.create_arc(300, 100, 400, 200,
                  fill='#ffff80',
                  start=45,
                  extent=270,
                  style=tkinter.CHORD)

Obrázek 5: Několik kruhových úsečí (pro oblouk o 180° není rozdíl mezi výsečí a úsečí patrný).

Pátý obrázek byl vykreslen následujícím skriptem:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height,
                            background='#ccffcc')
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_arc(0, 0, 100, 100, fill='#ff8080', style=tkinter.CHORD)
 
canvas.create_arc(100, 100, 200, 200, fill='#8080ff', start=45,
                  style=tkinter.CHORD)
 
canvas.create_arc(200, 0, 300, 100, fill='#80ffff', extent=180,
                  style=tkinter.CHORD)
 
canvas.create_arc(300, 100, 400, 200, fill='#ffff80', start=45, extent=270,
                  style=tkinter.CHORD)
 
canvas.create_arc(0, 200, 100, 300, fill='#ff8080', start=90, extent=270,
                  style=tkinter.CHORD)
 
canvas.create_arc(100, 300, 200, 400, fill='#8080ff', start=90+45, extent=270,
                  style=tkinter.CHORD)
 
canvas.create_arc(200, 200, 300, 300, fill='#80ffff', start=180, extent=180,
                  style=tkinter.CHORD)
 
canvas.create_arc(300, 300, 400, 400, fill='#ffff80', start=-45, extent=90,
                  style=tkinter.CHORD)
 
root.mainloop()

5. Styly liniových tvarů

U všech vykreslovaných liniových tvarů je možné specifikovat styl vykreslování. Styly se zadávájí pomocí nepovinných (pojmenovaných) parametrů:

Jméno parametru Význam
fill barva úsečky (již známe)
width šířka liniového tvaru (opět již známe)
dash styl vykreslení (čárkovaná, čerchovaná, …), viz následující kapitolu
cap styl zakončení objektů s šířkou větší než jeden pixel
join styl spojení navazujících úseček s šířkou větší než jeden pixel

Hodnoty parametru cap:

Hodnota Význam
tkinter.BUTT úsečka je ukončena přesně v koncových bodech
tkinter.PROJECTING úsečka je ukončena za koncovými body ve vzdálenosti odpovídající šířce/2
tkinter.ROUND oba konce úsečky jsou zaobleny (polokruh)

Hodnoty parametru join (viz též http://infohost.nmt.edu/tcc/hel­p/pubs/tkinter/web/cap-join-styles.html):

Hodnota Význam
tkinter.ROUND v místě lomu polyčáry je umístěn kruh o průměru odpovídajícímu její šířce
tkinter.BEVEL lom polyčáry je „useknut“ v koncovém bodě
tkinter.MITER hrany úseček jsou protaženy až do místa, kde se protínají

V dalším příkladu je použití některých stylů vykreslení ukázáno. Povšimněte si především způsobu použití stylů join a cap při navazování úseček:

Obrázek 6: Různé styly vykreslení na sebe navazujících úseček (polyčar). U spodních dvanácti polyčar je bílou barvou naznačena „kostra“ polyčáry spojující koncové body (vrcholy)

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height,
                            background='#ccffcc')
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_line(10, 10, 90, 90)
canvas.create_line(110, 10, 190, 90, fill='#8080ff')
canvas.create_line(210, 10, 290, 90, fill='#ffff80', width=8)
canvas.create_line(310, 10, 390, 90, fill='#80aa80', width=8, dash=15)
 
canvas.create_line(10, 110, 90, 190, fill='red', width=12)
canvas.create_line(110, 110, 190, 190, fill='red', width=12,
                   cap=tkinter.BUTT)
canvas.create_line(210, 110, 290, 190, fill='red', width=12,
                   cap=tkinter.PROJECTING)
canvas.create_line(310, 110, 390, 190, fill='red', width=12,
                   cap=tkinter.ROUND)
 
canvas.create_line(10, 110, 90, 190, fill='white')
canvas.create_line(110, 110, 190, 190, fill='white')
canvas.create_line(210, 110, 290, 190, fill='white')
canvas.create_line(310, 110, 390, 190, fill='white')
 
canvas.create_line(10, 210, 50, 290, 90, 210, fill='red', width=12)
canvas.create_line(110, 210, 150, 290, 190, 210, fill='red', width=12,
                   cap=tkinter.BUTT)
canvas.create_line(210, 210, 250, 290, 290, 210, fill='red', width=12,
                   cap=tkinter.PROJECTING)
canvas.create_line(310, 210, 350, 290, 390, 210, fill='red', width=12,
                   cap=tkinter.ROUND)
 
# pomocne usecky
canvas.create_line(10, 210, 50, 290, 90, 210, fill='white')
canvas.create_line(110, 210, 150, 290, 190, 210, fill='white')
canvas.create_line(210, 210, 250, 290, 290, 210, fill='white')
canvas.create_line(310, 210, 350, 290, 390, 210, fill='white')
 
canvas.create_line(10, 310, 50, 390, 90, 310, fill='red', width=12)
canvas.create_line(110, 310, 150, 390, 190, 310, fill='red', width=12,
                   join=tkinter.ROUND)
canvas.create_line(210, 310, 250, 390, 290, 310, fill='red', width=12,
                   join=tkinter.BEVEL)
canvas.create_line(310, 310, 350, 390, 390, 310, fill='red', width=12,
                   join=tkinter.MITER)
 
# pomocne usecky
canvas.create_line(10, 310, 50, 390, 90, 310, fill='white')
canvas.create_line(110, 310, 150, 390, 190, 310, fill='white')
canvas.create_line(210, 310, 250, 390, 290, 310, fill='white')
canvas.create_line(310, 310, 350, 390, 390, 310, fill='white')
 
root.mainloop()

6. Čárkované a čerchované úsečky

Při kresbě liniových obrazců nebo okrajů plošných obrazců je možné zvolit vzorek (styl) úseček – plná (výchozí nastavení), čárkovaná, čerchovaná, střídavá apod. K nastavení vzorku vykreslení úsečky slouží nepovinné parametry pojmenované dash a dashoff. Pokud se do parametru dash předá jediná číselná hodnota, vytvoří se čárkovaná úsečka s délkou jednotlivých úseků odpovídajících specifikované hodnotě. Je také možné zvolit si jiné jednotky, například centimetry, potom se ovšem namísto číselné hodnoty předává řetězec (viz úvodní část tohoto seriálu).

Pokud se však do parametru dash předá n-tice, budou jednotlivé prvky n-tice postupně interpretovány jako délka viditelné části úsečky, délka neviditelné části, opět délka viditelné části atd. Tímto způsobem lze tedy vytvořit například i čerchovanou čáru, tečkovanou apod. Pomocný pojmenovaný parametr dashoff specifikuje posun (offset) vzorku, přičemž posun může být kladný i záporný (to se hodí v těch případech, kdy potřebujeme, aby byl začátek úsečky tvořen neviditelným segmentem):

Demonstrační příklad, v němž se používají nepovinné parametry pojmenované dash a dashoff, vypadá následovně:

Obrázek 7: Úsečky vykreslené pomocí segmentů rozdílné délky.

Následuje výpis zdrojového kódu demonstračního příkladu:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height,
                            background='#ccffcc')
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_line(10, 10, 90, 90)
canvas.create_line(110, 10, 190, 90, fill='#8080ff')
canvas.create_line(210, 10, 290, 90, fill='#ffff80', width=8)
canvas.create_line(310, 10, 390, 90, fill='#80aa80', width=8, dash=15)
 
canvas.create_line(10, 110, 90, 190, width=2, dash=(12, 3))
canvas.create_line(110, 110, 190, 190, width=2, dash=(9, 6))
canvas.create_line(210, 110, 290, 190, width=2, dash=(6, 9))
canvas.create_line(310, 110, 390, 190, width=2, dash=(3, 12))
 
canvas.create_line(10, 210, 90, 290, width=2, dash=(12, 2, 2, 2))
canvas.create_line(110, 210, 190, 290, width=2, dash=(12, 2, 4, 2))
canvas.create_line(210, 210, 290, 290, width=2, dash=(12, 4, 2, 4))
canvas.create_line(310, 210, 390, 290, width=2, dash=(12, 2, 2, 2, 2, 2))
 
canvas.create_line(10, 310, 90, 390, width=2, dash=(12, 2, 2, 2), dashoff=0)
canvas.create_line(110, 310, 190, 390, width=2, dash=(12, 2, 4, 2), dashoff=5)
canvas.create_line(210, 310, 290, 390, width=2, dash=(12, 4, 2, 4), dashoff=10)
canvas.create_line(310, 310, 390, 390, width=2, dash=(12, 2, 2, 2, 2, 2),
                   dashoff=-5)
 
root.mainloop()

7. Kresba šipek

V některých aplikacích může být velmi užitečná podpora pro kresbu šipek k libovolné úsečce. Lze specifikovat, který konec (či konce) úsečky má být opatřen šipkou:

canvas.create_line(110, 50, 190, 50, arrow=tkinter.FIRST)
canvas.create_line(210, 50, 290, 50, arrow=tkinter.LAST)
canvas.create_line(310, 50, 390, 50, arrow=tkinter.BOTH)

Také je možné nastavit tvar šipky pomocí tří vzdáleností d1, d2 a d3, které se předávají pomocí n-tice tak, jak je to ukázáno níže:

canvas.create_line(10, 350, 90, 350,
                   width=2,
                   arrow=tkinter.BOTH,
                   arrowshape=(10, 10, 10))

Díky tomu je možné vytvořit šipky prakticky jakéhokoli žádaného tvaru, o čemž se můžete přesvědčit pohledem na tento obrázek:

Poznámka: šipky jsou samozřejmě vykresleny korektně i ve chvíli, kdy je úsečka kreslena pod jiným úhlem.

Obrázek 8: Uživatelsky definované šipky.

Následuje výpis zdrojového kódu demonstračního příkladu:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height,
                            background='#ccffcc')
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_line(10, 50, 90, 50)
canvas.create_line(110, 50, 190, 50, arrow=tkinter.FIRST)
canvas.create_line(210, 50, 290, 50, arrow=tkinter.LAST)
canvas.create_line(310, 50, 390, 50, arrow=tkinter.BOTH)
 
canvas.create_line(10, 150, 90, 150, width=5, )
canvas.create_line(110, 150, 190, 150, width=5, arrow=tkinter.FIRST)
canvas.create_line(210, 150, 290, 150, width=5, arrow=tkinter.LAST)
canvas.create_line(310, 150, 390, 150, width=5, arrow=tkinter.BOTH)
 
canvas.create_line(10, 250, 90, 250, width=2,   arrow=tkinter.LAST,
                   arrowshape=(10, 10, 10))
canvas.create_line(110, 250, 190, 250, width=2, arrow=tkinter.LAST,
                   arrowshape=(10, 20, 10))
canvas.create_line(210, 250, 290, 250, width=2, arrow=tkinter.LAST,
                   arrowshape=(10, 5, 10))
canvas.create_line(310, 250, 390, 250, width=2, arrow=tkinter.LAST,
                   arrowshape=(10, 0, 10))
 
canvas.create_line(10, 350, 90, 350, width=2, arrow=tkinter.BOTH,
                   arrowshape=(10, 10, 10))
canvas.create_line(110, 350, 190, 350, width=2, arrow=tkinter.BOTH,
                   arrowshape=(10, 20, 10))
canvas.create_line(210, 350, 290, 350, width=2, arrow=tkinter.BOTH,
                   arrowshape=(10, 5, 10))
canvas.create_line(310, 350, 390, 350, width=2, arrow=tkinter.BOTH,
                   arrowshape=(10, 0, 10))
 
root.mainloop()

8. Uzavřené obrazce

Kromě již popsaných kruhových výsečí a kruhových úsečí je možné pracovat i s dalšími uzavřenými (a tedy potenciálně vyplněnými) geometrickými tvary. Dalším jednoduchým uzavřeným obrazcem je obdélník, který je vytvořen pomocí metody canvas.create_rectangle. Vytvoření se provádí následovně:

canvas.create_rectangle(20, 220, 80, 280, fill='#ff8080')

Podobně jako u oblouku, i u obdélníků (a samozřejmě i čtverců) je možné volit styl výplně a obrysu.

Dále je možné pracovat s kruhem či elipsou. Ty se vytváří s využitím metody nazvané canvas.create_oval(), například:

canvas.create_oval(x1, y1, x2, y2, fill='#8080ff')

Souřadnice [x1, y1] a [x2, y2] udávají protilehlé vrcholy obalového obdélníka, podobně jako jsme to již viděli v případě oblouku. Pokud má obalový obdélník stejně dlouhé hrany, vykreslí se kruh či kružnice (podle nastavení vyplňování), v opačném případě se vykreslí elipsa. Způsob vyplnění tohoto typu objektu je stejný, jako u předchozích dvou typů uzavřených geometrických tvarů.

Nejsložitější uzavřené tvary se tvoří s využitím metody canvas.create_polygon(). Při vytváření tohoto objektu se může zadat prakticky libovolné množství souřadnic vrcholů. Buď se všechny souřadnice specifikují přímo:

canvas.create_polygon(310, 20, 390, 20, 350, 80,
                      fill='#ffff80',
                      outline='black')

nebo (a v praxi mnohem častěji) pomocí seznamu souřadnic.

canvas.create_polygon(seznam_se_souřadnicemi,
                      fill='#ffff80',
                      outline='black')

Polygon může být buď vyplněný nebo prázdný. Kromě klasického polygonu, jehož hrany jsou tvořeny polyčárou, je možné vytvářet i tvar ohraničený spline křivkami. Nastavení spline křivek se děje pomocí voleb smooth (povoluje či zakazuje spline křivky) a splinesteps hodnota (způsob rozdělení ideální křivky na úsečkové segmenty).

Obrázek 9: Uzavřené tvary.

Opět se podívejme na jednoduchý demonstrační příklad, v němž jsou tyto objekty vykresleny:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height,
                            background='#ccffcc')
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_rectangle(10, 30, 90, 70, fill='#ff8080')
 
canvas.create_rectangle(30, 110, 70, 190, fill='#ff8080')
 
canvas.create_rectangle(20, 220, 80, 280, fill='#ff8080')
 
canvas.create_oval(110, 30, 190, 70, fill='#8080ff')
 
canvas.create_oval(130, 110, 170, 190, fill='#8080ff')
 
canvas.create_oval(120, 220, 180, 280, fill='#8080ff')
 
canvas.create_polygon(210, 20, 290, 20, 250, 80, fill='#ffff80')
 
canvas.create_polygon(310, 20, 390, 20, 350, 80, fill='#ffff80',
                      outline='black')
 
canvas.create_polygon(210, 120, 250, 140, 290, 120, 250, 180,
                      fill='#80ff80')
 
canvas.create_polygon(310, 120, 350, 140, 390, 120, 350, 180, fill='#80ff80',
                      outline='black')
 
canvas.create_polygon(210, 220, 290, 220, 250, 280, fill='#ffff80',
                      smooth=1)
 
canvas.create_polygon(310, 220, 390, 220, 350, 280, fill='#ffff80',
                      outline='black', smooth=1)
 
canvas.create_polygon(210, 320, 250, 340, 290, 320, 250, 380, fill='#80ff80',
                      smooth=1)
 
canvas.create_polygon(310, 320, 350, 340, 390, 320, 350, 380, fill='#80ff80',
                      outline='black', smooth=1)
 
 
root.mainloop()

9. Styly vykreslení uzavřených obrazců

(Nejenom) u uzavřených obrazců můžeme zvolit, jaká vlastnost se má změnit ve chvíli, kdy se nad obrazcem pohybuje kurzor myši:

Vlastnost neaktivní entity Vlastnost po najetí myší
fill activefill
outline activeoutline
width activewidth
dash activedash

Obrázek 10: Uzavřené tvary s explicitně zadanými vlastnostmi.

Následující příklad je tedy interaktivní, protože se jednotlivé entity změní ve chvíli, kdy na ně najedete kurzorem myši:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height,
                            background='#ccffcc')
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_rectangle(10, 30, 90, 70, fill='#ff8080', width=2,
                        activefill='white')
 
canvas.create_rectangle(110, 30, 190, 70, fill='#ff8080', width=2,
                        dash=(5, 5), activedash=1)
 
canvas.create_rectangle(30, 110, 70, 190, fill='#ff8080',
                        activeoutline='yellow')
 
canvas.create_rectangle(20, 220, 80, 280, fill='#ff8080',
                        activeoutline='yellow', activewidth='5')
 
canvas.create_oval(130, 110, 170, 190, fill='#8080ff', width=2,
                   activedash=(10, 10))
 
canvas.create_oval(120, 220, 180, 280, fill=None, activefill='#8080ff')
 
canvas.create_rectangle(210, 30, 290, 70, fill=None, width=2,
                        activefill='white')
 
canvas.create_rectangle(310, 30, 390, 70, fill=None, width=2, dash=(5, 5),
                        activedash=1)
 
canvas.create_rectangle(230, 110, 270, 190, fill=None, activeoutline='yellow',
                        width=5)
 
canvas.create_rectangle(220, 220, 280, 280, fill=None, activeoutline='yellow',
                        activewidth='5')
 
canvas.create_oval(330, 110, 370, 190, fill=None, width=2, activedash=(10, 10))
 
canvas.create_oval(320, 220, 380, 280, fill=None, activefill='#8080ff',
                   width=5)
 
canvas.create_line(10, 330, 90, 370, fill='#80ff80', width=2,
                   activefill='white')
 
canvas.create_line(110, 330, 190, 370, fill='#80ff80', width=20,
                   activefill='white')
 
canvas.create_line(210, 330, 290, 370, fill='#80ff80', width=20,
                   activefill='white', dash=10)
 
 
root.mainloop()

10. Rámce (Frame) aneb kontejner pro další widgety uložený přímo na plátnu

Nejpodivnějším objektem, který můžeme na canvas položit, je takzvaný rámec neboli frame. Rámec je z pohledu uživatele tvořen obdélníkem s volitelnou barvou pozadí a konfigurovatelným reliéfem; z pohledu programátora se pak jedná o kontejner, na který je možné vkládat další widgety, například tlačítka apod. To je další důkaz toho, že canvas není pouhou „glorifikovanou bitmapou“, ale integrální součástí grafického uživatelského rozhraní, které je možné v Tkinteru vytvořit.

V následujícím příkladu je vytvořen prázdný rámec, se zvoleným reliéfem. Kromě plátna jsou do GUI aplikace vloženy další dva widgety – textové návěští a tlačítko. Ty jsou ovšem vloženy přímo do hlavního okna aplikace, takže jsou zobrazeny pod canvasem (protože canvas byl pomocí .pack do okna vložen jako první):

Obrázek 11: Prázdný rámec na canvasu, řídicí prvky jsou zobrazeny pod canvasem.

Následuje výpis zdrojového kódu demonstračního příkladu:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
def exit():
    sys.exit(0)
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=300, height=300, background='white')
canvas.pack()
 
canvas.create_line(0, 150,
                   80, 20,
                   220, 280,
                   300, 150, dash=10)
 
canvas.create_line(0, 150,
                   80, 20,
                   220, 280,
                   300, 150, smooth=True, width=2, fill="red")
 
nested_window = tkinter.Frame(relief=tkinter.RAISED)
canvas.create_window(150, 150, width=150, height=150, window=nested_window)
 
label = tkinter.Label(root, text="Hello world!")
button = tkinter.Button(root, text="Close window", command=exit)
 
label.pack()
button.pack()
 
root.mainloop()

V příkladu dalším je již vše jinak – dva widgety (textové návěští a tlačítko) jsou nyní vloženy na rámec umístěný na canvasu:

Obrázek 12: Řídicí prvky jsou vloženy do rámce na canvasu.

Opět následuje výpis zdrojového kódu demonstračního příkladu:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
import sys
 
 
def exit():
    sys.exit(0)
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=300, height=300, background='white')
canvas.pack()
 
canvas.create_line(0, 150,
                   80, 20,
                   220, 280,
                   300, 150, dash=10)
 
canvas.create_line(0, 150,
                   80, 20,
                   220, 280,
                   300, 150, smooth=True, width=2, fill="red")
 
nested_window = tkinter.Frame(relief=tkinter.RAISED)
canvas.create_window(150, 150, width=150, height=150, window=nested_window)
 
label = tkinter.Label(nested_window, text="Hello world!")
button = tkinter.Button(nested_window, text="Close window", command=exit)
 
label.pack()
button.pack()
 
root.mainloop()

11. Repositář s demonstračními příklady

Zdrojové kódy všech dnes popsaných demonstračních příkladů naleznete pod následujícími odkazy:

ict ve školství 24

Obrázek 13: Bitmapa se specifikovanou barvou popředí a pozadí (ukázka z dalšího pokrčování seriálu).

Obrázek 14: Texty s různě nastavenými kotvicími body (ukázka z dalšího pokrčování seriálu).

12. Odkazy na Internetu

  1. Hra Breakout napísaná v Tkinteri
    https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/
  2. Hra Snake naprogramovaná v Pythone s pomocou Tkinter
    https://www.root.cz/clanky/hra-snake-naprogramovana-v-pythone-s-pomocou-tkinter/
  3. The Tkinter Canvas Widget
    http://effbot.org/tkinter­book/canvas.htm
  4. Ovládací prvek (Wikipedia)
    https://cs.wikipedia.org/wi­ki/Ovl%C3%A1dac%C3%AD_prvek_­%28po%C4%8D%C3%ADta%C4%8D%29
  5. Rezervovaná klíčová slova v Pythonu
    https://docs.python.org/3/re­ference/lexical_analysis.html#ke­ywords
  6. TkDocs: Styles and Themes
    http://www.tkdocs.com/tuto­rial/styles.html
  7. Drawing in Tkinter
    http://zetcode.com/gui/tkin­ter/drawing/
  8. Changing ttk widget text color (StackOverflow)
    https://stackoverflow.com/qu­estions/16240477/changing-ttk-widget-text-color
  9. The Hitchhiker's Guide to Pyhton: GUI Applications
    http://docs.python-guide.org/en/latest/scenarios/gui/
  10. 7 Top Python GUI Frameworks for 2017
    http://insights.dice.com/2014/11/26/5-top-python-guis-for-2015/
  11. GUI Programming in Python
    https://wiki.python.org/mo­in/GuiProgramming
  12. Cameron Laird's personal notes on Python GUIs
    http://phaseit.net/claird/com­p.lang.python/python_GUI.html
  13. Python GUI development
    http://pythoncentral.io/introduction-python-gui-development/
  14. Graphic User Interface FAQ
    https://docs.python.org/2/faq/gu­i.html#graphic-user-interface-faq
  15. TkInter
    https://wiki.python.org/moin/TkInter
  16. Tkinter 8.5 reference: a GUI for Python
    http://infohost.nmt.edu/tcc/hel­p/pubs/tkinter/web/index.html
  17. TkInter (Wikipedia)
    https://en.wikipedia.org/wiki/Tkinter
  18. appJar
    http://appjar.info/
  19. appJar (Wikipedia)
    https://en.wikipedia.org/wiki/AppJar
  20. appJar na Pythonhosted
    http://pythonhosted.org/appJar/
  21. Stránky projektu PyGTK
    http://www.pygtk.org/
  22. PyGTK (Wikipedia)
    https://cs.wikipedia.org/wiki/PyGTK
  23. Stránky projektu PyGObject
    https://wiki.gnome.org/Pro­jects/PyGObject
  24. Stránky projektu Kivy
    https://kivy.org/#home
  25. Stránky projektu PyQt
    https://riverbankcomputin­g.com/software/pyqt/intro
  26. PyQt (Wikipedia)
    https://cs.wikipedia.org/wiki/PyGTK
  27. Stránky projektu PySide
    https://wiki.qt.io/PySide
  28. PySide (Wikipedia)
    https://en.wikipedia.org/wiki/PySide
  29. Stránky projektu Kivy
    https://kivy.org/#home
  30. Kivy (framework, Wikipedia)
    https://en.wikipedia.org/wi­ki/Kivy_(framework)
  31. QML Applications
    http://doc.qt.io/qt-5/qmlapplications.html
  32. KDE
    https://www.kde.org/
  33. Qt
    https://www.qt.io/
  34. GNOME
    https://en.wikipedia.org/wiki/GNOME
  35. Category:Software that uses PyGTK
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_PyGTK
  36. Category:Software that uses PyGObject
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_PyGObject
  37. Category:Software that uses wxWidgets
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_wxWidgets
  38. GIO
    https://developer.gnome.or­g/gio/stable/
  39. GStreamer
    https://gstreamer.freedesktop.org/
  40. GStreamer (Wikipedia)
    https://en.wikipedia.org/wi­ki/GStreamer
  41. Wax Gui Toolkit
    https://wiki.python.org/moin/Wax
  42. Python Imaging Library (PIL)
    http://infohost.nmt.edu/tcc/hel­p/pubs/pil/
  43. Why Pyjamas Isn’t a Good Framework for Web Apps (blogpost z roku 2012)
    http://blog.pyjeon.com/2012/07/29/why-pyjamas-isnt-a-good-framework-for-web-apps/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.