Obsah
1. Tvorba grafického uživatelského rozhraní v Pythonu s využitím knihovny PySimpleGUI (4)
2. Reakce prvků grafického uživatelského rozhraní na změnu velikosti okna
3. Reakce všech prvků na změnu velikosti okna
4. Zbylé ovládací prvky nabízené knihovnou PySimpleGUI
7. Další způsoby zobrazení slideru
8. ProgressBar: indikátor probíhající operace
9. Propojení posuvníku s progress barem
10. Výběrové menu (OptionMenu)
11. Výběr prvků ze seznamu (Listbox)
13. Data obrázku uložená přímo ve zdrojovém kódu
15. Složitější animace – vizualizace probíhající operace
18. Obsah poslední části seriálu o PySimpleGUI
19. Repositář s demonstračními příklady
1. Tvorba grafického uživatelského rozhraní v Pythonu s využitím knihovny PySimpleGUI (dokončení)
Jak již bylo zmíněno v perexu, dokončíme dnes popis většiny zbývajících ovládacích prvků (widgetů) nabízených knihovnou PySimpleGUI i způsob jejich použití. S informacemi získanými ve všech čtyřech článcích (předchozí články najdete na [1] [2] a [3]) je možné tvořit i aplikace s poměrně komplikovaným uživatelským rozhraním – a přitom bude GUI část dosti jednoduchá a přehledná.
Obrázek 1: Nepatrně složitější aplikace s GUI, která je součástí samotného balíčku PySimpleGUI.
2. Reakce prvků grafického uživatelského rozhraní na změnu velikosti okna
Nejprve si ukažme, jakým způsobem mohou prvky grafického uživatelského rozhraní (tedy například tlačítka) reagovat na změnu velikosti okna. Prozatím víme, že mezi ovládací prvky je možné v případě potřeby vkládat „pružiny“ představované neviditelným objektem PySimpleGUI.Push, který dokáže zajistit, že se mezi prvky GUI vloží stejně velké mezery. Pružiny mohou být na jednotlivých řádcích umístěny zcela libovolně, což je ostatně patrné z následujícího kódu:
... ... ... [ sg.Push(), sg.Button("Button1"), sg.Button("Button2"), ], [ sg.Button("Button1"), sg.Push(), sg.Button("Button2"), ], [ sg.Button("Button1"), sg.Button("Button2"), sg.Push(), ], [ sg.Push(), sg.Button("Button1"), sg.Button("Button2"), sg.Push(), ], [ sg.Push(), sg.Button("Button1"), sg.Push(), sg.Button("Button2"), sg.Push(), ], ... ... ...
Ovšem při změně velikosti okna zůstávají velikosti samotných prvků GUI nezměněné, pouze se více roztáhnou či naopak stlačí pružiny mezi nimi. Toto chování však není jediné možné, protože jednotlivé prvky se mohou zvětšovat či zmenšovat současně s okny (nejedná se sice o zcela obvyklé či očekáváné chování, ovšem v některých situacích se může hodit).
Nejprve ovšem musíme změnu velikosti oken povolit. To je ve skutečnosti velmi snadné, protože při vytváření okna můžeme konstruktoru PySimpleGUI.Window() předat nepovinný parametr resizable nastavený na logickou hodnotu True. V takovém případě bude možné měnit velikost okna, a to všemi dostupnými prostředky (záleží na okenním manažeru atd.):
window = sg.Window("Window #37", layout, size=(320, 260), resizable=True, finalize=True)
Dále musíme v seznamu obsahujícím všechny ovládací prvky explicitně uvést klíče těch prvků, u nichž budeme chtít modifikovat jejich chování. Příkladem může být „odesílací“ tlačítko vytvářené konstruktorem PySimpleGUI.Submit():
sg.Submit(key="foo")
Po vytvoření okna (viz výše uvedený konstruktor) změníme vlastnosti tlačítka s klíčem „foo“. Konkrétně se jedná o vlastnosti nazvané expand_x a expand_y, které určují, jestli se bude tlačítko zvětšovat ve směru x-ové osy a/nebo osy y-ové:
window["foo"].expand(expand_x=True, expand_y=True)
Výsledný skript bude vypadat takto:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [ sg.Button("Button1"), sg.Button("Button2"), ], [ sg.Push(), sg.Button("Button1"), sg.Button("Button2"), ], [ sg.Button("Button1"), sg.Push(), sg.Button("Button2"), ], [ sg.Button("Button1"), sg.Button("Button2"), sg.Push(), ], [ sg.Push(), sg.Button("Button1"), sg.Button("Button2"), sg.Push(), ], [ sg.Push(), sg.Button("Button1"), sg.Push(), sg.Button("Button2"), sg.Push(), ], [sg.Submit(key="foo")], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #37", layout, size=(320, 260), resizable=True, finalize=True) window["foo"].expand(expand_x=True, expand_y=True) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
Vyzkoušejme si chování tohoto skriptu:
Obrázek 2: Okno ve své výchozí velikosti 320×260 pixelů.
Obrázek 3: Po zvětšení okna se příslušně zvětší i odesílací tlačítko.
Obrázek 4: Po zmenšení okna se příslušně zmenší i odesílací tlačítko.
3. Reakce všech prvků na změnu velikosti okna
Samozřejmě nám nic nebrání v tom, abychom vytvořili okno či dialog, jehož všechny prvky budou reagovat na změnu velikosti okna/dialogu. V našem konkrétním příkladu se tlačítkům přiřadí celočíselný klíč (může se jednat o jakýkoli hashovatelný objekt), což nám umožní snadný průchod všemi ovládacími prvky grafického uživatelského rozhraní:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [ sg.Button("Button1", key=1), sg.Button("Button2", key=2), ], [ sg.Push(), sg.Button("Button1", key=3), sg.Button("Button2", key=4), ], [ sg.Button("Button1", key=5), sg.Push(), sg.Button("Button2", key=6), ], [ sg.Button("Button1", key=7), sg.Button("Button2", key=8), sg.Push(), ], [ sg.Push(), sg.Button("Button1", key=9), sg.Button("Button2", key=10), sg.Push(), ], [ sg.Push(), sg.Button("Button1", key=11), sg.Push(), sg.Button("Button2", key=12), sg.Push(), ], [sg.Submit(key=13)], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #38", layout, size=(320, 260), resizable=True, finalize=True) for i in range(1, 14): window[i].expand(expand_x=True, expand_y=True) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
Opět si vyzkoušejme chování tohoto skriptu:
Obrázek 5: Okno ve své výchozí velikosti 320×260 pixelů.
Obrázek 6: Po zvětšení okna se příslušně zvětší i všechna tlačítka.
Obrázek 7: Po zmenšení okna se příslušně zmenší i všechna tlačítka.
4. Zbylé ovládací prvky nabízené knihovnou PySimpleGUI
Knihovna PySimpleGUI nabízí programátorům několik desítek ovládacích prvků (widgetů). Kolik jich přesně je záleží na tom, nad jakou knihovnou je PySimpleGUI použita. Pokud se jedná o TkInter, což je výchozí volba, jsou dostupné následující ovládací prvky:
Text | Column | ProgressBar |
Input | Frame | Table |
Combo | Tab | Tree |
OptionMenu | TabGroup | VerticalSeparator |
Multiline | Pane | HorizontalSeparator |
Output | Graph | StatusBar |
Radio | Slider | Sizegrip |
Checkbox | Listbox | Push |
Spin | Menu | VPush |
Button | MenubarCustom | Sizer |
Image | ButtonMenu | |
Canvas | Titlebar |
S některými ovládacími prvky jsme se seznámili v předchozích článcích. Mnohé z nich (Text, Combo, InputText, Checkbox, Radio a Submit, resp. Button) jsou použity v tomto příkladu:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Text("Name", size=(8, 0)), sg.InputText(key="name")], [sg.Text("Surname", size=(8, 0)), sg.InputText(key="surname")], [ sg.Text("Role", size=(8, 0)), sg.Combo( ["Administrator", "Maintainer", "Guest"], default_value="Guest", readonly=True, key="role", ), ], [ sg.Text("Register e-mail", size=(8, 0)), sg.Checkbox("", default=True, key="register e-mail"), ], [ sg.Text("Color theme", size=(8, 0)), sg.Radio("Light", "THEME", default=False, key="light_theme"), sg.Radio("Dark", "THEME", default=True, key="dark_theme"), ], [sg.Submit()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #9", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
Obrázek 8: Některé ovládací prvky, s nimiž jsme se již setkali – neměnitelný text, vstupní textová pole, výběrový box (combobox), zatrhávací box (checkbox), radiová tlačítka (radio buttons) a samozřejmě běžné tlačítko pro potvrzení voleb provedených v dialogu.
Ovšem i některé další prvky již byly popsány, především pak Column, StatusBar, Menu, Frame a Canvas. S dalšími ovládacími prvky se seznámíme v navazujících kapitolách.
5. Spinbox
Ovládací prvek nazvaný Spinbox nebo jen Spin (možno volně přeložit jako číselník) je směsicí widgetu Entry a Scrollbaru. Pokud tomuto ovládacímu prvku předáme při jeho konstrukci n-tici s řetězci, bude možné provádět výběr libovolného řetězce z této n-tice s využitím zobrazených tlačítek se šipkami nahoru a dolů. A pokud je nastaven fokus na tento prvek, lze použít i kurzorové šipky přímo na klávesnici:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Text("Spin"), sg.Spin(["Alpher", "Bethe", "Gamow",], s=(15, 2))], [sg.Submit()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #39", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
A takto bude spinbox vypadat v okně aplikace:
Obrázek 9: Spinbox zobrazený v okně aplikace.
Po potvrzení dialogu se vrátí aktuálně vybraná hodnota spinboxu:
Event: Submit Values: {0: 'Alpher'}
To znamená, že je praktičtější si spinbox pojmenovat:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Text("Spin"), sg.Spin(["Alpher", "Bethe", "Gamow",], s=(15, 2), key="Author")], [sg.Submit()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #39", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
S výsledkem po stisku tlačítka Submit:
Event: Submit Values: {'Author': 'Bethe'}
6. Slider (posuvník)
Slider je speciální forma posuvníku (scrollbar) určeného pro specifikaci numerické hodnoty s tím, že je hodnota vizuálně zobrazena (umístěna) na zvolené škále od-do. Předností posuvníku je fakt, že je u něj možné zobrazit značky představující zvolenou škálu hodnot a tak zrychlit změnu hodnot prováděnou uživatelem. Posuvník se vytváří konstruktorem PySimpleGUI.Slider, přičemž se v prvním parametru předá n-tice s minimální a maximální hodnotou. Zadat lze i orientaci a popř. i další parametry:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Text("Slider"), sg.Slider((0, 10), orientation='h', s=(20, 15), key="slider")], [sg.Submit()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #41", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
A takto by se měl posuvník (slider) zobrazit v okně:
Obrázek 10: Slider zobrazený v okně aplikace.
Po potvrzení dialogu či uzavření okna je hodnotou objektu typu posuvník pochopitelně vybraná hodnota:
Event: Submit Values: {'slider': 2.0}
7. Další způsoby zobrazení slideru
Velmi užitečné je zobrazení hodnot na slideru. Pro tento účel je nutné použít nepovinný parametr nazvaný tick_interval, kterému se předá krok mezi jednotlivými hodnotami. Takový slider je ovšem pochopitelně vyšší, popř. širší, v závislosti na jeho orientaci:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Text("Slider"), sg.Slider((0, 10), orientation='h', tick_interval=1, s=(20, 15), key="slider")], [sg.Submit()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #42", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
Výsledkem bude takto vypadající dialog:
Obrázek 11: Slider i s hodnotami na pravítku.
Taktéž je možné změnit orientaci slideru (i když vertikální orientace je málo používaná):
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Text("Slider"), sg.Slider((0, 10), orientation='v', tick_interval=1, s=(20, 15), key="slider")], [sg.Submit()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #43", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
Výsledek:
Obrázek 12: Vertikálně orientovaný slider.
A pochopitelně je možné dopředu zvolit hodnotu, která bude vybrána:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Text("Slider"), sg.Slider((0, 10), orientation='h', tick_interval=1, s=(20, 15), default_value=7, key="slider")], [sg.Submit()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #44", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
8. ProgressBar: indikátor probíhající operace
Dalším často využívaným ovládacím prvkem je indikátor probíhající operace („teploměr“), který se nazývá ProgressBar. Při konstrukci tohoto prvku je nutné zvolit minimálně jeho maximální hodnotu a volitelně pak rozměry, orientaci (horizontální, vertikální), jméno atd. Změna teploměru, tedy typicky posun o další políčko směrem ke konci, se provádí metodou UpdateBar.
V demonstračním příkladu je nutno desetkrát kliknout na tlačítko Next step, aby „teploměr“ došel ke svému konci. Po jedenáctém výběru tohoto tlačítka je skript ukončen:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.ProgressBar(10, orientation="h", size=(20, 20), key="progress")], [sg.Button("Next step")], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #45", layout) progress_bar = window["progress"] # obsluha smyčky událostí (event loop) for i in range(11): # přečtení události event, values = window.read() print("Event: ", event, " Values: ", values) progress_bar.UpdateBar(i+1) # reakce na událost "uzavření okna" if event == sg.WIN_CLOSED: break # po přečtení události okno zavřeme window.close()
Obrázek 13: Dialog ihned po svém zobrazení.
Obrázek 14: Situace po několika kliknutích na tlačítko Next step.
Obrázek 15: Po dalším výběru tlačítka Next step se dialog uzavře a skript se ukončí.
9. Propojení posuvníku s progress barem
V dalším demonstračním příkladu si můžeme otestovat propojení posuvníku s „teploměrem“. Po každé změně pozice značky na posuvníku se příslušným způsobem změní i indikátor míry dokončení úkolu na „teploměru“. Přečtení hodnoty posuvníku a změna progress baru je pochopitelně provedena ve smyčce pro obsluhu událostí (viz zvýrazněnou dvojici řádků):
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Slider((0, 10), orientation='h', tick_interval=1, s=(25, 15), default_value=0, key="slider")], [sg.ProgressBar(10, orientation="h", size=(20, 20), key="progress")], [sg.Button("Update")], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #46", layout) progress_bar = window["progress"] slider = window["slider"] # obsluha smyčky událostí (event loop) while True: # přečtení události event, values = window.read() print("Event: ", event, " Values: ", values) # reakce na událost "uzavření okna" if event == sg.WIN_CLOSED: break # změna hodnoty na progress baru value = values["slider"] progress_bar.UpdateBar(value) # po přečtení události okno zavřeme window.close()
Výsledný dialog se bude chovat následovně:
Obrázek 16: Progress bar ukazuje stejnou hodnotu, jaká je vybrána posuvníkem.
Obrázek 17: Progress bar ukazuje stejnou hodnotu, jaká je vybrána posuvníkem.
Obrázek 18: Progress bar ukazuje stejnou hodnotu, jaká je vybrána posuvníkem.
10. Výběrové menu (OptionMenu)
Poměrně netypickým ovládacím prvkem je výběrové menu. To lze považovat za obdobu list boxu (viz navazující kapitolu), ovšem výběrové menu je zobrazeno odlišným způsobem – jedná se vlastně o kombinaci tlačítka s kontextovým menu. Tento neobvyklý ovládací prvek je navíc dostupný jen v případě, pokud se pro správu GUI používá knihovna TkInter.
Podívejme se tedy jen v krátkosti na to, jak se tento prvek zařazuje do dialogů:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Text("Option menu"), sg.OptionMenu(["Alpher", "Bethe", "Gamow",], s=(15, 2))], [sg.Submit()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #47", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
Obrázek 19: Po výběru prvku OptionMenu se vlastně zobrazí kontextové menu. To je považováno za samostatné okno, proto je tento screenshot „uříznutý“.
11. Výběr prvků ze seznamu (Listbox)
V případě, že je zapotřebí dát uživatelům na výběr z několika pevně zadaných hodnot, je pravděpodobně nejjednodušší a nejpřehlednější použít radiová tlačítka, s nimiž jsme se již seznámili v předchozích článcích. Pokud je hodnot mnoho nebo se mohou měnit, je většinou vhodnější použít výběr ze seznamu (listbox), popř. výše zmíněné výběrové menu (to za předpokladu, že chcete využít dnes již méně známé ovládací prvky). Samotný seznam (listbox) je používanější a známější; proto ho pravděpodobně budete preferovat.
Při konstrukci listboxu je pouze nutné konstruktoru předat sekvenci s prvky, které se mají zobrazit. Dále lze zvolit velikost seznamu a popř. povolit či naopak zakázat zobrazení scrollbaru ve chvíli, kdy nelze na dané ploše zobrazit všechny jeho prvky. Ukažme si ten nejjednodušší způsob použití:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Text("Option menu"), sg.Listbox(["Alpher", "Bethe", "Gamow",], s=(20, 3))], [sg.Submit()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #48", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
Výsledný dialog může vypadat následovně:
Obrázek 20: Dialog s výběrovým boxem se seznamem hodnot.
12. Statický rastrový obrázek
Další element grafického uživatelského rozhraní, který si popíšeme, je velmi jednoduchý. Jedná se o statický rastrový obrázek, který lze načíst z externího souboru a umístit ho do zvoleného místa na dialogu (například ve formě ikony atd.). Podporována je většina formátů rastrových obrázků vhodných pro ikony, tedy primárně GIF a PNG. Přidání obrázku do dialogu je triviální, jak je to ostatně patrné i při pohledu na následující zdrojový kód:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Image("globe.png")], [sg.Push(), sg.Submit(), sg.Push()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #49", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
Obrázek zobrazený v dialogovém okně bude vypadat následovně:
Obrázek 21: Ikona zobrazená v dialogovém okně.
13. Data obrázku uložená přímo ve zdrojovém kódu
Samotný obrázek se většinou načítá z externích souborů, ovšem v případě potřeby lze malé ikony atd. mít uložené přímo ve zdrojovém kódu Pythonu. V takovém případě se původní obrázek převede (například nástrojem base64) do sekvence znaků podle kódování Base64 a výsledek se uloží jako hodnota typu bytes. To je v Pythonu snadné, protože stačí tuto hodnotu reprezentovat literálem zapisovaným takto:
b"hodnoty bajtů"
Díky použití Base64 se v takovém literálu nenachází žádné speciální (řídicí) znaky.
Předchozí demonstrační příklad tedy můžeme upravit do poněkud rozsáhlé podoby (schválně ho uvádím celý, aby bylo patrné, že zdrojové kódy mohou být obrovské):
import PySimpleGUI as sg image=b""" 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 """ # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Image(image)], [sg.Push(), sg.Submit(), sg.Push()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #52", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
14. Animace
Do dialogů lze vložit i animované GIFy. Stále se bude jednat o widget typu Image, navíc způsob přepínání snímků v animovaném GIFu je možné řídit programově, konkrétně zavoláním metody UpdateAnimation. Příkladem může být skript, v němž se snímek změní po každém stisku tlačítka Next frame. Samotný animovaný GIF přitom obsahuje pouze dvojici snímků a vypadá následovně:
Obrázek 22: Animovaný GIF se dvěma snímky vložený do dialogu.
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Push(), sg.Image("blink.gif", key="gif"), sg.Push()], [sg.Push(), sg.Button("Next frame"), sg.Push()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #50", layout) gif = window["gif"] # obsluha smyčky událostí (event loop) while True: # přečtení události event, values = window.read() print("Event: ", event, " Values: ", values) if event == "Next frame": gif.UpdateAnimation("blink.gif") # reakce na událost "uzavření okna" if event == sg.WIN_CLOSED: break # po přečtení události okno zavřeme window.close()
A takto vypadá výsledný dialog:
Obrázek 23: Dialog s animovaným GIFem.
Obrázek 24: Dialog s animovaným GIFem po přepnutí snímku.
15. Složitější animace – vizualizace probíhající operace
V některých situacích je nutné vizuálně ukázat, že probíhá nějaká operace, ovšem ne vždy je možné použít již popsaný ProgressBar, protože nemusí být dopředu jasné, kolik kroků či jaký čas je nutný pro dokončení operace. V takovém případě si můžeme vypomoci animací, která vlastně neneznačuje, kdy se nějaká činnost dokončí. Můžeme použít například tento animovaý GIF:
Obrázek 25: Animovaný GIF naznačující probíhající operaci.
V dialogu navíc čekáme na přijetí události jen po specifikovanou dobu. V případě, že událost nebude do tohoto okamžiku přijata, pouze se přepne další snímek animace a opět se počká na další událost. Tím je zajištěno (i bez nutnosti použití čítače), že se budou jednotlivé snímky animace automaticky přepínat:
import PySimpleGUI as sg # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Push(), sg.Image("progress.gif", key="gif"), sg.Push()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #51", layout) gif = window["gif"] # obsluha smyčky událostí (event loop) while True: # přečtení události event, values = window.read(timeout=50) print("Event: ", event, " Values: ", values) gif.UpdateAnimation("progress.gif") # reakce na událost "uzavření okna" if event == sg.WIN_CLOSED: break # po přečtení události okno zavřeme window.close()
Výsledek bude vypadat takto:
Obrázek 26: Dialog s animovaným obrázkem.
Obrázek 27: Dialog s animovaným obrázkem.
16. Tabulka
Předposledním ovládacím prvkem grafického uživatelského rozhraní, se kterým se v dnešním článku setkáme, je tabulka. Data tabulky jsou reprezentována formou vnořených n-tic, seznamů, atd. První řádek dat ve výchozím nastavení označuje nadpisy sloupců, to je však možné v případě potřeby změnit. Podívejme se nyní na způsob zobrazení jednoduché tabulky:
import PySimpleGUI as sg data = [ ["Jan 2024", "Jan 2023", "Language", "Ratings", "Change"], [1, 1, "Python", "13.97%", "-2.39%"], [2, 2, "C", "11.44%", "-4.81%"], [3, 3, "C++", "9.96%", "-2.95%"], [4, 4, "Java", "7.87%", "-4.34%"], [5, 5, "C#", "7.16%", "+1.43%"], [6, 7, "JavaScript", "2.77%", "-0.11%"], ] # ovládací prvky, které se mají zobrazit v okně layout = [ [sg.Table(data)], [sg.Push(), sg.Submit(), sg.Push()], ] # vytvoření okna s ovládacími prvky window = sg.Window("Window #53", layout) # přečtení jediné události event, values = window.read() print("Event: ", event, " Values: ", values) # po přečtení události okno zavřeme window.close()
Výsledkem bude tento dialog:
Obrázek 28: Tabulka zobrazená v dialogu.
17. Strom
Dalším typem ovládacího prvku grafického uživatelského rozhraní jsou stromy tree. Jedná se o jediný prvek GUI, u kterého neexistuje jednoduše zapsatelný konstruktor. Namísto toho je nutné stromy konstruovat programově, od kořenového uzlu k listům stromu (root, leaf. Do stromu se uzly přidávají metodou:
data.Insert("klíč předka", "klíč", "jméno uzlu", [hodnoty])
Pro uzly navázané přímo na kořenový uzel se namísto klíče předka používá prázdný řetězec.
Příklad stromu se třemi uzly navázanými na kořenový uzel. Každý z těchto uzlů má tři potomky (které jsou přímo koncovými uzly):
data = sg.TreeData() # předek klíč jméno hodnoty data.Insert("", "A", "A", []) data.Insert("", "B", "B", []) data.Insert("", "C", "D", []) data.Insert("A", "A1", "A1", []) data.Insert("A", "A2", "A2", []) data.Insert("A", "A3", "A3", []) data.Insert("B", "B1", "B1", []) data.Insert("B", "B2", "B2", []) data.Insert("B", "B3", "B3", []) data.Insert("C", "C1", "C1", []) data.Insert("C", "C2", "C2", []) data.Insert("C", "C3", "C3", [])
18. Obsah poslední části seriálu o PySimpleGUI
V navazující a současně i poslední části seriálu o knihovně PySimpleGUI nejprve dokončíme popis tabulek a stromů, což jsou nejsložitější elementy grafického uživatelského rozhraní. A posléze si popíšeme i práci s taby, které využijeme ve složitějších typech dialogů.
19. Repositář s demonstračními příklady
Všechny Pythonovské skripty, které jsme si ukázali v úvodní trojici článků o PySimpleGUI a pochopitelně i v dnešním článku ukázali, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady. Pro jejich spuštění je nutné mít nainstalován balíček PySimpleGUI, jenž závisí na standardním balíčku TkInter:
20. Odkazy na Internetu
- PySimpleGUI
https://www.pysimplegui.org/en/latest/ - Kivy na GitHubu
https://github.com/kivy/kivy - DearPyGui na GitHubu
https://github.com/hoffstadt/DearPyGui - PySimpleGUI Tutorial
https://www.tutorialspoint.com/pysimplegui/index.htm - PySimpleGUI – Canvas Element
https://www.tutorialspoint.com/pysimplegui/pysimplegui_canvas_element.htm - Dokumentace ke knihovně PySimpleGUI
https://www.pysimplegui.org/en/latest/ - Dokumentace ke knihovně DearPyGui
https://dearpygui.readthedocs.io/en/latest/index.html# - 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/ - Stránky projektu wxPython
https://wxpython.org/ - wxPython Project Phoenix (na GitHubu)
https://github.com/wxWidgets/Phoenix/blob/wxPython-4.0.3/README.rst - wxPython API Documentation
https://docs.wxpython.org/index.html - wxWidgets
https://wxwidgets.org/ - wxPython 4.0.3 na PyPi
https://pypi.org/project/wxPython/4.0.3/ - wxGlade – a GUI builder for wxWidgets
http://wxglade.sourceforge.net/ - Repositář projektu wxGlade
https://github.com/wxGlade/wxGlade/ - wxGlade’s documentation
http://wxglade.sourceforge.net/docs/index.html - Graphical User Interfaces (GUI)
https://pythonspot.com/gui/ - wxPyWiki
https://wiki.wxpython.org/FrontPage - Getting started with wxPython
https://wiki.wxpython.org/Getting%20Started#A_First_Application:_.22Hello.2C_World.22 - wxPython GUI tutorial
https://pythonspot.com/wxpython-gui-tutorial/ - wxPython tutorial
http://zetcode.com/wxpython/ - Build wxPython On Raspberry Pi
https://wiki.wxpython.org/BuildWxPythonOnRaspberryPi - wxPython History
https://wxpython.org/pages/history/index.html - Installing wxPython 4.0 (Project Phoenix) on Fedora 27
https://blog.wizardsoftheweb.pro/installing-wxpython-on-fedora/ - Category:Software that uses wxWidgets
https://en.wikipedia.org/wiki/Category:Software_that_uses_wxWidgets - Hra Breakout napísaná v Tkinteri
https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/ - 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/ - Hand Coded GUI Versus Qt Designer GUI
https://stackoverflow.com/questions/387092/hand-coded-gui-versus-qt-designer-gui - Qt Creator Manual
http://doc.qt.io/qtcreator/ - Qt Designer Manual
http://doc.qt.io/qt-5/qtdesigner-manual.html - Qt Creator (Wikipedia)
https://en.wikipedia.org/wiki/Qt_Creator - QIODevice
https://pyside.github.io/docs/pyside/PySide/QtCore/QIODevice.html#PySide.QtCore.QIODevice - QFile
https://pyside.github.io/docs/pyside/PySide/QtCore/QFile.html#PySide.QtCore.QFile - QUiLoader
https://pyside.github.io/docs/pyside/PySide/QtUiTools/QUiLoader.html#PySide.QtUiTools.PySide.QtUiTools.QUiLoader.load - QSvgWidget
https://pyside.github.io/docs/pyside/PySide/QtSvg/QSvgWidget.html - QByteArray
https://pyside.github.io/docs/pyside/PySide/QtCore/QByteArray.html - Differences Between PySide and PyQt
https://wiki.qt.io/Differences_Between_PySide_and_PyQt - PySide 1.2.1 tutorials
https://pyside.github.io/docs/pyside/tutorials/index.html - PySide tutorial
http://zetcode.com/gui/pysidetutorial/ - Drawing in PySide
http://zetcode.com/gui/pysidetutorial/drawing/ - Qt Core
https://pyside.github.io/docs/pyside/PySide/QtCore/Qt.html - Signals & Slots
http://doc.qt.io/qt-4.8/signalsandslots.html - Signals and Slots in PySide
http://wiki.qt.io/Signals_and_Slots_in_PySide - Intro to PySide/PyQt: Basic Widgets and Hello, World!
http://www.pythoncentral.io/intro-to-pysidepyqt-basic-widgets-and-hello-world/ - Leo editor
http://leoeditor.com/ - IPython Qt Console aneb vylepšený pseudoterminál
https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-ipython-a-ipython-notebook/#k06 - 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/ - appJar widgets
http://appjar.info/pythonWidgets/ - 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