Hlavní navigace

Grafické uživatelské rozhraní v Pythonu: kouzla s kreslicí plochou (canvasem)

Pavel Tišnovský

Jedním z nejmocnějších nástrojů knihovny Tkinter je takzvané plátno (kreslicí plocha) neboli canvas, která umožňuje relativně snadno vykreslovat i složitější schémata či vektorové obrázky a následně manipulovat s jednotlivými objekty.

Obsah

1. Grafické uživatelské rozhraní v Pythonu: kouzla s kreslicí plochou (canvasem)

2. Zaškrtávací tlačítka v menu

3. Výběrová tlačítka v menu

4. Klávesové akcelerátory v menu

5. Práce s grafikou v knihovně Tkinter

6. Widget canvas

7. Vykreslení základních prvků na kreslicí plochu

8. Nastavení stylů vykreslovaných prvků

9. Přiřazení tagů (jmenovek) k vykreslovaným prvkům

10. Použití tagů pro nastavení stylů vykreslování

11. Využití tagů pro reakci na manipulaci s grafickými objekty uživatelem

12. Zkrácení předchozího příkladu

13. Lomené čáry (polyčáry)

14. Spline křivky na canvasu

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

16. Odkazy na Internetu

1. Dokončení předchozího tématu: další možnosti menu

Jeden z největších přínosů většiny grafických uživatelských rozhraní spočívá v možnosti jednoduché a názorné práce s grafickými informacemi (rastrovými obrázky, vektorovou grafikou, 3D grafikou, různými grafy apod.). V knihovně/toolkitu Tk je samozřejmě práce s grafikou podporovaná velmi dobře, zejména v porovnání se stávajícími (kupodivu i těmi modernějšími) aplikačními rozhraními či toolkity. Mnohé toolkity dokonce práci s grafikou nepodporují vůbec nebo nabízí pouhé objektové „obalení“ funkcí z knihoven na nižší úrovni – dobrým příkladem tohoto řešení může být postarší knihovna VCL (Visual Component Library) firmy Borland, která obsahuje určité objekty a metody pro práci s grafikou vytvářenou na canvasu, veškeré volání těchto metod je však prováděno přímo voláním funkcí z Windows API (což omezovalo přenositelnost, dokud nedošlo k přepsání).

Poznámka: praktické použití canvasu (kreslicí plochy, plátna) při tvorbě 2D hry je popsáno v paralelním článku Hra Snake naprogramovana v Pythone s pomocou Tkinter/.

Toolkit Tk, na němž je knihovna Tkinter založena, práci s grafikou pojímá odlišně – grafický výstup není pouze „write only“, protože s grafickými objekty je možné i po jejich vykreslení manipulovat – grafický objekt se totiž chová jako skutečný objekt (z hlediska OOP), s jehož atributy je možné manipulovat a který nabízí různé pomocné metody. Navíc lze k objektům přiřazovat jmenovky (tagy) a provádět vybrané operace se všemi objekty, které obsahují zvolený tag. Knihovna Tkinter vývojářům nabízí i další zajímavé vlastnosti, například takzvaný selektor „current“ s referencí na ten grafický objekt, který vyvolal aktuální událost apod. Právě tzv. canvas a jeho grafické objekty mohou být jedním z důvodů, proč má dnes použití Tkinteru význam, a to i přesto, že existují modernější toolkity.

2. Zaškrtávací tlačítka v menu

Ještě než si popíšeme základy práce s canvasem, musíme dokončit téma, kterému jsme se věnovali minule. Jedná se o práci s menu. Prozatím víme, jak do menu vkládat běžná tlačítka i podmenu.

Ve všech typech menu je však možné použít i zaškrtávací tlačítka. Tato tlačítka se od běžných příkazových tlačítek liší tím, že s sebou mohou nést jednu pravdivostní hodnotu: true/false. Tento typ položek menu tedy nahrazuje klasické check boxy použité v dialogových oknech. Graficky je zaškrtávací tlačítko zobrazeno tak, že se kromě textu a případné ikony u položky menu objevuje prázdný či vyplněný symbol zaškrtnutí. Přesná podoba a umístění symbolu je závislá na použitém grafickém uživatelském rozhraní a nastaveném stylu (resp. skinu). Typicky je tento symbol umístěn vlevo od textu se zarovnáním k levému okraji menu. Zaškrtávací tlačítka se vytváří pomocí příkazu:

identifikátor_menu.add_checkbutton(label="text", onvalue=True, offvalue=False)

S případným uvedením globální proměnné, která bude sledovat stav tlačítka, například:

word_wrap = tkinter.BooleanVar()
 
identifikátor_menu.add_checkbutton(label="text", onvalue=True, offvalue=False,
                           variable=word_wrap)

Pokud má být položka zaškrtnuta, změní se kód následovně:

word_wrap = tkinter.BooleanVar()
word_wrap.set(True)
 
identifikátor_menu.add_checkbutton(label="text", onvalue=True, offvalue=False,
                           variable=word_wrap)

Obrázek 1: Screenshot prvního demonstračního příkladu se zaškrtávacími tlačítky v menu.

Demonstrační příklad s několika zaškrtávacími položkami v menu může vypadat následovně:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
def test():
    print("Test!")
 
 
root = tkinter.Tk()
 
menubar = tkinter.Menu(root)
 
image_names = [
    "document-open",
    "document-save",
    "application-exit",
    "edit-undo",
    "edit-cut",
    "edit-copy",
    "edit-paste",
    "edit-delete",
    "edit-select-all"]
 
images = {}
for image_name in image_names:
    images[image_name] = tkinter.PhotoImage(file="icons/%s.gif" % image_name)
 
filemenu = tkinter.Menu(menubar, tearoff=0)
 
filemenu.add_command(label="Open", underline=0, image=images["document-open"],
                     compound="left")
 
filemenu.add_command(label="Save", underline=0, image=images["document-save"],
                     compound="left")
 
filemenu.add_separator()
 
filemenu.add_command(label="Exit", underline=1,
                     image=images["application-exit"], compound="left",
                     command=root.quit)
 
menubar.add_cascade(label="File", menu=filemenu, underline=0)
 
 
editmenu = tkinter.Menu(menubar, tearoff=0)
 
editmenu.add_command(label="Undo", underline=0, image=images["edit-undo"],
                     compound="left")
 
editmenu.add_separator()
 
editmenu.add_command(label="Cut", underline=2, image=images["edit-cut"],
                     compound="left")
 
editmenu.add_command(label="Copy", underline=0, image=images["edit-copy"],
                     compound="left")
 
editmenu.add_command(label="Paste", underline=0, image=images["edit-paste"],
                     compound="left")
 
editmenu.add_command(label="Delete", underline=2, image=images["edit-delete"],
                     compound="left")
 
editmenu.add_separator()
 
editmenu.add_command(label="Select All", underline=7,
                     image=images["edit-select-all"], compound="left")
 
menubar.add_cascade(label="Edit", menu=editmenu, underline=0)
 
 
word_wrap = tkinter.BooleanVar()
show_all = tkinter.BooleanVar()
show_all.set(True)
 
optionmenu = tkinter.Menu(menubar, tearoff=0)
optionmenu.add_checkbutton(label="Word wrap", onvalue=True, offvalue=False,
                           variable=word_wrap)
optionmenu.add_checkbutton(label="Show all", onvalue=True, offvalue=False,
                           variable=show_all)
 
menubar.add_cascade(label="Options", menu=optionmenu, underline=0)
 
 
colors = ("white", "yellow", "orange", "red", "magenta",
          "blue", "cyan", "green")
colormenu = tkinter.Menu(menubar, tearoff=0)
 
for color in colors:
    colormenu.add_command(label=color, background=color)
 
menubar.add_cascade(label="Colors", menu=colormenu, underline=0)
 
helpmenu = tkinter.Menu(menubar, tearoff=0)
helpmenu.add_command(label="About", command=test, underline=0)
menubar.add_cascade(label="Help", menu=helpmenu, underline=0)
 
root.config(menu=menubar)
root.geometry("320x200")
 
root.mainloop()

3. Výběrová tlačítka v menu

Kromě zaškrtávacích tlačítek je možné v menu všech typů použít i výběrová tlačítka. Zatímco byla zaškrtávací tlačítka na sobě nezávislá, u výběrových tlačítek je zaručeno, že bude vybráno maximálně jedno z nich (nemusí však být vybráno tlačítko žádné). Výběrová tlačítka v menu se tak do značné míry podobají radio buttonům používaným v dialogových boxech; například i tím, že mohou nastavovat hodnotu předem zadané globální proměnné. Výběrová tlačítka se vytváří s využitím příkazu:

identifikátor_menu.add_radiobutton(label="text")

Opět se podívejme na demonstrační příklad, v němž je použito složitější menu s radio tlačítky:

colors = ("white", "yellow", "orange", "red", "magenta",
          "blue", "cyan", "green")
colormenu = tkinter.Menu(menubar, tearoff=0)
 
for color in colors:
    colormenu.add_radiobutton(label=color, background=color)
 
menubar.add_cascade(label="Colors", menu=colormenu, underline=0)

Obrázek 2: Screenshot druhého demonstračního příkladu s výběrovými tlačítky v menu.

Úplný zdrojový kód tohoto příkladu vypadá následovně:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
def test():
    print("Test!")
 
 
root = tkinter.Tk()
 
menubar = tkinter.Menu(root)
 
image_names = [
    "document-open",
    "document-save",
    "application-exit",
    "edit-undo",
    "edit-cut",
    "edit-copy",
    "edit-paste",
    "edit-delete",
    "edit-select-all"]
 
images = {}
for image_name in image_names:
    images[image_name] = tkinter.PhotoImage(file="icons/%s.gif" % image_name)
 
filemenu = tkinter.Menu(menubar, tearoff=0)
 
filemenu.add_command(label="Open", underline=0, image=images["document-open"],
                     compound="left")
 
filemenu.add_command(label="Save", underline=0, image=images["document-save"],
                     compound="left")
 
filemenu.add_separator()
 
filemenu.add_command(label="Exit", underline=1,
                     image=images["application-exit"], compound="left",
                     command=root.quit)
 
menubar.add_cascade(label="File", menu=filemenu, underline=0)
 
 
editmenu = tkinter.Menu(menubar, tearoff=0)
 
editmenu.add_command(label="Undo", underline=0, image=images["edit-undo"],
                     compound="left")
 
editmenu.add_separator()
 
editmenu.add_command(label="Cut", underline=2, image=images["edit-cut"],
                     compound="left")
 
editmenu.add_command(label="Copy", underline=0, image=images["edit-copy"],
                     compound="left")
 
editmenu.add_command(label="Paste", underline=0, image=images["edit-paste"],
                     compound="left")
 
editmenu.add_command(label="Delete", underline=2, image=images["edit-delete"],
                     compound="left")
 
editmenu.add_separator()
 
editmenu.add_command(label="Select All", underline=7,
                     image=images["edit-select-all"], compound="left")
 
menubar.add_cascade(label="Edit", menu=editmenu, underline=0)
 
 
word_wrap = tkinter.BooleanVar()
show_all = tkinter.BooleanVar()
show_all.set(True)
 
optionmenu = tkinter.Menu(menubar, tearoff=0)
optionmenu.add_checkbutton(label="Word wrap", onvalue=True, offvalue=False,
                           variable=word_wrap)
optionmenu.add_checkbutton(label="Show all", onvalue=True, offvalue=False,
                           variable=show_all)
 
menubar.add_cascade(label="Options", menu=optionmenu, underline=0)
 
 
colors = ("white", "yellow", "orange", "red", "magenta",
          "blue", "cyan", "green")
colormenu = tkinter.Menu(menubar, tearoff=0)
 
for color in colors:
    colormenu.add_radiobutton(label=color, background=color)
 
menubar.add_cascade(label="Colors", menu=colormenu, underline=0)
 
helpmenu = tkinter.Menu(menubar, tearoff=0)
helpmenu.add_command(label="About", command=test, underline=0)
menubar.add_cascade(label="Help", menu=helpmenu, underline=0)
 
root.config(menu=menubar)
root.geometry("320x200")
 
root.mainloop()

4. Klávesové akcelerátory v menu

Poslední vlastnost menu, kterou si popíšeme, je zobrazení a navázání klávesových akcelerátorů (například Ctrl+C) k určité položce menu.

Nastavení klávesových akcelerátorů (což je další typ horkých klíčů) se musí provést ve dvou krocích. Nejprve je nutné akcelerátor specifikovat u každé položky menu s využitím volby accelerator, například:

filemenu.add_command(label="Open", underline=0, accelerator="Ctrl+O",
                     command=lambda: print("Open"))

Knihovna Tkinter nás nijak neomezuje v tom, jaký text je u volby accelerator zapsán; samozřejmě je však vhodné, když popis souvisí se skutečně nastaveným akcelerátorem. Příklad z předchozího článku si můžeme nepatrně upravit tak, že u každé položky (kde to má význam) uvedeme popis příslušné klávesové zkratky:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
root = tkinter.Tk()
 
menubar = tkinter.Menu(root)
 
filemenu = tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Open", underline=0, accelerator="Ctrl+O",
                     command=lambda: print("Open"))
filemenu.add_command(label="Save", underline=0, accelerator="Ctrl+S",
                     command=lambda: print("Save"))
filemenu.add_separator()
filemenu.add_command(label="Exit", underline=1, accelerator="Ctrl+Q",
                     command=root.quit)
menubar.add_cascade(label="File", menu=filemenu, underline=0)
 
editmenu = tkinter.Menu(menubar, tearoff=0)
editmenu.add_command(label="Undo", underline=0, accelerator="Ctrl+U",
                     command=lambda: print("Undo"))
editmenu.add_separator()
editmenu.add_command(label="Cut", underline=2, accelerator="Ctrl+X",
                     command=lambda: print("Cut"))
editmenu.add_command(label="Copy", underline=0, accelerator="Ctrl+C",
                     command=lambda: print("Copy"))
editmenu.add_command(label="Paste", underline=0, accelerator="Ctrl+V",
                     command=lambda: print("Paste"))
editmenu.add_command(label="Delete", underline=2,
                     command=lambda: print("Delete"))
editmenu.add_separator()
editmenu.add_command(label="Select All", underline=7, accelerator="Ctrl+A",
                     command=lambda: print("Select All"))
menubar.add_cascade(label="Edit", menu=editmenu, underline=0)
 
helpmenu = tkinter.Menu(menubar, tearoff=0)
helpmenu.add_command(label="About", underline=0, accelerator="F1",
                     command=lambda: print("About"))
menubar.add_cascade(label="Help", menu=helpmenu, underline=0)
 
root.config(menu=menubar)
 
root.mainloop()

Obrázek 3: Screenshot třetího demonstračního příkladu s klávesovými akcelerátory.

To však není vše, protože je ještě nutné akcelerátor na položku menu (resp. přesněji řečeno na nějaký příkaz) navázat. K tomu se používá nám již známý příkaz bind. Pro označení modifikátorů kláves se používají prefixy Control- a Meta-. Také si všimněte, že se rozlišují velikosti písmen stlačených kláves, takže je rozdíl mezi zápisem Control-x a Control-X (druhá možnost nemusí na některých systémech vůbec fungovat):

root.bind('<Control-o>', lambda event: cmd_open())
root.bind('<Control-s>', lambda event: cmd_save())
root.bind('<Control-u>', lambda event: cmd_undo())
root.bind('<F1>', lambda event: cmd_help())
...
...
...
atd. i pro další položky menu

Demonstrační příklad nyní dostane plně funkční podobu – budou v něm fungovat jak horké klíče, tak i akcelerátory (většinou se jim říká klávesové zkratky, ale někdy to může být matoucí kvůli existenci horkých klíčů):

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
def cmd_open():
    print("Open")
 
 
def cmd_save():
    print("Save")
 
 
def cmd_undo():
    print("Undo")
 
 
def cmd_help():
    print("Help")
 
 
root = tkinter.Tk()
 
menubar = tkinter.Menu(root)
 
filemenu = tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Open", underline=0, accelerator="Ctrl+O",
                     command=cmd_open)
filemenu.add_command(label="Save", underline=0, accelerator="Ctrl+S",
                     command=cmd_save)
filemenu.add_separator()
filemenu.add_command(label="Exit", underline=1, accelerator="Ctrl+Q",
                     command=root.quit)
menubar.add_cascade(label="File", menu=filemenu, underline=0)
 
editmenu = tkinter.Menu(menubar, tearoff=0)
editmenu.add_command(label="Undo", underline=0, accelerator="Ctrl+U",
                     command=cmd_undo)
editmenu.add_separator()
editmenu.add_command(label="Cut", underline=2, accelerator="Ctrl+X",
                     command=lambda: print("Cut"))
editmenu.add_command(label="Copy", underline=0, accelerator="Ctrl+C",
                     command=lambda: print("Copy"))
editmenu.add_command(label="Paste", underline=0, accelerator="Ctrl+V",
                     command=lambda: print("Paste"))
editmenu.add_command(label="Delete", underline=2,
                     command=lambda: print("Delete"))
editmenu.add_separator()
editmenu.add_command(label="Select All", underline=7, accelerator="Ctrl+A",
                     command=lambda: print("Select All"))
menubar.add_cascade(label="Edit", menu=editmenu, underline=0)
 
helpmenu = tkinter.Menu(menubar, tearoff=0)
helpmenu.add_command(label="About", underline=0, accelerator="F1",
                     command=cmd_help)
menubar.add_cascade(label="Help", menu=helpmenu, underline=0)
 
root.config(menu=menubar)
 
root.bind('<Control-o>', lambda event: cmd_open())
root.bind('<Control-s>', lambda event: cmd_save())
root.bind('<Control-u>', lambda event: cmd_undo())
root.bind('<F1>', lambda event: cmd_help())
 
root.mainloop()

Obrázek 4: Screenshot čtvrtého demonstračního příkladu s klávesovými akcelerátory.

5. Práce s grafikou v knihovně Tkinter

Práci s grafikou můžeme chápat ve dvou úrovních. Na vyšší úrovni manipulujeme s jednotlivými geometricky popsanými tvary, jakými jsou například úsečka, obdélník, kruh, text či spline křivka. Každému tvaru může být přiřazeno značné množství atributů, jedná se například o tloušťku čáry, kterou je vykreslena hranice objektu, barva a styl výplně uzavřených objektů či font vykreslovaného textu. Na úrovni nižší se může manipulovat přímo s pixely, které tvoří zobrazovaný rastrový obrázek. V knihovně Tkinter je pro práci na vyšší úrovni určen widget canvas spolu s dalšími objektu, na nižší úrovni pak widgety (resp. objekty) bitmap, image a photo. Objekt typu image přitom zapouzdřuje více typů obrázků, od monochromatického BitmapImage přes vícebarevný PhotoImage až po uživatelsky vytvářené formáty.

6. Widget canvas

Jak jsme si již řekli v předchozí kapitole, je pro práci s grafikou na vyšší úrovni nabízen v knihovně Tkinter widget canvas, česky bychom mohli tento název přeložit jako (malířské) plátno. Nenechte se ovšem zmýlit tímto názvem, který má v jiných programových knihovnách odlišný (většinou jednodušší) význam, zde se jedná o velmi mocný widget, který současně slouží jako kontejner pro další objekty.

Vkládané objekty si své vlastnosti zachovávají, tj. lze s nimi i po jejich vykreslení interaktivně i programově pohybovat, měnit jejich vlastnosti apod. K tomu musíme připočítat možnost uložit celé plátno do PostScriptového souboru se zachováním vektorových charakteristik plátna (tj. neprovádí se ztrátový převod na bitmapy). To mj. znamená, že se vlastnosti canvasu do určité míry podobají použití SVG na webových stránkách; naproti tomu „webový canvas“ je vlastně pouze bitmapa, do níž se objekty vykreslují a ihned po jejich vykreslení se jejich vlastnosti ztratí (zůstanou jen barevné pixely).

Každý objekt, který je na plátno umístěn, musí mít specifikovány souřadnice počátečního a koncového bodu, v případě lomených čar, spline křivek a polygonů se samozřejmě specifikuje bodů více. Souřadnice bodů mohou být zadány pomocí více jednotek, jež se rozlišují podle jednoho písmene zapsaného za numerickou hodnotou (hodnoty jsou samozřejmě platné pouze při správně nakalibrované obrazovce, což kupodivu mnoho systémů dodnes nedodržuje):

Přípona Význam
m milimetry
c centimetry
i palce
p body (implicitní hodnota)

Poznámka: pokud potřebujete použít například milimetry, je nutné souřadnice zapisovat do řetězce.

V následující tabulce je ukázáno, jaké objekty je možné na plátno pokládat:

Jméno objektu Význam
arc kruhový nebo eliptický oblouk
bitmap bitmapový obrázek
image obecně vícebarevný rastrový obrázek
line úsečka, lomená úsečka nebo dokonce hladká spline křivka (!)
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
window vnořené okno se samostatným řízením

7. Vykreslení základních prvků na kreslicí plochu

V následujícím demonstračním příkladu je ukázáno jednoduché použití plátna, na které jsou s využitím metody create_TYP_OBJEKTU vloženy jednoduché objekty – úsečka, kruh a text. Vzhledem k tomu, že se při specifikaci souřadnic neuvádí u číselných hodnot žádná přípona (používáme celá čísla), předpokládá se, že jsou všechny hodnoty zapsané v bodech (pixelech). To také znamená, že na různých obrazovkách a při různých rozlišeních budou mít nakreslené objekty jinou velikost, což nám v tomto případě nemusí vadit.

Při vytváření plátna se musí zadat jeho rozměry a samozřejmě i kontejner, na nějž je plátno vloženo:

canvas = tkinter.Canvas(root, width=256, height=256)

Následně upravíme velikost okna (kontejneru) tak, aby se na něj plátno vešlo:

canvas.pack()

Dále již použijeme výše zmíněné metody pro vykreslení objektů. Povšimněte si, že je podporován i objekt s textem:

canvas.create_oval(10, 10, 100, 100)
canvas.create_line(0, 0, 255, 255)
canvas.create_line(0, 255, 255, 0)
 
canvas.create_text(50, 120, text="Hello world!")

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

Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=256, height=256)
canvas.pack()
 
canvas.create_oval(10, 10, 100, 100)
canvas.create_line(0, 0, 255, 255)
canvas.create_line(0, 255, 255, 0)
 
canvas.create_text(50, 120, text="Hello world!")
 
root.mainloop()

8. Nastavení stylů vykreslovaných prvků

Grafické objekty vkládané na plátno mohou mít nastaveno velké množství vlastností, jak je ukázáno na modifikaci předchozího příkladu (nejsou zde ukázány zdaleka všechny vlastnosti, ty jsou uvedeny a podrobně popsány v helpu):

Vlastnost Popis
outline barva obrysu
fill barva výplně
width šířka obrysu
dash vzorek při vykreslování úseček a polyčar
font popis použitého fontu

Obrázek 6: Změna stylu vykreslovaných grafických objektů.

Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=256, height=256)
canvas.pack()
 
canvas.create_oval(10, 10, 100, 100, fill="red", outline="blue", width=3)
canvas.create_line(0, 0, 255, 255, width=5)
canvas.create_line(0, 255, 255, 0, dash=123)
 
canvas.create_text(150, 120, text="Hello world!", fill="white",
                   font="Helvetica 20")
 
root.mainloop()

9. Přiřazení tagů (jmenovek) k vykreslovaným prvkům

Při vkládání objektů na plátno je možné specifikovat i jejich jmenovku (tag), pomocí které je možné objektům přiřadit nějaké vlastnosti či na ně navázat události. Každému objektu dokonce může být v případě potřeby přiřazeno jmenovek více. Jmenovky se používají zejména při hromadné změně vlastností objektů. Nejprve si vytvořme plátno (canvas), na které položíme (vykreslíme) čtyři kruhy vybarvené modře. Tyto kruhy mají přiřazenu stejnou jmenovku – ovals:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=256, height=256)
canvas.pack()
 
canvas.create_oval(10, 10, 110, 110, tags="ovals", fill="blue")
canvas.create_oval(150, 10, 250, 110, tags="ovals", fill="blue")
canvas.create_oval(10, 150, 110, 250, tags="ovals", fill="blue")
canvas.create_oval(150, 150, 250, 250, tags="ovals", fill="blue")
 
root.mainloop()

Obrázek 7: Čtveřice oválů s nastavením fill=„blue“.

10. Použití tagů pro nastavení stylů vykreslování

S využitím metody itemconfig() je možné změnit styl vykreslení libovolného grafického objektu. Objekt či objekty jsou specifikovány selektorem, přičemž mezi podporované selektory patří i jméno tagu. Pokud tedy budeme chtít, aby všechny vykreslované kruhy měly modrou výplň, nemusíme tuto barvu specifikovat u každého kruhu zvlášť; postačuje změnit vlastnost všech objektů se stejným tagem:

canvas.itemconfig("ovals", fill="blue")

Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=256, height=256)
canvas.pack()
 
canvas.create_oval(10, 10, 110, 110, tags="ovals")
canvas.create_oval(150, 10, 250, 110, tags="ovals")
canvas.create_oval(10, 150, 110, 250, tags="ovals")
canvas.create_oval(150, 150, 250, 250, tags="ovals")
 
canvas.itemconfig("ovals", fill="blue")
 
root.mainloop()

Obrázek 8: Čtveřice oválů s nastavením fill=„blue“, ovšem provedeného hromadně přes tagy.

11. Využití tagů pro reakci na manipulaci s grafickými objekty uživatelem

Předchozí demonstrační příklad můžeme upravit takovým způsobem, že do něj přidáme handlery pro trojici událostí. První událost nastane v tom případě, že uživatel na některý objekt najede kurzorem myši (Enter), druhá událost naopak nastane při odjetí kurzoru mimo daný objekt (Leave). Třetí událost vznikne po stisku tlačítka myši nad objektem. Vzhledem k tomu, že je všem čtyřem kruhům nastavena stejná jmenovka, je možné obsluhu událostí napsat pouze jednou.

Pro navázání nějaké akce na událost se použije metoda tag_bind:

canvas.tag_bind("ovals", "<Enter>",
                lambda e: canvas.itemconfig("current", fill="red"))
 
canvas.tag_bind("ovals", "<Leave>",
                lambda e: canvas.itemconfig("current", fill="blue"))
 
canvas.tag_bind("ovals", "<Button-1>",
                lambda e: canvas.itemconfig("current", fill="yellow"))

Povšimněte si použití speciálního selektoru „current“, který označuje ten objekt, který událost vyvolal. Pokud by tento selektor neexistoval, šlo by najít ten objekt, který je nejblíže ke kurzoru myši.

Obrázek 9: Po stisku levého tlačítka myši se změnila vlastnost fill.

Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=256, height=256)
canvas.pack()
 
canvas.create_oval(10, 10, 110, 110, tags="ovals")
canvas.create_oval(150, 10, 250, 110, tags="ovals")
canvas.create_oval(10, 150, 110, 250, tags="ovals")
canvas.create_oval(150, 150, 250, 250, tags="ovals")
 
canvas.itemconfig("ovals", fill="blue")
 
canvas.tag_bind("ovals", "<Enter>",
                lambda e: canvas.itemconfig("current", fill="red"))
canvas.tag_bind("ovals", "<Leave>",
                lambda e: canvas.itemconfig("current", fill="blue"))
canvas.tag_bind("ovals", "<Button-1>",
                lambda e: canvas.itemconfig("current", fill="yellow"))
 
root.mainloop()

12. Zkrácení předchozího příkladu

Předchozí příklad je možné nepatrně zkrátit tak, že použijeme jednu novou funkci určenou pouze pro změnu barvy aktuálního objektu (toho, který vyvolal událost). Tato funkce obsahuje jediný příkaz:

def setcolor(color):
    canvas.itemconfig("current", fill=color)

Samotná specifikace handlerů událostí se zjednoduší. Povšimněte si toho, jak se s využitím lambdy můžeme elegantně zbavit parametru e (event), který jako jediný vstupuje do callback funkce (celý zápis s lambdou je vlastně anonymní callback funkcí):

canvas.tag_bind("ovals", "<Enter>", lambda e: setcolor("red"))
canvas.tag_bind("ovals", "<Leave>", lambda e: setcolor("blue"))
canvas.tag_bind("ovals", "<Button-1>", lambda e: setcolor("yellow"))

Obrázek 10: Po najetí kurzorem myši na objekt se změnila vlastnost fill.

Opět se podívejme na úplný zdrojový kód tohoto demonstračního příkladu:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
def setcolor(color):
    canvas.itemconfig("current", fill=color)
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=256, height=256)
canvas.pack()
 
canvas.create_oval(10, 10, 110, 110, tags="ovals")
canvas.create_oval(150, 10, 250, 110, tags="ovals")
canvas.create_oval(10, 150, 110, 250, tags="ovals")
canvas.create_oval(150, 150, 250, 250, tags="ovals")
 
canvas.itemconfig("ovals", fill="blue")
 
canvas.tag_bind("ovals", "<Enter>", lambda e: setcolor("red"))
canvas.tag_bind("ovals", "<Leave>", lambda e: setcolor("blue"))
canvas.tag_bind("ovals", "<Button-1>", lambda e: setcolor("yellow"))
 
root.mainloop()

Troufnu si tvrdit, že výše uvedený příklad není možné snad v žádném jiném toolkitu napsat jednodušším způsobem.

13. Lomené čáry (polyčáry)

Pro práci s neuzavřenými křivkami lze použít objekt line. Na první pohled se jedná o velmi jednoduchý objekt, ve skutečnosti s ním však lze vytvářet i velmi složité obrazce složené například ze spline křivek. V nejjednodušším případě se pomocí objektu line vykreslí pouze jedna úsečka:

jméno_plátna.create_line(x1, y1, x2, y2, další_volby)

Dále je možné specifikovat více bodů (vrcholů), což značí, že se na plátno vykreslí místo jedné úsečky lomená čára (polyčára, polyline):

jméno_plátna.create_line(x1, y1, x2, y2, ..., xn, yn, další_volby)

Obrázek 11: Lomená čára (polyline) vykreslená čárkovaně.

Podívejme se na velmi jednoduchý příklad použití:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=300, height=300)
canvas.pack()
 
canvas.create_line(0, 150,
                   80, 20,
                   220, 280,
                   300, 150, dash=10)
 
root.mainloop()

14. Spline křivky na canvasu

Při vytváření polyčar je možné specifikovat poměrně velké množství voleb. Pravděpodobně nejpoužívanější jsou volby fill (volba barvy úsečky), width (tloušťka čáry), joinstyle (způsob ukončení hran) a arrow (vykreslení šipek na konci čar). Kromě toho je také možné specifikovat, že se má místo lomené čáry vykreslit spline křivka. To zajišťuje volba spline s booleovskou hodnotou a popř. i splinesteps s celočíselnou hodnotou, kterou se zadává, na kolik úsečkových segmentů má být každá část lomené čáry rozdělena. Způsob práce se spline křivkami je patrný z dalšího demonstračního příkladu:

#!/usr/bin/env python
 
import tkinter
from tkinter import ttk
 
 
root = tkinter.Tk()
 
canvas = tkinter.Canvas(root, width=300, height=300)
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")
 
root.mainloop()

Obrázek 12: Lomená čára (polyline) vykreslená čárkovaně a spline křivka vykreslená tučně červeně.

15. 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:

16. Odkazy na Internetu

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