Obsah
1. Grafické uživatelské rozhraní v Pythonu: knihovna Tkinter (3.část)
2. Ovládací prvek Radiobutton (přepínač)
3. Zarovnání přepínačů v mřížce
4. Nastavení přepínače ve skupině, který má být implicitně vybrán
5. Použití ovládacího prvku ttk.Radiobutton
6. Zjednodušení tvorby rozsáhlejší skupiny přepínačů
7. Ovládací prvek Listbox (seznam)
8. Získání aktuálně vybraného prvku ze seznamu
9. Přidání posuvníku k seznamu
10. Vzájemné provázání posuvníku a seznamu
11. Ovládací prvek Spinbox (číselník) s výběrem numerických hodnot
12. Prvek Spinbox s předvoleným seznamem hodnot
15. Kontejner LabelFrame (rámec s textovým popiskem)
16. Repositář s demonstračními příklady
1. Grafické uživatelské rozhraní v Pythonu: knihovna Tkinter (3.část)
Na začátku dnešního článku si ve stručnosti připomeňme, které základní grafické ovládací prvky je možné v knihovně Tkinter použít pro vytváření grafického uživatelského rozhraní. Některé prvky (widgety) jsme si již popsali v předchozích dvou částech tohoto seriálu; dnes se budeme věnovat prvkům Radiobutton, Spinbox a Listbox. Nezapomeneme ani na popisy kontejnerů Frame a LabelFrame:
Jméno widgetu | Význam a funkce |
---|---|
Label | widget, který zobrazuje v okně či dialogu měnitelný text |
Button | graficky zobrazené tlačítko, které implicitně reaguje na levé tlačítko myši |
Checkbutton | dvoustavový přepínač, který implicitně reaguje na levé tlačítko myši |
Radiobutton | widget, jichž může být sdruženo větší množství, vždy pouze jeden je vybraný |
Scale | dnes nazýván pojmem slider atd., jedná se o widget s posuvnou částí a přidruženým textem, kde se zobrazuje hodnota v závislosti na poloze posuvné části |
Entry | widget, do kterého je možné zapisovat text, k tomu má přidruženo mnoho klávesových zkratek (jde o kombinaci staršího a novějšího standardu) |
Spinbox | widget určený pro zadávání číselných hodnot kombinací klávesnice a myši (i s kontrolou mezí) |
Menu | vertikální menu, které se skládá z více položek |
Menubutton | používá se spolu s menu pro vytváření jednotlivých položek |
Listbox | widget, jež nabízí na výběr libovolné množství řádků s textem |
Scrollbar | podobné widgetu scale s tím rozdílem, že zobrazuje posuvné šipky a naopak nezobrazuje přidruženou číselnou hodnotu |
Frame | jeden z několika nabízených kontejnerů; tento má tvar obdélníka (může být také neviditelný nebo může mít 3D rámeček) |
LabelFrame | další kontejner, tentokrát s volitelným textovým popiskem |
Toplevel | další z kontejnerů, tento se chová jako samostatné okno či dialog |
Bitmap | bitmapa, tj. rastrový obrázek |
Photo/Photoimage | rastrový obrázek, jež může být načten z externího souboru v mnoha různých formátech |
Canvas | widget, na který lze programově vkládat další grafické komponenty (úsečky, oblouky, kružnice, polyčáry, text atd.) |
2. Ovládací prvek Radiobutton (přepínač)
Dalším velmi často používaným ovládacím prvkem (přesněji řečeno specializovanou variantou tlačítka) je takzvaný Radiobutton (přepínač). Tento typ widgetu se od předchozích dvou typů tlačítek (Button a Checkbutton odlišuje především tím, že je používán ve větších skupinách. Z každé skupiny přitom může být vybrán (nastaven) pouze jeden přepínač, od čehož je ostatně odvozen původní anglický název tohoto ovládacího prvku, protože připomíná přepínač kanálů na starších rádiích.
Jak se však pozná, které přepínače patří k sobě, tj. do jedné skupiny? Skupina je určena jménem sledovací proměnné, která musí být pro přepínače v jedné skupině stejná. Přitom je vhodné nastavit pro každý přepínače odlišnou hodnotu, která se do zvolené proměnné uloží v případě jeho výběru. Podívejme se na jednoduchý příklad s pěti přepínači patřícími do stejné skupiny. Sledovací proměnná se jmenuje „radio_var“ a její hodnota odpovídá textu na některém z přepínačů (můžete si však samozřejmě zvolit odlišné hodnoty):
#!/usr/bin/env python import tkinter import sys root = tkinter.Tk() radio_var = tkinter.StringVar() radio1 = tkinter.Radiobutton(root, variable=radio_var, value="Assembler", text="Assembler") radio2 = tkinter.Radiobutton(root, variable=radio_var, value="Basic", text="Basic") radio3 = tkinter.Radiobutton(root, variable=radio_var, value="Brainfuck", text="Brainfuck") radio4 = tkinter.Radiobutton(root, variable=radio_var, value="C", text="C") radio5 = tkinter.Radiobutton(root, variable=radio_var, value="Python", text="Python") showButton = tkinter.Button(root, text="Show var", command=lambda: print(radio_var.get())) quitButton = tkinter.Button(root, text="Exit", background='#ff8080', command=exit) radio1.grid(column=1, row=1) radio2.grid(column=1, row=2) radio3.grid(column=1, row=3) radio4.grid(column=1, row=4) radio5.grid(column=1, row=5) showButton.grid(column=2, row=6, sticky="we", padx=6, pady=6) quitButton.grid(column=2, row=7, sticky="we", padx=6, pady=6) root.mainloop()
Obrázek 1: Skupina přepínačů v dnešním prvním demonstračním příkladu. Povšimněte si, že přepínače nejsou zarovnány a současně se nachází ve stavu, kdy není žádný z nich vybrán (tento stav je navíc matoucí kvůli tomu, že jsou „kuličky“ zobrazeny v každém přepínači).
Obrázek 2: Teprve po explicitním výběru přestanou být přepínače šedivé.
3. Zarovnání přepínačů v mřížce
Předchozí demonstrační příklad umisťoval přepínače do mřížky ve výchozím nastavení: vycentroval je, a to jak ve vertikálním, tak i v horizontálním směru. To je samozřejmě problematické (mírně řečeno), protože každý přepínač má jinak dlouhý text. Nicméně tento nedostatek je možné velmi snadno napravit způsobem, který již známe. Jednoduše totiž levý okraj přepínačů „přilepíme“ k okraji mřížky:
radio1.grid(column=1, row=1, sticky="w") radio2.grid(column=1, row=2, sticky="w") radio3.grid(column=1, row=3, sticky="w") radio4.grid(column=1, row=4, sticky="w") radio5.grid(column=1, row=5, sticky="w")
Upravený příklad vypadá takto:
#!/usr/bin/env python import tkinter import sys root = tkinter.Tk() radio_var = tkinter.StringVar() radio1 = tkinter.Radiobutton(root, variable=radio_var, value="Assembler", text="Assembler") radio2 = tkinter.Radiobutton(root, variable=radio_var, value="Basic", text="Basic") radio3 = tkinter.Radiobutton(root, variable=radio_var, value="Brainfuck", text="Brainfuck") radio4 = tkinter.Radiobutton(root, variable=radio_var, value="C", text="C") radio5 = tkinter.Radiobutton(root, variable=radio_var, value="Python", text="Python") showButton = tkinter.Button(root, text="Show var", command=lambda: print(radio_var.get())) quitButton = tkinter.Button(root, text="Exit", background='#ff8080', command=exit) radio1.grid(column=1, row=1, sticky="w") radio2.grid(column=1, row=2, sticky="w") radio3.grid(column=1, row=3, sticky="w") radio4.grid(column=1, row=4, sticky="w") radio5.grid(column=1, row=5, sticky="w") showButton.grid(column=2, row=6, sticky="we", padx=6, pady=6) quitButton.grid(column=2, row=7, sticky="we", padx=6, pady=6) root.mainloop()
Obrázek 3: Přepínače jsou nyní zarovnány na levý okraj buněk mřížky.
4. Nastavení přepínače ve skupině, který má být implicitně vybrán
Přepínače již tedy máme zarovnané, nicméně chování aplikace je stále „suboptimální“ (korporátní eufemismus), protože při prvním zobrazení dialogu není žádný přepínač vybrán. Implicitně vybraný přepínač se nenastavuje žádnou jeho vlastností (checked=True atd.), ale uložením vhodné hodnoty do sledovací proměnné. Pokud uložená hodnota odpovídá hodnotě specifikované u přepínače, bude tento vybrán. Pokud bude mít více přepínačů stejnou hodnotu, budou vybrány všechny takové přepínače (což uživatele dokonale zmate):
Obrázek 4: Pokud chcete uživatele zmást, můžete více přepínačům přiřadit stejnou hodnotu.
Upravme si náš demonstrační příklad takovým způsobem, aby byl implicitně vybrán programovací jazyk C:
radio_var = tkinter.StringVar() radio_var.set("C")
Upravený příklad vypadá takto:
#!/usr/bin/env python import tkinter import sys root = tkinter.Tk() radio_var = tkinter.StringVar() radio_var.set("C") radio1 = tkinter.Radiobutton(root, variable=radio_var, value="Assembler", text="Assembler") radio2 = tkinter.Radiobutton(root, variable=radio_var, value="Basic", text="Basic") radio3 = tkinter.Radiobutton(root, variable=radio_var, value="Brainfuck", text="Brainfuck") radio4 = tkinter.Radiobutton(root, variable=radio_var, value="C", text="C") radio5 = tkinter.Radiobutton(root, variable=radio_var, value="Python", text="Python") showButton = tkinter.Button(root, text="Show var", command=lambda: print(radio_var.get())) quitButton = tkinter.Button(root, text="Exit", background='#ff8080', command=exit) radio1.grid(column=1, row=1, sticky="w") radio2.grid(column=1, row=2, sticky="w") radio3.grid(column=1, row=3, sticky="w") radio4.grid(column=1, row=4, sticky="w") radio5.grid(column=1, row=5, sticky="w") showButton.grid(column=2, row=6, sticky="we", padx=6, pady=6) quitButton.grid(column=2, row=7, sticky="we", padx=6, pady=6) root.mainloop()
Obrázek 5: Přepínače jsou nyní zarovnány na levý okraj buněk mřížky a jeden z nich je implicitně vybrán.
5. Použití ovládacího prvku ttk.Radiobutton
Namísto přepínače Radiobutton z knihovny Tkinter samozřejmě můžeme použít stejně pojmenovaný řídicí prvek, tentokrát nabízený nadstavbovou knihovnou Ttk. Samotný zdrojový kód se v tomto případě změní jen nepatrně, ovšem výhodou je, že bude možné použít styly napodobující nativní ovládací prvky:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() style = ttk.Style() style.theme_use("alt") style.configure('Red.TButton', background='#ff8080') radio_var = tkinter.StringVar() radio_var.set("Python") radio1 = ttk.Radiobutton(root, variable=radio_var, value="Assembler", text="Assembler") radio2 = ttk.Radiobutton(root, variable=radio_var, value="Basic", text="Basic") radio3 = ttk.Radiobutton(root, variable=radio_var, value="Brainfuck", text="Brainfuck") radio4 = ttk.Radiobutton(root, variable=radio_var, value="C", text="C") radio5 = ttk.Radiobutton(root, variable=radio_var, value="Python", text="Python") showButton = ttk.Button(root, text="Show var", command=lambda: print(radio_var.get())) quitButton = ttk.Button(root, text="Exit", style='Red.TButton', command=exit) radio1.grid(column=1, row=1, sticky="w") radio2.grid(column=1, row=2, sticky="w") radio3.grid(column=1, row=3, sticky="w") radio4.grid(column=1, row=4, sticky="w") radio5.grid(column=1, row=5, sticky="w") showButton.grid(column=2, row=6, sticky="we", padx=6, pady=6) quitButton.grid(column=2, row=7, sticky="we", padx=6, pady=6) root.mainloop()
Obrázek 6: Použití ovládacích prvků ttk.Radiobutton.
6. Zjednodušení tvorby rozsáhlejší skupiny přepínačů
U rozsáhlejší skupiny přepínačů je většinou zbytečné pro každý přepínač implicitně vytvářet novou proměnnou, přidávat přepínač do mřížky atd. Práci si můžeme zjednodušit například tak, že použijeme takové hodnoty přepínačů, které přímo odpovídají jejich textovým popiskům:
langs = ("Assembler", "Basic", "Brainfuck", "C", "Python")
Přepínače následně vytvoříme v jediné smyčce (konkrétně je použita generátorová notace sekvence):
radio_buttons = (ttk.Radiobutton(root, text=lang, value=lang, variable=radio_var) for lang in langs)
Přepínače následně vložíme do mřížky, samozřejmě s korektním zarovnáním:
for i, radio_button in enumerate(radio_buttons): radio_button.grid(column=1, row=i, sticky="w")
Celý zdrojový příklad může vypadat následovně:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() style = ttk.Style() style.theme_use("alt") style.configure('Red.TButton', background='#ff8080') radio_var = tkinter.StringVar() radio_var.set("Python") langs = ("Assembler", "Basic", "Brainfuck", "C", "Python") radio_buttons = (ttk.Radiobutton(root, text=lang, value=lang, variable=radio_var) for lang in langs) showButton = ttk.Button(root, text="Show var", command=lambda: print(radio_var.get())) quitButton = ttk.Button(root, text="Exit", style='Red.TButton', command=exit) for i, radio_button in enumerate(radio_buttons): radio_button.grid(column=1, row=i, sticky="w") showButton.grid(column=2, row=6, sticky="we", padx=6, pady=6) quitButton.grid(column=2, row=7, sticky="we", padx=6, pady=6) root.mainloop()
Obrázek 7: Dialog vytvořený předchozím příkladem.
Jde to však provést ještě jednodušeji, a to tehdy, pokud nahradíme grid manažer za pack manažer, neboť u něj nebude zapotřebí specifikovat indexy buněk v mřížce:
for radio_button in radio_buttons: radio_button.pack(fill="x")
Opět se podívejme na úplný příklad:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() style = ttk.Style() style.theme_use("alt") style.configure('Red.TButton', background='#ff8080') radio_var = tkinter.StringVar() radio_var.set("Python") langs = ("Assembler", "Basic", "Brainfuck", "C", "Python") radio_buttons = (ttk.Radiobutton(root, text=lang, value=lang, variable=radio_var) for lang in langs) showButton = ttk.Button(root, text="Show var", command=lambda: print(radio_var.get())) quitButton = ttk.Button(root, text="Exit", style='Red.TButton', command=exit) for radio_button in radio_buttons: radio_button.pack(fill="x") showButton.pack() quitButton.pack() root.mainloop()
Obrázek 8: Dialog vytvořený předchozím příkladem.
7. Ovládací prvek Listbox (seznam)
Při výběru z většího množství položek se stává použití přepínačů (Radiobutton) neefektivní a většinou se namísto nich využívá další ovládací prvek nazvaný jednoduše Listbox neboli seznam (což je ovšem v kontextu programovacího jazyka Python poněkud matoucí název). Tento ovládací prvek umožňuje, aby uživatel vybral jednu či několik položek z prakticky libovolně dlouhého seznamu řetězců. Vzhledem k tomu, že seznam/listbox je jediným prvkem obsahujícím větší množství hodnot, pracuje se s ním dosti odlišným způsobem, než tomu bylo u přepínačů. Nejprve si ukažme, jakým způsobem se Listbox vytváří a jak se do něj vkládají jednotlivé řetězce (položky):
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() langs = ("Assembler", "Basic", "Brainfuck", "C", "Python") listbox = tkinter.Listbox(root) for lang in langs: listbox.insert(tkinter.END, lang) quitButton = ttk.Button(root, text="Exit", command=exit) listbox.pack() quitButton.pack() root.mainloop()
Z předchozího demonstračního příkladu je patrné, jakým způsobem se s využitím metody listbox.insert do seznamu vkládají jednotlivé položky: jednoduše se zadá pozice vkládané položky a potom text, který se má zobrazit. Pozice může být zadána buď absolutně indexem (celé číslo), nebo je možné použít konstantu END, která zabezpečí vložení položky na konec seznamu (resp. zcela přesně řečeno za aktuální konec seznamu). Kromě metody insert je ovšem možné použít i další metody určené pro manipulaci s položkami, například get (získání textové podoby), delete (vymazání položky), index, scan atd. Také stojí za povšimnutí, že položky se do seznamu mohou vkládat až po jeho zobrazení bez nutnosti explicitně zavolat nějakou překreslovací rutinu.
Obrázek 9: Jednoduchý výběrový seznam.
8. Získání aktuálně vybraného prvku ze seznamu
Při práci se seznamem poprvé narazíme na menší problém: jakým způsobem je vlastně možné reagovat na změnu výběru? K dispozici totiž nemáme ani volbu variable ani volbu command. Řešení spočívá v programovém navázání (bind) procedury na nějaký ovládací prvek. Procedura je zavolána v případě, že na widgetu došlo k nějaké události, typicky při interakci uživatele s widgetem. Pro navázání procedury se používá metoda bind. Této metodě se předává typ události, který je, podobně jako v konfiguračních souborech Motifu, uzavřen do lomených závorek. Za specifikací typu události se nachází příkaz (typicky jméno funkce či anonymní funkce), který se provede v případě, že daná událost skutečně nastane:
listbox.bind("<<ListboxSelect>>", on_listbox_select)
Předchozí příklad je možné upravit tak, že po každé změně výběru ze seznamu (pomocí myši) se zavolá funkce nazvaná on_listbox_select, která vypíše text právě vybrané položky. K tomu využijeme metodu pro získání pozice aktivní položky (curselection). Ve skutečnosti však metoda curselection vrací sekvenci indexů, protože samotný seznam je možné volbou selectmode=EXTENDED přepnout do režimu výběru většího množství položek. V našem případě stačí ze sekvence získat první prvek, protože ve výchozím nastavení je možé ze seznamu vybrat jedinou položku:
def on_listbox_select(event): index = listbox.curselection()[0] global langs print(langs[index])
Úplný zdrojový kód takto upraveného příkladu bude vypadat následovně:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() langs = ("Assembler", "Basic", "Brainfuck", "C", "Python") listbox = tkinter.Listbox(root) for lang in langs: listbox.insert(tkinter.END, lang) def on_listbox_select(event): index = listbox.curselection()[0] global langs print(langs[index]) listbox.bind("<<ListboxSelect>>", on_listbox_select) quitButton = ttk.Button(root, text="Exit", command=exit) listbox.pack() quitButton.pack() root.mainloop()
Obrázek 10: Tento příklad se vizuálně nijak neliší od příkladu předchozího.
9. Přidání posuvníku k seznamu
Delší seznamy se prakticky nikdy nezobrazují v oknech a dialozích celé. Většinou se spokojíme jen se zobrazením několika položek přičemž další položky budou dostupné po posunu obsahu seznamu. V takových případech většinou budeme vyžadovat, aby se vedle seznamu zobrazil i posuvník (scrollbar), který bude se seznamem provázán. V dalším příkladu si napřed ukážeme pouze vytvoření a zobrazení posuvníku, jeho provázání bude předmětem navazující kapitoly.
Při vytváření seznamu můžeme omezit jeho výšku (počet zobrazených prvků):
listbox = tkinter.Listbox(root, height=4)
Následně vytvoříme nový GUI prvek typu Scrollbar:
scrollbar = tkinter.Scrollbar(root)
Jak seznam, tak i scrollbar přidáme na hlavní okno. Povšimněte si, že seznam je „nalepen“ ke všem čtyřem okrajům mřížky, zatímco u scrollbaru požadujeme jeho „nalepení“ k horní a dolní straně (scrollbar tedy bude stejně vysoký, jako seznam):
listbox.grid(column=1, row=1, sticky="nswe") scrollbar.grid(column=2, row=1, sticky="ns") quitButton.grid(column=1, row=2)
Obrázek 11: Scrollbar, který však není navázaný na seznam.
Úplný zdrojový kód takto upraveného příkladu bude vypadat následovně:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() scrollbar = tkinter.Scrollbar(root) langs = ("Assembler", "Basic", "Brainfuck", "C", "C++", "Java", "Julia", "Perl", "Python") listbox = tkinter.Listbox(root, height=4) for lang in langs: listbox.insert(tkinter.END, lang) def on_listbox_select(event): index = listbox.curselection()[0] global langs print(langs[index]) listbox.bind("<<ListboxSelect>>", on_listbox_select) quitButton = ttk.Button(root, text="Exit", command=exit) listbox.grid(column=1, row=1, sticky="nswe") scrollbar.grid(column=2, row=1, sticky="ns") quitButton.grid(column=1, row=2) root.mainloop()
10. Vzájemné provázání posuvníku a seznamu
Scrollbar (posuvník) je nutné se seznamem provázat. Nejprve scrollbar vytvoříme:
scrollbar = ttk.Scrollbar(root)
Pro zcela korektní použití Scrollbaru by bylo vhodné nastavit zpětnou vazbu při změně vybrané položky v seznamu tak, aby se upravila i poloha posuvníku na scrollbaru. To zajistí řádek:
listbox = tkinter.Listbox(root, yscrollcommand=scrollbar.set, height=4)
Nyní již máme k dispozici oba GUI objekty – listbox i scrollbar, takže můžeme určit, jaký příkaz se provede ve chvíli, kdy se změní pozice scrollbaru (typicky myší). Jediné, co musíme provést, je specifikace metody listbox.yview, která se automaticky zavolá a předá se jí nový index v seznamu, který má být viditelný:
scrollbar.config(command=listbox.yview)
Poznámka: samozřejmě je možné pojmenovaný parametr command= specifikovat již při vytváření scrollbaru, ovšem v té chvíli ještě neexistuje objekt se seznamem. Pokud by existoval, nebylo by naopak možné při jeho konstrukci použít parametr yscrollcommand=scrollbar.set, takže se zde jedná o problém slepice-vejce :-)
Obrázek 12: Scrollbar, který je navázaný na seznam.
Úplný zdrojový kód tohoto příkladu vypadá následovně:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() scrollbar = ttk.Scrollbar(root) langs = ("Assembler", "Basic", "Brainfuck", "C", "C++", "Java", "Julia", "Perl", "Python") listbox = tkinter.Listbox(root, yscrollcommand=scrollbar.set, height=4) scrollbar.config(command=listbox.yview) for lang in langs: listbox.insert(tkinter.END, lang) def on_listbox_select(event): index = listbox.curselection()[0] global langs print(langs[index]) listbox.bind("<<ListboxSelect>>", on_listbox_select) quitButton = ttk.Button(root, text="Exit", command=exit) listbox.grid(column=1, row=1, sticky="nswe") scrollbar.grid(column=2, row=1, sticky="ns") quitButton.grid(column=1, row=2) root.mainloop()
11. Ovládací prvek Spinbox (číselník) s výběrem numerických hodnot
Ovládací prvek nazvaný Spinbox (možno volně přeložit jako číselník) je směsicí widgetu Entry a Scrollbaru. Uživatel totiž může do tohoto widgetu zadávat čísla z předem známého intervalu, zvyšování a snižování číselné hodnoty zabezpečí zobrazené šipky (můžeme se přít o to, zda je takový způsob zadávání hodnot efektivní, uživatelé ho však – zdá se – preferují). Tento interval se většinou zadává již při vytváření widgetu s využitím voleb from_ a to. Kromě toho je ještě možné volbou textvariable specifikovat název proměnné, která bude sledovat právě zadanou hodnotu. Použití Spinboxu je ukázáno na následujícím jednoduchém příkladu:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() number = tkinter.IntVar() spinbox = tkinter.Spinbox(root, from_=100, to=120, width=10, textvariable=number) showButton = ttk.Button(root, text="Show var", command=lambda: print(number.get())) quitButton = ttk.Button(root, text="Exit", command=exit) spinbox.grid(column=1, row=1) showButton.grid(column=1, row=2) quitButton.grid(column=2, row=2) root.mainloop()
Obrázek 14: Spinbox použitý pro výběr celého čísla.
Poznámka: pozor si musíme dát zejména na volbu from_, která skutečně obsahuje podtržítko. To je v kontrastu s původní knihovnou Tk (pro jazyk Tcl), kde se tato volba jmenovala from; nicméně pro Tkinter muselo dojít k úpravě, neboť from je v Pythonu rezervovaným klíčovým slovem.
12. Prvek Spinbox s předvoleným seznamem hodnot
Ve skutečnosti se však číselník nemusí používat pouze pro výběr celých čísel ze zadaného intervalu. Pokud totiž s využitím volby values předáme při konstrukci číselníku n-tici s řetězci, bude možné provádět výběr libovolného řetězce z této n-tice. V dalším příkladu je navíc ukázáno použití volby wrap, kterou se specifikuje, zda se výběr položek prováděný v jednom směru zastaví na první/poslední položce či nikoli:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() selected_lang = tkinter.StringVar() langs = ("Assembler", "Basic", "Brainfuck", "C", "C++", "Java", "Julia", "Perl", "Python") spinbox = tkinter.Spinbox(root, values=langs, width=10, textvariable=selected_lang, wrap=True) showButton = ttk.Button(root, text="Show var", command=lambda: print(selected_lang.get())) quitButton = ttk.Button(root, text="Exit", command=exit) spinbox.grid(column=1, row=1) showButton.grid(column=1, row=2) quitButton.grid(column=2, row=2) root.mainloop()
Obrázek 15: Spinbox použitý pro výběr explicitně zapsaných prvků.
13. Kontejnery
Prozatím jsme ovládací prvky vkládali přímo na plochu okna, konkrétně do hlavního a jediného okna aplikace. To nemusí být u rozsáhlejších formulářů a dialogů ta nejlepší volba, protože takové okno nebude mít prvky uspořádané hierarchicky. Ovšem knihovna Tkinter podporuje použití speciálních komponent nazvaných kontejnery. Jedná se o komponenty, na které je možné vkládat různé widget a další kontejnery. Obecně tak interně vzniká stromová datová struktura. V knihovně Tkinter patří mezi kontejnery především prvky nazvané Frame a LabelFrame. Ukažme si použití prvku LabelFrame při tvorbě dialogu, v němž budou v levé straně sdruženy přepínače (Radiobutton ) a na straně druhé pak běžná tlačítka (Button).
Vytvoření hlavního okna nazvaného root, neboť v hierarchii leží v kořenu stromu:
root = tkinter.Tk()
Vytvoření dvou kontejnerů LabelFrame. Tyto kontejnery budou v hierarchii grafických objektů ležet přímo v hlavním okně (viz první parametr):
f1 = ttk.LabelFrame(root, text="Languages") f2 = ttk.LabelFrame(root, text="Commands")
Vytvoření sady přepínačů, které budou umístěny do prvního kontejneru (viz první parametr):
radio_buttons = (ttk.Radiobutton(f1, text=lang, value=lang, variable=radio_var) for lang in langs)
Vytvoření dvou běžných tlačítek umístěných do kontejneru druhého:
showButton = ttk.Button(f2, text="Show var", command=lambda: print(radio_var.get())) quitButton = ttk.Button(f2, text="Exit", style='Red.TButton', command=exit)
Přidání kontejnerů do okna:
f1.grid(column=1, row=1, sticky="ne", padx=6, pady=6) f2.grid(column=2, row=1, sticky="ne", padx=6, pady=6)
14. Kontejner Frame (rámec)
Základní typ kontejneru se jmenuje Frame a používá se velmi jednoduše postupem, který jsme si naznačili v předchozí kapitole. U tohoto kontejneru není zapotřebí zadávat žádný styl vykreslování:
Obrázek 16: Použití kontejneru Frame (samotný kontejner je implicitně neviditelný).
Příklad použití:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() style = ttk.Style() style.theme_use("alt") style.configure('Red.TButton', background='#ff8080') radio_var = tkinter.StringVar() radio_var.set("Python") langs = ("Assembler", "Basic", "Brainfuck", "C", "Python") f1 = ttk.Frame(root) f2 = ttk.Frame(root) radio_buttons = (ttk.Radiobutton(f1, text=lang, value=lang, variable=radio_var) for lang in langs) showButton = ttk.Button(f2, text="Show var", command=lambda: print(radio_var.get())) quitButton = ttk.Button(f2, text="Exit", style='Red.TButton', command=exit) for i, radio_button in enumerate(radio_buttons): radio_button.grid(column=1, row=i, sticky="w") showButton.grid(column=1, row=1, sticky="we", padx=6, pady=6) quitButton.grid(column=1, row=2, sticky="we", padx=6, pady=6) f1.grid(column=1, row=1, sticky="ne", padx=6, pady=6) f2.grid(column=2, row=1, sticky="ne", padx=6, pady=6) root.mainloop()
15. Kontejner LabelFrame (rámec s textovým popiskem)
Ovládací prvek nazvaný LabelFrame slouží ve většině případů jako kontejner, do něhož se ukládají další ovládací prvky. Rozdíl oproti podobnému ovládacímu prvku Frame popsanému v předchozí kapitole spočívá především v tom, že zde může být specifikovaný i nápis, který se zobrazí v levém horním rohu widgetu. Podobně jako u tlačítek, je možné i u kontejnerů Frame a LabelFrame zvolit jejich velikost i to, jakým způsobem se zvýrazní jejich trojrozměrný okraj. V následujícím demonstračním příkladu prozatím použijeme implicitní způsob zobrazení:
Obrázek 17: Použití kontejneru LabelFrame.
Úplný zdrojový kód příkladu, v němž se používají kontejnery LabelFrame, vypadá následovně:
#!/usr/bin/env python import tkinter from tkinter import ttk import sys root = tkinter.Tk() style = ttk.Style() style.theme_use("alt") style.configure('Red.TButton', background='#ff8080') radio_var = tkinter.StringVar() radio_var.set("Python") langs = ("Assembler", "Basic", "Brainfuck", "C", "Python") f1 = ttk.LabelFrame(root, text="Languages") f2 = ttk.LabelFrame(root, text="Commands") radio_buttons = (ttk.Radiobutton(f1, text=lang, value=lang, variable=radio_var) for lang in langs) showButton = ttk.Button(f2, text="Show var", command=lambda: print(radio_var.get())) quitButton = ttk.Button(f2, text="Exit", style='Red.TButton', command=exit) for i, radio_button in enumerate(radio_buttons): radio_button.grid(column=1, row=i, sticky="w") showButton.grid(column=1, row=1, sticky="we", padx=6, pady=6) quitButton.grid(column=1, row=2, sticky="we", padx=6, pady=6) f1.grid(column=1, row=1, sticky="ne", padx=6, pady=6) f2.grid(column=2, row=1, sticky="ne", padx=6, pady=6) root.mainloop()
16. Repositář s demonstračními příklady
Zdrojové kódy všech dnešních demonstračních příkladů naleznete pod následujícími odkazy:
17. Odkazy na Internetu
- 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/