Obsah
1. Tvorba grafického uživatelského rozhraní v Pythonu: widgety v knihovně appJar
2. Nastavení volného místa mezi widgety (padding)
3. Ukázka konfigurace mezer mezi widgety v oknu
4. Volné místo a umístění widgetů do mřížky
5. Zaškrtávací pole (check box)
6. Konfigurace zaškrtávacích polí
8. Změna způsobu zobrazení přepínačů
9. Implicitně vybraný přepínač
11. Seznam s možností výběru více prvků
13. Widgety, které pracují se slovníkem stavů
14. Rozbalovací seznam s výběrem většího množství prvků
15. Ukázka použití rozbalovacího seznamu s možností výběru více prvků
16. Widget se skupinou zaškrtávacích prvků
17. Ukázka použití skupiny zaškrtávacích prvků
18. Repositář s demonstračními příklady
1. Tvorba grafického uživatelského rozhraní v Pythonu: widgety v knihovně appJar
Nejdříve se podívejme na způsob implicitního vkládání widgetů do oken či dialogů. Jednoduchý projekt, který je zobrazen pod tímto odstavcem (a o kterém jsme se již bavili minule), po svém spuštění zobrazí okno s textovým návěštím, pod nímž je umístěna dvojice tlačítek. Všechny tři zmíněné widgety jsou přitom „namačkány“ na sobě; mezera mezi nimi je jen minimální:
#!/usr/bin/env python from appJar import gui app = gui() app.setSticky("we") app.addLabel("title", "Hello world!") app.addButton("Ok", None) app.addButton("Quit", None) app.go()

Obrázek 1: Screenshot předchozího příkladu.
Totéž platí i ve chvíli, kdy budeme widgety umisťovat vedle sebe do pomyslné mřížky – widgety stále budou implicitně „namačkány“ vedle sebe:
#!/usr/bin/env python from appJar import gui app = gui() app.setSticky("we") app.addLabel("title", "Hello world!", colspan=2) app.addButton("Ok", None, 1, 0) app.addButton("Quit", None, 1, 1) app.go()

Obrázek 2: Screenshot předchozího demonstračního příkladu.
2. Nastavení volného místa mezi widgety (padding)
Pro nastavení horizontálních a vertikálních mezer mezi jednotlivými widgety se používají metody setPadX(), setPadY() a setPadding(). Kromě toho je možné nastavit i interní horizontální a vertikální mezery. Ty se umisťují mezi okraj widgetu (ten je většinou viditelný, například u tlačítka) a vlastním textem a/nebo ovládací částí widgetu. Většinou se velikost interních mezer ponechává na výchozí hodnotě, ale samozřejmě vám nic nebrání laborovat i s těmito parametry:
Metoda | Význam |
---|---|
setPadX() | mezery mezi buňkami umístěnými vedle sebe |
setPadY() | mezery mezi buňkami umístěnými nad sebou |
setPadding() | kombinace předchozích dvou metod |
setIPadX() | interní horizontální mezera mezi okrajem widgetu a jeho textem/ovládací částí |
setIPadY() | interní vertikální mezera mezi okrajem widgetu a jeho textem/ovládací částí |
setIPadding() | kombinace předchozích dvou metody |
setInPadX() | totožné se setIPadX() |
setInPadY() | totožné se setIPadY() |
setInPadding() | totožné se setIPadding() |

Obrázek 3: Změna interní vertikální mezery mezi okrajem widgetu a jeho textem.
Poznámka: ve skutečnosti je možné volat metodu setPadding() dvěma způsoby – předáním dvojice hodnot představujících mezery či předání pole či n-tice s dvojicí prvků, které taktéž představují mezery mezi widg poety.
První způsob:
app.setPadding(x_padding, y_padding)
Alernativní způsob:
app.setPadding([x_padding, y_padding]) app.setPadding((x_padding, y_padding))
3. Ukázka konfigurace mezer mezi widgety v oknu
Podívejme se nyní na použití metody setPadding() v praxi. Mezi widgety budeme vkládat mezeru o šířce/výšce deseti pixelů:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: app.infoBox("Ok, Ok", "Ok button pressed") app = gui() app.setSticky("news") app.setPadding(10, 10) app.addLabel("title", "Hello world!") app.addButton("Ok", onButtonPress) app.addButton("Quit", onButtonPress) app.go()

Obrázek 4: Screenshot dnešního prvního demonstračního příkladu.
4. Volné místo a umístění widgetů do mřížky
Widgety jsou vždy umisťované do pomyslné mřížky, takže i při explicitním nastavení buňky (buněk), do nichž se widgety mají vložit, se bude velikost těchto buněk zvětšovat o nastavené mezery. Můžeme se o tom snadno přesvědčit po spuštění následujícího příkladu:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: app.infoBox("Ok, Ok", "Ok button pressed") app = gui() app.setSticky("news") app.setPadding(10, 10) app.addLabel("title", "Hello world!") app.addButton("Ok", onButtonPress, 1, 1) app.addButton("Quit", onButtonPress, 2, 1) app.go()
Poznámka: v současné verzi knihovny appJar není možné specifikovat různé velikosti mezer v různých směrech (na rozdíl od CCS, kde to možné je).

Obrázek 5: Screenshot dnešního druhého demonstračního příkladu.
5. Zaškrtávací pole (check box)
Dalším typem grafického ovládacího prvku (widgetu) je takzvaný checkbox neboli zaškrtávací pole. Od obyčejného tlačítka se tento widget liší především tím, že je vizuálně patrný jeho stav – nastaveno/nenastaveno. Tento typ tlačítek je zobrazován různým způsobem, typicky se však jedná o čtvereček, který je buď zatržený (znak ✓ či ×) nebo prázdný; v některých GUI prostředích se však stav tlačítka reprezentuje pouze jeho barvou. V nejjednodušším případě se přepínací tlačítko vytvoří následovně:
app.addCheckBox("text checkboxu")
Stav zaškrtávacího tlačítka se zjišťuje metodou getCheckBox(), které se předá text checkboxu. Vidíme tedy, že text se současně používá jako identifikátor widgetu. Dejte si tedy pozor na to, aby se v jednom okně či dialogu nevyskytl stejně pojmenovaný checkbox:
app.getCheckBox("text checkboxu")
Návratovou hodnotou této funkce je pravdivostní hodnota True či False.
V dalším demonstračním příkladu se po stlačení tlačítka Ok zobrazí stav obou zaškrtávacích polí. Povšimněte si, že můžeme bez problémů používat znaky Unicode; navíc dokáže infoBox zobrazit víceřádkový text.

Obrázek 6: Použití Unicode znaků ve víceřádkové zprávě.
Následuje zdrojový kód příkladu:
#!/usr/bin/env python from appJar import gui CHECK_SYMBOL = "\u2713" MULTIPLY_SYMBOL = "\u2715" def state(choice): return CHECK_SYMBOL if choice else MULTIPLY_SYMBOL def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "1st choice: {c1}\n2nd choice: {c2}".format( c1=state(app.getCheckBox("1st choice")), c2=state(app.getCheckBox("2nd choice"))) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 10) app.addCheckBox("1st choice", 1, 1) app.addCheckBox("2nd choice", 2, 1) app.addButton("Show choices", onButtonPress, 3, 1) app.addButton("Quit", onButtonPress, 3, 2) app.go()

Obrázek 7: Screenshot dnešního třetího demonstračního příkladu.
6. Konfigurace zaškrtávacích polí
Metodou setCheckBox() je možné nastavit ta zaškrtávací pole, která mají být implicitně vybrána:
app.setCheckBox("1st choice", ticked=True)
Navíc je možné explicitně zavolat funkci navázanou na příslušný widget (tímto tématem jsme se však ještě nezabývali):
app.setCheckBox("1st choice", callFunction=True)
V následujícím demonstračním příkladu je po jeho spuštění implicitně vybráno první zaškrtávací pole:
#!/usr/bin/env python from appJar import gui CHECK_SYMBOL = "\u2713" MULTIPLY_SYMBOL = "\u2715" def state(choice): return CHECK_SYMBOL if choice else MULTIPLY_SYMBOL def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "1st choice: {c1}\n2nd choice: {c2}".format( c1=state(app.getCheckBox("1st choice")), c2=state(app.getCheckBox("2nd choice"))) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 10) app.addCheckBox("1st choice", 1, 1) app.addCheckBox("2nd choice", 2, 1) app.setCheckBox("1st choice", ticked=True) app.addButton("Show choices", onButtonPress, 3, 1) app.addButton("Quit", onButtonPress, 3, 2) app.go()

Obrázek 8: Screenshot dnešního čtvrtého demonstračního příkladu.
7. Přepínače (radio buttons)
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 CheckBox 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.
Skupina přepínačů se vytvoří jednoduše – postačuje jim nastavit shodný název, ale odlišný text (poslední dva parametry určují umístění widgetů do mřížky):
app.addRadioButton("languages", "Assembler", 1, 1) app.addRadioButton("languages", "C", 2, 1) app.addRadioButton("languages", "C++", 3, 1) app.addRadioButton("languages", "Perl", 4, 1) app.addRadioButton("languages", "Python", 5, 1)
Aktuálně vybraný přepínač se zjistí snadno metodou getRadioButton(). V našem konkrétním případě tedy:
volba = app.getRadioButton("languages")
Návratovou hodnotou je text zobrazený vedle vybraného přepínače.
Podívejme se na jednoduchý příklad, v němž je použita jedna skupina pěti přepínačů:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "Your choice: {c}".format(c=app.getRadioButton("languages")) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 2) app.addRadioButton("languages", "Assembler", 1, 1) app.addRadioButton("languages", "C", 2, 1) app.addRadioButton("languages", "C++", 3, 1) app.addRadioButton("languages", "Perl", 4, 1) app.addRadioButton("languages", "Python", 5, 1) app.addButton("Show choice", onButtonPress, 6, 1) app.addButton("Quit", onButtonPress, 6, 2) app.go()

Obrázek 9: Screenshot dnešního pátého demonstračního příkladu.

Obrázek 10: Zobrazení uživatelem vybraného přepínače.
8. Změna způsobu zobrazení přepínačů
Pokud metodou setRadioButton() nastavíme konfigurační parametr tick na False, dojde ke změne zobrazení přepínačů:
app.setRadioTick("languages", tick=False)
Přepínače se nyní podobají tlačítkům, z nichž vždy jen jedno je implicitně vybráno:

Obrázek 11: Změna způsobu zobrazení přepínačů metodou setRadioTick().
Příklad z předchozí kapitoly upravíme snadno:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "Your choice: {c}".format(c=app.getRadioButton("languages")) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 2) app.addRadioButton("languages", "Assembler", 1, 1) app.addRadioButton("languages", "C", 2, 1) app.addRadioButton("languages", "C++", 3, 1) app.addRadioButton("languages", "Perl", 4, 1) app.addRadioButton("languages", "Python", 5, 1) app.setRadioTick("languages", tick=False) app.addButton("Show choice", onButtonPress, 6, 1) app.addButton("Quit", onButtonPress, 6, 2) app.go()

Obrázek 12: Zobrazení vybraného přepínače.
9. Implicitně vybraný přepínač
Podobně jako u zaškrtávacích tlačítek je možné i u přepínačů vybrat ten přepínač, který je implicitně vybraný. V našem konkrétním případě můžeme s využitím metody setRadioButton() implicitně vybrat hodnotu/přepínač Python. Volání této metody je velmi snadné:
app.setRadioButton("languages", "Python")

Obrázek 13: Ihned po zobrazení hlavního okna je vybrán jazyk Python..
Upravený zdrojový kód demonstračního příkladu vypadá následovně:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "Your choice: {c}".format(c=app.getRadioButton("languages")) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 2) app.addRadioButton("languages", "Assembler", 1, 1) app.addRadioButton("languages", "C", 2, 1) app.addRadioButton("languages", "C++", 3, 1) app.addRadioButton("languages", "Perl", 4, 1) app.addRadioButton("languages", "Python", 5, 1) app.setRadioButton("languages", "Python") app.addButton("Show choice", onButtonPress, 6, 1) app.addButton("Quit", onButtonPress, 6, 2) app.go()

Obrázek 14: Zobrazení implicitně vybraného přepínače.
10. Seznam (listbox)
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 ovládací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). Vše zajišťuje jediná metoda, které se předá jméno ovládacího prvku a seznam hodnot:
app.addListBox("listbox", ["Assembler", "C", "C++", "Perl", "Python"])

Obrázek 15: Seznam neboli listbox s pěti prvky.
Pro získání vybrané hodnoty použijeme:
value = app.getListItems("listbox"))
Tato metoda se skutečně jmenuje getListItems() a nikoli jen getListItem(). Navíc tato metoda vrací seznam hodnot, v tomto konkrétním případě jednoprvkový seznam. Důvod tohoto chování si vysvětlíme později.
Opět se podívejme na příklad, v němž je listbox použit:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "Your choice: {c}".format(c=app.getListItems("listbox")) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 2) app.addListBox("listbox", ["Assembler", "C", "C++", "Perl", "Python"]) app.addButton("Show choice", onButtonPress) app.addButton("Quit", onButtonPress) app.go()

Obrázek 16: Zobrazení prvku vybraného ze seznamu.
11. Seznam s možností výběru více prvků
Pokud ihned po vytvoření seznamu zavoláme následující metodu:
app.setListBoxMulti("listbox", True)
změní se chování seznamu, neboť z něho nyní bude možné vybrat větší množství prvků. Ostatně se můžeme podívat na další screenshot:

Obrázek 17: Seznam neboli listbox s pěti prvky. Listbox je nakonfigurován tak, by umožnil výběr většího množství prvků.
V případě, že zavoláme nám již známou metodu getListItems():
value = app.getListItems("listbox"))
vrátí tato metoda seznam všech vybraných prvků. Právě toto je hlavní důvod, proč je v názvu metody použit plurál:

Obrázek 18: Zobrazení všech tří prvků vybraných ze seznamu.
Opět si ukažme, jak bude vypadat upravený demonstrační příklad:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "Your choice: {c}".format(c=app.getListItems("listbox")) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 2) app.addListBox("listbox", ["Assembler", "C", "C++", "Perl", "Python"]) app.setListBoxMulti("listbox", True) app.addButton("Show choice", onButtonPress) app.addButton("Quit", onButtonPress) app.go()
12. Rozbalovací seznam
Kromě klasického výběrového seznamu obsahuje knihovna appJar i velmi podobný ovládací prvek nazvaný rozbalovací seznam. Ten se vytváří metodou addOptionBox(), která má stejné parametry jako metoda addListBox():
app.addOptionBox("optionbox", ["Assembler", "C", "C++", "Perl", "Python"])
Vytvořený rozbalovací seznam vypadá z pohledu moderního desktopu trošku zvláštně:

Obrázek 19: Rozbalovací seznam (OptionBox).
Ukažme si příklad, v němž je rozbalovací seznam použit. Můžeme vidět, že se tento příklad prakticky neliší od předchozího příkladu, až na to, že používáme odlišný widget:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "Your choice: {c}".format(c=app.getOptionBox("optionbox")) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 2) app.addOptionBox("optionbox", ["Assembler", "C", "C++", "Perl", "Python"]) app.addButton("Show choice", onButtonPress) app.addButton("Quit", onButtonPress) app.go()
13. Widgety, které pracují se slovníkem stavů
Widgety, které jsme si prozatím popsali, pracovaly buď s jedinou hodnotou (vstupní textové pole, zaškrtávací pole) nebo se seznamem hodnot (ListBox, OptionBox). V knihovně appJar však najdeme i dva widgety, které pro reprezentaci svého stavu používají slovník (dictionary, dict). Jedná se o rozbalovací seznam umožňující výběr většího množství prvků a dále o widget nazvaný poněkud neobvykle Properties. První zmíněný widget vrací vybrané prvky ve formě slovníku, v němž je každý prvek klíčem a hodnotou je stav jeho výběru (True/False). Naproti tomu widget Properties používá slovník již při specifikaci těch prvků, které se mají v tomto widgetu zobrazit. Podrobnosti si řekneme v navazujících kapitolách.
14. Rozbalovací seznam s výběrem většího množství prvků
Rozbalovací seznam, v němž je možné vybrat větší množství prvků, se vytváří metodou addTickOptionBox(). Základní použití této metody vypadá následovně:
app.addTickOptionBox("optionbox", ["Assembler", "C", "C++", "Perl", "Python"])
Povšimněte si, že běžný rozbalovací seznam se vytváří prakticky stejně:
app.addOptionBox("optionbox", ["Assembler", "C", "C++", "Perl", "Python"])

Obrázek 20: Rozbalovací seznam s možností výběru více prvků (TickOptionBox) se podobá běžnému rozbalovacímu seznamu (OptionBox), ovšem u jednotlivých prvků je zobrazen příznak výběru (✓).
Jediným rozdílem je, že v prvním případě je možné prvky zaškrtávat.
Pokud zavoláme metodu:
app.getOptionBox("optionbox")
vrátí se slovník obsahující u jména každého prvku hodnotu True či False podle toho, zda byl prvek vybrán či nikoli:

Obrázek 21: Výsledek volání metody app.getOptionBox(„optionbox“).
15. Ukázka použití rozbalovacího seznamu s možností výběru více prvků
Následuje příklad použití rozbalovacího seznamu s možností výběru více prvků:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "Your choice: {c}".format(c=app.getOptionBox("optionbox")) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 2) app.addTickOptionBox("optionbox", ["Assembler", "C", "C++", "Perl", "Python"]) app.addButton("Show choice", onButtonPress) app.addButton("Quit", onButtonPress) app.go()
16. Widget se skupinou zaškrtávacích prvků
Poslední widget, který si dnes popíšeme, se jmenuje Properties. Ve skutečnosti se jedná o skupinu zaškrtávacích tlačítek, jenž je ovšem vytvořena jediným voláním metody addProperties():
app.addProperties("properties", {"Assembler": True, "C": True, "C++": False, "Perl": False, "Python": True})
Z ukázky je patrné, že se této metodě předává slovník s prvky (ty zde vystupují jako klíče slovníku), přičemž u každého prvku lze specifikovat, jestli má být implicitně vybrán či nikoli:

Obrázek 22: Widget pojmenovaný Properties.
Stav widgetu Properties se přečte metodou getProperties(), která taktéž vrací slovník:
values = app.getProperties("properties")

Obrázek 23: Stav widgetu Properties.
17. Ukázka použití skupiny zaškrtávacích prvků
Opět se podívejme na způsob použití widgetu Properties. Není to nic těžkého:
#!/usr/bin/env python from appJar import gui def onButtonPress(buttonName): if buttonName == "Quit": app.stop() else: msg = "Your choices: {c}".format(c=app.getProperties("properties")) app.infoBox("Show choices:", msg) app = gui() app.setSticky("news") app.setPadding(10, 2) app.addProperties("properties", {"Assembler": True, "C": True, "C++": False, "Perl": False, "Python": True}) app.addButton("Show choice", onButtonPress) app.addButton("Quit", onButtonPress) app.go()
18. Repositář s demonstračními příklady
Zdrojové kódy všech dvanácti dnes popsaných demonstračních příkladů naleznete pod následujícími odkazy:
Poznámka: pro úspěšné spuštění těchto příkladů musíte mít v aktuálním adresáři rozbalenou knihovnu appJar!. Podrobnosti jsme si řekli v předchozím článku.
19. Odkazy na Internetu
- Hra Breakout napísaná v Tkinteri
https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/ - Hra Snake naprogramovaná v Pythone s pomocou Tkinter
https://www.root.cz/clanky/hra-snake-naprogramovana-v-pythone-s-pomocou-tkinter/ - TkDND
http://freecode.com/projects/tkdnd - Python Tkinter Fonts
https://www.tutorialspoint.com/python/tk_fonts.htm - The Tkinter Canvas Widget
http://effbot.org/tkinterbook/canvas.htm - Ovládací prvek (Wikipedia)
https://cs.wikipedia.org/wiki/Ovl%C3%A1dac%C3%AD_prvek_%28po%C4%8D%C3%ADta%C4%8D%29 - Rezervovaná klíčová slova v Pythonu
https://docs.python.org/3/reference/lexical_analysis.html#keywords - TkDocs: Styles and Themes
http://www.tkdocs.com/tutorial/styles.html - Drawing in Tkinter
http://zetcode.com/gui/tkinter/drawing/ - Changing ttk widget text color (StackOverflow)
https://stackoverflow.com/questions/16240477/changing-ttk-widget-text-color - 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/ - 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 - 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/