Hlavní navigace

Grafické uživatelské rozhraní v Pythonu: knihovna Tkinter (3.část)

Pavel Tišnovský

V dnešní části seriálu o knihovnách určených pro tvorbu GUI v Pythonu si popíšeme další grafické ovládací prvky z knihovny Tkinter. Bude se jednat o přepínače, seznamy, posuvníky a číselníky (spinboxy).

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

13. Kontejnery

14. Kontejner Frame (rámec)

15. Kontejner LabelFrame (rámec s textovým popiskem)

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

17. Odkazy na Internetu

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 – listboxscrollbar, 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:

Příklad Odkaz
31_radio_button.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/31_radio_but­ton.py
31_radio_button.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/31_radio_but­ton.py
32_radio_button_align.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/32_radio_but­ton_align.py
33_radio_button_default_value.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/33_radio_but­ton_default_value.py
34_ttk_radio_button.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/34_ttk_ra­dio_button.py
35_ttk_button_groups.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/35_ttk_but­ton_groups.py
36_ttk_button_pack.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/36_ttk_but­ton_pack.py
37_listbox.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/37_listbox­.py
38_listbox_bind.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/38_listbox_bin­d.py
39_listbox_scroll.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/39_listbox_scro­ll.py
40_listbox_scroll_linked.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/40_listbox_scro­ll_linked.py
41_spinbox.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/41_spinbox­.py
42_spinbox_values.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/42_spinbox_va­lues.py
43_frame.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/43_frame.py
44_labelframe.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/Tkinter/44_labelfra­me.py

17. Odkazy na Internetu

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