Obsah
1. Grafické uživatelské rozhraní v Pythonu: kouzla s kreslicí plochou (canvasem)
2. Zaškrtávací tlačítka v menu
4. Klávesové akcelerátory v menu
5. Práce s grafikou v knihovně Tkinter
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
15. Repositář s demonstračními příklady
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
- The Tkinter Canvas Widget
http://effbot.org/tkinterbook/canvas.htm - Ovládací prvek (Wikipedia)
https://cs.wikipedia.org/wiki/Ovl%C3%A1dac%C3%AD_prvek_%28po%C4%8D%C3%ADta%C4%8D%29 - Rezervovaná klíčová slova v Pythonu
https://docs.python.org/3/reference/lexical_analysis.html#keywords - TkDocs: Styles and Themes
http://www.tkdocs.com/tutorial/styles.html - Drawing in Tkinter
http://zetcode.com/gui/tkinter/drawing/ - Changing ttk widget text color (StackOverflow)
https://stackoverflow.com/questions/16240477/changing-ttk-widget-text-color - Hra Breakout napísaná v Tkinteri
https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/ - The Hitchhiker's Guide to Pyhton: GUI Applications
http://docs.python-guide.org/en/latest/scenarios/gui/ - 7 Top Python GUI Frameworks for 2017
http://insights.dice.com/2014/11/26/5-top-python-guis-for-2015/ - GUI Programming in Python
https://wiki.python.org/moin/GuiProgramming - Cameron Laird's personal notes on Python GUIs
http://phaseit.net/claird/comp.lang.python/python_GUI.html - Python GUI development
http://pythoncentral.io/introduction-python-gui-development/ - Graphic User Interface FAQ
https://docs.python.org/2/faq/gui.html#graphic-user-interface-faq - TkInter
https://wiki.python.org/moin/TkInter - Tkinter 8.5 reference: a GUI for Python
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html - TkInter (Wikipedia)
https://en.wikipedia.org/wiki/Tkinter - appJar
http://appjar.info/ - appJar (Wikipedia)
https://en.wikipedia.org/wiki/AppJar - appJar na Pythonhosted
http://pythonhosted.org/appJar/ - Stránky projektu PyGTK
http://www.pygtk.org/ - PyGTK (Wikipedia)
https://cs.wikipedia.org/wiki/PyGTK - Stránky projektu PyGObject
https://wiki.gnome.org/Projects/PyGObject - Stránky projektu Kivy
https://kivy.org/#home - Stránky projektu PyQt
https://riverbankcomputing.com/software/pyqt/intro - PyQt (Wikipedia)
https://cs.wikipedia.org/wiki/PyGTK - Stránky projektu PySide
https://wiki.qt.io/PySide - PySide (Wikipedia)
https://en.wikipedia.org/wiki/PySide - Stránky projektu Kivy
https://kivy.org/#home - Kivy (framework, Wikipedia)
https://en.wikipedia.org/wiki/Kivy_(framework) - QML Applications
http://doc.qt.io/qt-5/qmlapplications.html - KDE
https://www.kde.org/ - Qt
https://www.qt.io/ - GNOME
https://en.wikipedia.org/wiki/GNOME - Category:Software that uses PyGTK
https://en.wikipedia.org/wiki/Category:Software_that_uses_PyGTK - Category:Software that uses PyGObject
https://en.wikipedia.org/wiki/Category:Software_that_uses_PyGObject - Category:Software that uses wxWidgets
https://en.wikipedia.org/wiki/Category:Software_that_uses_wxWidgets - GIO
https://developer.gnome.org/gio/stable/ - GStreamer
https://gstreamer.freedesktop.org/ - GStreamer (Wikipedia)
https://en.wikipedia.org/wiki/GStreamer - Wax Gui Toolkit
https://wiki.python.org/moin/Wax - Python Imaging Library (PIL)
http://infohost.nmt.edu/tcc/help/pubs/pil/ - 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/