Hlavní navigace

Tvorba GUI v Pythonu: menu, toolbary a widgety pro vstup textu v knihovně appJar

Pavel Tišnovský

Ve čtvrté části článku o knihovně appJar pro tvorbu GUI v Pythonu si nejdříve ukážeme poslední klasický widget určený pro vstup textu a dále se budeme zabývat tvorbou standardních dialogů, nástrojového pruhu a hlavního menu.

Obsah

1. Tvorba GUI v Pythonu: menu, toolbary a widgety pro vstup textu v knihovně appJar

2. Nejjednodušší podoba widgetu pro vstup textových údajů

3. Spojení widgetů Label a Entry

4. Omezení délky zadaného textu, specifikace výchozí hodnoty, widget pro zápis utajených údajů (hesla atd.)

5. Další podporované varianty widgetu pro vstup údajů, nastavení fokusu na textový widget

6. Reakce na změnu textu zapisovaného do widgetu Entry

7. Standardní dialogy

8. Jednoduché dialogy se zobrazením zprávy uživateli

9. Dialogy určené pro získání odpovědí uživatele

10. Nástrojový pruh (toolbar) v knihovně appJar

11. Jednoduchý nástrojový pruh se třemi tlačítky

12. Vytvoření složitějšího pruhu s nástroji

13. Toolbar s vlastními ikonami

14. Systém menu

15. Vytvoření jednoduchého menu

16. Zavolání separátních callback funkcí pro každou položku menu

17. Menu s přepínacími tlačítky

18. Menu s toolbarem ve společném okně

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

20. Odkazy na Internetu

1. Tvorba GUI v Pythonu: menu, toolbary a widgety pro vstup textu v knihovně appJar

Widget pro vstup textu se v nejjednodušším případě vytvoří metodou addEntry, které se předá jméno ovládacího prvku:

app.addEntry("login")

Samozřejmě je možné určit umístění widgetu do pomyslné mřížky i počet sousedních buněk, které widget obsadí (to se nijak neliší od ostatních widgetů):

app.addEntry("login", 0, 1, colspan=2)

Obrázek 1: Standardní widget určený pro vstup textu.

Text zapsaný do widgetu Entry se přečte metodou nazvanou getEntry(), které se předá jméno dříve vytvořeného widgetu (v našem případě používáme jméno „login“):

text=app.getEntry("login")

Obrázek 2: Text zapsaný uživatelem do widgetu lze snadno programově přečíst.

2. Nejjednodušší podoba widgetu pro vstup textových údajů

Podívejme se nyní na příklad, v němž je vytvořen widget pro vstup textových údajů a po stisku tlačítka Show Input se v informačním dialogu zobrazí jeho aktuální obsah:

#!/usr/bin/env python
 
from appJar import gui
 
 
def onButtonPress(buttonName):
    if buttonName == "Quit":
        app.stop()
    else:
        msg = "You type: {text}".format(text=app.getEntry("login"))
        app.infoBox("You type:", msg)
 
 
app = gui()
 
app.setSticky("news")
app.setPadding(2, 2)
 
app.addLabel("LoginLbl", "Login:", 0, 0)
app.addEntry("login", 0, 1, colspan=2)
 
app.addHorizontalSeparator(1, 0, colspan=3)
 
app.addButton("Show input", onButtonPress, 2, 1)
app.addButton("Quit", onButtonPress, 2, 2)
 
app.go()

3. Spojení widgetů Label a Entry

Widget pro vstup textu je prakticky ve všech případech spojen s textovým návěštím s popisem funkce widgetu. Aby nebylo nutné neustále vytvářet jak Label tak i Entry, lze použít kombinaci obou widgetů, která se vytvoří metodou addLabelEntry (varianta addLabel??? existuje i pro mnoho dalších widgetů):

app.addLabelEntry("Login:")

Opět je možné určit umístění widgetu do pomyslné mřížky i počet sousedních buněk, které widget obsadí:

app.addLabelEntry("Login:", 0, 0, colspan=2)

Obrázek 3: Widget Label+Entry.

Předchozí příklad je díky existenci metody addLabelEntry možné zkrátit a zpřehlednit o (pouhý) jeden řádek:

#!/usr/bin/env python
 
from appJar import gui
 
 
def onButtonPress(buttonName):
    if buttonName == "Quit":
        app.stop()
    else:
        msg = "You type: {text}".format(text=app.getEntry("Login:"))
        app.infoBox("You type:", msg)
 
 
app = gui()
 
app.setSticky("news")
app.setPadding(2, 2)
 
app.addLabelEntry("Login:", 0, 0, colspan=2)
 
app.addHorizontalSeparator(1, 0, colspan=3)
 
app.addButton("Show input", onButtonPress, 2, 1)
app.addButton("Quit", onButtonPress, 2, 2)
 
app.go()

Obrázek 4: Text zapsaný uživatelem do widget Label+Entry.

4. Omezení délky zadaného textu, specifikace výchozí hodnoty, widget pro zápis utajených údajů (hesla atd.)

Délku textu zapsaného do widgetu Entry je možné omezit. Příkladem může být rozhraní pro starodávné systémy, které omezovaly login na pouhých osm znaků. Toho lze velmi snadno docílit:

app.setEntryMaxLength("login", 8)

Taktéž je možné specifikovat výchozí text ve widgetu. Pokud widget není vybrán, je výchozí text vypsán formou nápovědy:

app.setEntryDefault("login", "your login")

Někdy je nutné zapsat údaje, o kterých by nemělo vědět okolí. Toho lze dosáhnout použitím metody addSecretEntry, která zapisovaný text skryje a vypíše namísto něj pouze hvězdičky. Chování tohoto widgetu je následující – pokud do něj zkopírujeme text ze schránky či z výběru, je skutečně zkopírován původní text, který se ovšem zobrazí jako hvězdičky. Pokud naopak vybereme skrytý text a přeneseme ho do jiné aplikace, přenesou se pouze hvězdičky (u jiných knihoven tomu tak být nemusí, ovšem chování appJar vede k nepatrně větší bezpečnosti):

app.addSecretEntry("password", 1, 1, colspan=2)

Obrázek 5: Widget s výchozím textem (zobrazuje se formou nápovědy) a widget pro zápis hesla.

Všechny vlastnosti popsané v předchozím textu jsou použity ve vylepšeném příkladu:

#!/usr/bin/env python
 
from appJar import gui
 
 
def onButtonPress(buttonName):
    if buttonName == "Quit":
        app.stop()
    else:
        msg = "You type: {login} + {password}".format(
            login=app.getEntry("login"),
            password=app.getEntry("password"))
        app.infoBox("You type:", msg)
 
 
app = gui()
 
app.setSticky("news")
app.setPadding(2, 2)
 
app.addLabel("LoginLbl", "Login:", 0, 0)
app.addEntry("login", 0, 1, colspan=2)
app.setEntryMaxLength("login", 8)
app.setEntryDefault("login", "your login")
 
app.addLabel("PasswordLbl", "Password:", 1, 0)
app.addSecretEntry("password", 1, 1, colspan=2)
 
app.addHorizontalSeparator(2, 0, colspan=3)
 
app.addButton("Show input", onButtonPress, 3, 1)
app.addButton("Quit", onButtonPress, 3, 2)
 
app.go()

Obrázek 6: Heslo lze ovšem programově získat v čitelné podobě.

5. Další podporované varianty widgetu pro vstup údajů, nastavení fokusu na textový widget

Widget Entry podporuje i některé další varianty. Zajímavá je varianta, která vedle samotného widgetu zobrazí i znak ukazující, jestli byl text zvalidován či nikoli. Vizuální validace textu se provede metodou setEntryValid, opakem je metoda setEntryInvalid:

app.addValidationEntry("name", 0, 1, colspan=2)
app.setEntryInvalid("name")

Další varianta umožňuje zadání číselných údajů (typu double, nikoli celočíselného typu), ovšem bez možnosti specifikace minimální a maximální hodnoty, takže se stejně musí provádět validace:

app.addLabel("AgeLbl", "Age:", 2, 0)
app.addNumericEntry("age", 2, 1, colspan=2)

Obrázek 7: První dva widgety jsou označeny jako nevalidní.

V dalším příkladu jsou použity tři widgety pro vstup textu. První widget umožňuje zadat jméno s omezením na deset znaků, druhý widget je určen pro zadání hesla a widget třetí pro zadání věku. Můžete si sami vyzkoušet, že poslední widget bude akceptovat reálné hodnoty a to včetně hodnot záporných:

#!/usr/bin/env python
 
from appJar import gui
 
 
def onButtonPress(buttonName):
    if buttonName == "Quit":
        app.stop()
    else:
        msg = "You type:\n{name}\n{surname}\n{age}".format(
            name=app.getEntry("name"),
            surname=app.getEntry("surname"),
            age=app.getEntry("age"))
        app.infoBox("You type:", msg)
 
 
app = gui()
 
app.setSticky("news")
app.setPadding(2, 2)
 
app.addLabel("NameLbl", "Name:", 0, 0)
app.addValidationEntry("name", 0, 1, colspan=2)
app.setEntryMaxLength("name", 10)
app.setEntryDefault("name", "your name")
app.setEntryInvalid("name")
 
app.setFocus("name")
 
app.addLabel("SurnameLbl", "Surname:", 1, 0)
app.addValidationEntry("surname", 1, 1, colspan=2)
app.setEntryMaxLength("surname", 10)
app.setEntryDefault("surname", "your surname")
app.setEntryInvalid("surname")
 
app.addLabel("AgeLbl", "Age:", 2, 0)
app.addNumericEntry("age", 2, 1, colspan=2)
 
app.addHorizontalSeparator(4, 0, colspan=3)
 
app.addButton("Show input", onButtonPress, 5, 1)
app.addButton("Quit", onButtonPress, 5, 2)
 
app.go()

6. Reakce na změnu textu zapisovaného do widgetu Entry

Pokud potřebujete zareagovat na jakoukoli změnu zapisovaného textu (a nečekat tak na stisk tlačítka Ok), stačí si zaregistrovat příslušnou callback funkci:

app.setEntryChangeFunction("name", onTextChange)
app.setEntryChangeFunction("surname", onTextChange)

Obrázek 8: Vstupní textová pole označená hvězdičkou značí, že je widget připraven pro vstup textu.

Callback funkci se předá jméno widgetu, takže lze snadno zjistit, zda do něj byl zapsán nějaký text a na základě této podmínky nastavit příznak validního nebo naopak nevalidního vstupu:

def onTextChange(widgetName):
    if not app.getEntry(widgetName):
        app.setEntryInvalid(widgetName)
    else:
        app.setEntryValid(widgetName)

Obrázek 9: První textové pole obsahuje validní text, druhé je prázdné (nevalidní) a třetí obsahuje záporné číslo.

Opět se podívejme na zdrojový kód příkladu, v němž je tato callback funkce deklarována a zaregistrována:

#!/usr/bin/env python
 
from appJar import gui
 
 
def onButtonPress(buttonName):
    if buttonName == "Quit":
        app.stop()
    else:
        msg = "You type:\n{name}\n{surname}\n{age}".format(
            name=app.getEntry("name"),
            surname=app.getEntry("surname"),
            age=app.getEntry("age"))
        app.infoBox("You type:", msg)
 
 
def onTextChange(widgetName):
    if not app.getEntry(widgetName):
        app.setEntryInvalid(widgetName)
    else:
        app.setEntryValid(widgetName)
 
 
app = gui()
 
app.setSticky("news")
app.setPadding(2, 2)
 
app.addLabel("NameLbl", "Name:", 0, 0)
app.addValidationEntry("name", 0, 1, colspan=2)
app.setEntryMaxLength("name", 10)
app.setEntryWaitingValidation("name")
app.setEntryChangeFunction("name", onTextChange)
 
app.addLabel("SurnameLbl", "Surname:", 1, 0)
app.addValidationEntry("surname", 1, 1, colspan=2)
app.setEntryMaxLength("surname", 10)
app.setEntryWaitingValidation("surname")
app.setEntryChangeFunction("surname", onTextChange)
 
app.addLabel("AgeLbl", "Age:", 2, 0)
app.addNumericEntry("age", 2, 1, colspan=2)
 
app.addHorizontalSeparator(4, 0, colspan=3)
 
app.addButton("Show input", onButtonPress, 5, 1)
app.addButton("Quit", onButtonPress, 5, 2)
 
app.go()

7. Standardní dialogy

S dialogovými okny jsme se setkali již při popisu možností knihovny Tkinter. Připomeňme si, že kromě zvládnutí vyšší komplexnosti aplikací hrají dialogová okna i další roli – pomáhají totiž standardizovat některé společné části aplikací. Například pro otevření souboru, uložení souboru, tisk dokumentu nebo výběr barvy je možné (a velmi vhodné) použít standardní dialog dodávaný s GUI systémem. Do jaké míry se tento systém standardizace využívá, čtenář patrně vidí na svém desktopu sám: určitá míra standardizace je patrná, také je však zřejmé, že mnohé aplikace využívají jiné GUI knihovny, o míchání několika desktopových prostředích ani nemluvě (to zdaleka není pouze problém GNU softwaru, „lidová tvořivost“ je vidět i na komerčních programech).

Při práci s dialogovými okny i v knihovně appJar rozlišujeme dialogy modální a nemodální. Modální dialogy převezmou řízení celé aplikace a nedovolí uživateli pokračovat v práci, dokud nevybere z dialogu nějaký příkaz. Naproti tomu jsou nemodální okna zobrazena „paralelně“ s aplikací a neblokují vstup do aplikace (kromě toho, že jsou většinou zobrazena nad aplikací). Vzhledem k tomu, že jsou modální okna programátorsky jednodušeji zvládnutelná, používají se častěji, a to i v těch případech, kdy modální okno uživatele zdržuje či mu komplikuje práci. Typickým příkladem je dialog pro vyhledávání (například řetězců), který by měl být prakticky vždy nemodální, ale mnohé aplikace ho implementují jako dialog modální.

V knihovně appJar nalezneme několik metod sloužících pro vytvoření standardních dialogových oken. Tato okna buď pouze zobrazí nějakou informaci, nebo si od uživatele vyžádají odpověď na zadanou otázku popř. rozhodnutí, zda se má pokračovat v nějaké činnosti, která neproběhla korektně (dialog typu Retry/Cancel):

Metoda Zobrazený dialog Použitá ikona
infoBox() dialog se zprávou
errorBox() dialog se zprávou
warningBox() dialog se zprávou
     
yesNoBox() dialog s tlačítky Yes a No
okBox() dialog s tlačítky Ok a Cancel
retryBox() dialog s tlačítky Retry a Cancel

Poznámka: ve skutečnosti se mohou ikony na různých systémech odlišovat, jejich význam však bude odpovídat typu dialogového okna. Výše zobrazená trojice ikon byla získána na Linuxu s Fluxboxem.

8. Jednoduché dialogy se zobrazením zprávy uživateli

První tři standardní dialogová okna vyvolaná metodami infoBox(), errorBox() a warningBox() si můžete nechat zobrazit po spuštění dalšího demonstračního příkladu. Pro zobrazení každého dialogového okna je určeno jedno z tlačítek „Info“, „Error“ a „Warning“, zatímco tlačítko „Quit“ ihned ukončí aplikaci. Povšimněte si, že u všech tří dialogových oken můžete zvolit titulek i vlastní zprávu zobrazenou uživateli. Ve zprávě je dokonce možné s využitím řídicího znaku \n provést odřádkování:

#!/usr/bin/env python
 
from appJar import gui
 
 
def onButtonPress(buttonName):
    if buttonName == "Info":
        app.infoBox("Info box", "Info box")
    elif buttonName == "Error":
        app.errorBox("Error box", "Error box")
    elif buttonName == "Warning":
        app.warningBox("Warning box", "Warning box")
    else:
        app.stop()
 
 
app = gui()
 
app.addButtons(["Info", "Error", "Warning", "Quit"], onButtonPress)
 
app.go()

Obrázek 10: Dialog s běžnou informací.

Obrázek 11: Dialog s chybovým hlášením.

Obrázek 12: Dialog s varováním.

9. Dialogy určené pro získání odpovědí uživatele

V dalším příkladu jsou ukázána dialogová okna zobrazená metodami yesNoBox(), okBox() a retryBox(). Ve chvíli, kdy uživatel vybere jednu z odpovědí, je dialogové okno zavřeno a návratová hodnota metody je vypsána na standardní výstup (terminál). Povšimněte si přitom, že se ve všech případech jedná o pravdivostní hodnotu True či False, přičemž True odpovídá tlačítkům „Ok“, „Yes“ či „Retry“ a False tlačítkům „Cancel“ či „No“:

def onButtonPress(buttonName):
    if buttonName == "Yes/No":
        print(app.yesNoBox("Yes No box", "Yes No box"))
    elif buttonName == "Ok/Cancel":
        print(app.okBox("Ok/Cancel box", "Ok/Cancel box"))
    elif buttonName == "Retry/Cancel":
        print(app.retryBox("Retry/Cancel box", "Retry/Cancel box"))
    else:
        if reallyQuit():
            app.stop()

Obrázek 13: Dialog s tlačítky Yes a No.

Obrázek 14: Dialog s tlačítky OK a Cancel.

Obrázek 15: Dialog s tlačítky Retry a Cancel.

Následuje výpis zdrojového kódu tohoto příkladu:

#!/usr/bin/env python
 
from appJar import gui
 
 
def reallyQuit():
    return app.yesNoBox("Really quit?", "Really you really want to quit?")
 
 
def onButtonPress(buttonName):
    if buttonName == "Yes/No":
        print(app.yesNoBox("Yes No box", "Yes No box"))
    elif buttonName == "Ok/Cancel":
        print(app.okBox("Ok/Cancel box", "Ok/Cancel box"))
    elif buttonName == "Retry/Cancel":
        print(app.retryBox("Retry/Cancel box", "Retry/Cancel box"))
    else:
        if reallyQuit():
            app.stop()
 
 
app = gui()
 
app.addButtons(["Yes/No", "Ok/Cancel", "Retry/Cancel", "Quit"], onButtonPress)
 
app.go()

10. Nástrojový pruh (toolbar) v knihovně appJar

V knihovně appJar nalezneme i podporu pro jednoduchý nástrojový pruh neboli toolbar. Implicitně je v nástrojovém pruhu zobrazena sada ikon, přičemž se po stisku libovolné ikony zavolá buď jedna společná callback funkce nebo je možné pro každou ikonu specifikovat vlastní callback funkci. Vytvoření toolbaru tedy může vypadat následovně:

tools = ["About", "Help", "Off"]
app.addToolbar(tools, společná_callback_funkce, findIcon=True)

Příslušné callback funkci se předá jméno ikony.

Pokud budeme chtít pro každou ikonu vyvolat vlastní callback funkci, změní se volání takto:

tools = ["About", "Help", "Off"]
functions = [callback_funkce_pro_ikonu_about,
             callback_funkce_pro_ikonu_help,
             callback_funkce_pro_ikonu_off]
app.addToolbar(tools, functions, findIcon=True)

Příslušné callback funkci se již jméno ikony nepředává!

Každá ikona je při použití parametru findIcon=True implicitně vybrána ze standardní sady ikon na základě jejího jména. Všechny ikony naleznete v podadresáři appJar/resources/icons:

Jméno ikony Jméno ikony Jméno ikony Jméno ikony
3d-cube 3d-cylinder 3d-glasses 3d-plane
3d-pyramid 3d-sphere 3d-wedge 3d-x-axis-rotation
3d-y-axis-rotation 3d-z-axis-rotation 4-direction-alt 4-direction
8-direction-alt 8-direction about access
activity add address-book airport
alarm alert-alt alert alt
anchor announce antivirus-alt antivirus
arrow-1-backward arrow-1-down-left arrow-1-down arrow-1-down-right
arrow-1-forward arrow-1-left arrow-1-right arrow-1-up-left
arrow-1-up arrow-1-up-right arrow-2-down-left arrow-2-down
arrow-2-down-right arrow-2-left arrow-2-right arrow-2-up-left
arrow-2-up arrow-2-up-right arrow-3-down-left arrow-3-down
arrow-3-down-right arrow-3-left arrow-3-right arrow-3-up-left
arrow-3-up arrow-3-up-right arrow-4-down-left arrow-4-down
arrow-4-down-right arrow-4-left arrow-4-right arrow-4-up-left
arrow-4-up arrow-4-up-right arrow-5-down arrow-5-left
arrow-5-right arrow-5-up arrow-6-down arrow-6-left
arrow-6-right arrow-6-up arrow-7-down arrow-7-left
arrow-7-right arrow-7-up arrow-8-down arrow-8-left
arrow-8-right arrow-8-up arrow-circular-alt-1 arrow-circular-alt-2
arrow-orthogonal-alt-1 arrow-orthogonal-alt-2 asterisk-alt asterisk
attachment axis-x-y-alt axis-x-y badge
bag-alt-1 bag-alt-2 bag-alt-3 bag-alt-4
bag-alt-5 baggage bag balance
bank-alt-1 bank-alt-2 bank barcode
basket-alt-1 basket-alt-2 basket-alt-3 basket
battery-empty-alt battery-empty battery-full-alt battery-full
bicycle blog bluetooth book-alt-2
book-alt-3 book-alt-4 book-alt bookmark-alt
bookmark book books box-alt
box briefcase brush-alt-1 brush-alt-2
brush bug bussiness-card cake-alt
cake calculator-alt calculator calendar-alt-1
calendar-alt-2 calendar cancel cart-alt-1
cart-alt-2 cart-alt-3 cart-alt-4 cart-alt-5
cart-alt-6 cart chair-alt chair
chat-active-alt-1 chat-active-alt-2 chat-active chat-alt-1
chat-alt-2 chat-alt-3 chat check-alt
checkbox-empty checkbox check cheque
cinema city close code
color-picker color-swatch comment-alt comment
compass-alt compass compress computer-laptop
computer computer-retro connect-alt-1 connect-alt-2
connection-error-alt-1 connection-error-alt-2 connection-error connect
construction content controls-alt-1 controls-alt-2
controls copy couch-alt-1 couch-alt-2
couch counter credit-card crop
cross cup-alt cup cut
data-alt-1 data-alt-2 database-add-alt-1 database-add-alt-2
database-add database-alt-1 database-alt-2 database-download-alt-1
database-download-alt-2 database-download database database-reload-alt-1
database-reload-alt-2 database-reload database-remove-alt-1 database-remove-alt-2
database-remove database-upload-alt-1 database-upload-alt-2 database-upload
database-user-alt database-user data delete
directions-alt directions display-mac-alt display-mac
display document-empty document-new document
documents door-closed door download-alt-1
download-alt-2 download-alt-3 download-alt-4 download
drawer-locked drawer drawers-alt-1 drawers-alt-2
drawers drawer-unlocked drill drive-disk-cd
drive-network edit-alt-1 edit-alt-2 edit-document
edit eightball elevator enter
eraser ethernet exit export
facebook-alt factory fan-alt fan
favourite-add favourite file-add file-download-alt
file-download file-edit file file-remove
files file-upload-alt file-upload fill
filter find fire firewall
firewire first-aid-alt first-aid flag
folder-open folder font food
ftp-alt-1 ftp-alt-2 ftp full-screen-alt-1
full-screen-alt-2 full-screen-alt-3 full-screen-alt-4 full-screen-alt-5
full-screen-exit-alt-1 full-screen-exit-alt-2 full-screen-exit-alt-3 full-screen-exit-alt-4
full-screen-exit-alt-5 full-screen-exit full-screen gameboy
games-alt-1 games-alt-2 games-alt-3 games-alt-4
games-alt-5 games-alt-6 games gift
glasses glasses-swim grid-alt-1 grid-alt-2
grid-alt-3 grid-dot grid hammer-alt
hammer hardware-chip hardware-processor hedge
help-alt help hierarchy high-definition
home icecream idea-alt idea
info i-phone i-pod key-Alt
key-A key-apple key-backspace-alt key-backspace
keyboard key-caps key-command key-control
key-enter keyhole key-option key-page-down
key-page-up key key-shift key-tab
key-windows kokoretsi kraftwerk label
lamp-alt-1 lamp-alt-2 lamp layers
layout-content layout-header layout layout-sidebar
license-key link-broken link list-numbered
list-ordered list list-unordered location
login logout luck magnet
mail-inbox mail mail-read mail-sent
man map-marker-pin map md-analog
md-aspect-ratio-alt md-aspect-ratio md-audio md-backward
md-betamax md-brightness md-camera-photo-alt-1 md-camera-photo-alt-2
md-camera-photo-alt-3 md-camera-photo md-camera-polaroid md-camera-surveillance
md-camera-video-alt-1 md-camera-video-alt-2 md-camera-video md-camera-web
md-cassette-tape md-cd-burn md-cd-card md-cd
md-contrast md-dat md-disc-3–5</td><td>md-disc-3
md-disc-5–1–4'' md-eject md-equalizer-alt md-equalizer
md-fast-backward-alt md-fast-backward md-fast-forward-alt md-fast-forward
md-film md-flash md-headphones-alt md-headphones-mic
md-headphones md-inverse md-knob-alt md-knob-decrease
md-knob-increase md-knob md-knob-volume md-levels-alt
md-levels-decrease md-levels-increase md-levels md-microphone-alt
md-microphone md-minidisc md-music md-next
md-pause md-photo-alt-1 md-photo-alt 2 md-photo
md-photos-alt md-photos md-picture-broken-link-alt md-picture-broken-link
md-play md-previous md-radio md-record
md-reload md-repeat-alt md-repeat-once md-repeat
md-resume md-shuffle md-sound md-speaker-alt
md-speaker md-split md-stop md-stream-audio
md-stream-video md-synth md-time-pos-back md-time-pos-forward
md-time-position md-time-pos-set md-tv md-vhs
md-video md-vinyl-33–1–3 md-vinyl-45 md-volume-0-alt
md-volume-0 md-volume-1 md-volume-2 md-volume-3
md-volume-down md-volume-up medal-alt medal
mobile-alt mobile module moleskine
moon mouse navigation-alt-1 navigation-alt-2
navigation network-alt-1 network new
node no notepad-alt notepad
off open-in-new-window open open-source
orientation-landscape orientation-portrait padlock-closed padlock-open
paintroller-alt paintroller park-bench paste
pattern pen pie-chart pill
pinetree plugin-disabled plugin podcast
pollution power-off power-on-off power-on
power-standby preferences presentation print-alt
printer printer-preview print projector
read-only redo refreshment refresh
register remote-control report resize
rip road-sign rss ruby
ruler-alt ruler safety-box save
science screenshot script search-advanced
search server servers settings
shape-circle shape-ellipse shape-hexagon shape-kite
shape-parallelogram-orthogonal shape-pentagon shape-rhombus shapes-align-hori-center
shapes-align-hori-left shapes-align-hori-right shapes-align-verti-bottom shapes-align-verti-middle
shapes-align-verti-top shapes-flip-horizontal shapes-flip-vertical shapes-move-back
shapes-move-backward shapes-move-forward shapes-move-front shape-square
shapes-rotate-anticlockwise shapes-rotate-clockwise shape-trapezoid shape-triangle-equilateral
shape-triangle-isosceles shape-triangle-rectangular shape-triangle-scalene sitemap
spaceship stamp star statistics-chart
sticky-note stop-alt stop stopwatch
store switch-off-alt switch-off switch-on-alt
switch-on table tab tag
tags target telephone tent
terminal thumbnails tie time-alt
time toolbox trafficlight-green trafficlight-orange
trafficlight trafficlight-red trash-empty trash-full
trophy truck twitter typewritter
undo unfold-from-bottom unfold-from-left unfold-from-right
unfold-from-top unfold-multiple user-alt-1 user-alt-2
user-alt-3 user-alt-4 user-art user-chat
user-female-alt-1 user-female-alt-2 user-female user-locked
user-male-alt-1 user-male-alt-2 user-male-alt-3 user-male
user-offline user user-refresh users-alt
user-sleep users view wall-alt
wallet wall water-alt water
weather-cloud weather-clouds weather-cloud-sun weather-rain
weather-snow weather-sun weather-thunder web
weight wi-fi window window-stack
window-tile-horizontally window-tile window-tile-vertically wireless-router
wizard zoom-in zoom-out zoom

11. Jednoduchý nástrojový pruh se třemi tlačítky

Ukažme si nyní, jak je možné vytvořit jednoduchý nástrojový pruh se třemi tlačítky. Nejdříve nadefinujeme seznam s názvy tlačítek, přičemž názvy odpovídají standardním ikonám (viz předchozí kapitolu):

tools = ["About", "Help", "Off"]

Toolbar nakonfigurujeme takovým způsobem, že se při výběru libovolné ikony bude volat jediná (společná) callback funkce:

app.addToolbar(tools, onToolbarButtonPress, findIcon=True)

Obrázek 16: Toolbar se třemi standardními ikonami.

V callback funkci je tedy nutné se na základě jména tlačítka rozhodnout, jaká akce se má vyvolat. Nejprimitivnější řešení může být vytvořeno zřetězením konstrukcí if-elif-elif…-else, ovšem většinou bude výhodnější použít slovník popř. deklarovat samostatné callback funkce pro jednotlivé ikony:

def onToolbarButtonPress(buttonName):
    if buttonName == "Off":
        if reallyQuit():
            app.stop()
    elif buttonName == "About":
        showAboutDialog()
    else:
        showHelpDialog()

Třetí ikona slouží k ukončení aplikace, ovšem až po potvrzení uživatelem:

def reallyQuit():
    return app.yesNoBox("Really quit?", "Really you really want to quit?")

Úplný zdrojový kód tohoto příkladu vypadá následovně:

#!/usr/bin/env python
 
from appJar import gui
 
 
def showAboutDialog():
    app.infoBox("About", "App1")
 
 
def showHelpDialog():
    app.infoBox("Help", "simple\nhelp\nmessage")
 
 
def reallyQuit():
    return app.yesNoBox("Really quit?", "Really you really want to quit?")
 
 
def onToolbarButtonPress(buttonName):
    if buttonName == "Off":
        if reallyQuit():
            app.stop()
    elif buttonName == "About":
        showAboutDialog()
    else:
        showHelpDialog()
 
 
tools = ["About", "Help", "Off"]
 
app = gui()
 
app.addToolbar(tools, onToolbarButtonPress, findIcon=True)
 
app.go()

12. Vytvoření složitějšího pruhu s nástroji

Po spuštění dalšího příkladu by se měl zobrazit mnohem rozsáhlejší pruh s 25 ikonami:

Obrázek 17: Celkem 25 standardních ikon nabízených knihovnou appJar.

Explicitně rozeznávány jsou však pouze ikony „About“, „Help“ a „Quit“, všechny ostatní ikony pouze vedou k zobrazení jednotného dialogu se jménem ikony:

#!/usr/bin/env python
 
from appJar import gui
 
 
def showAboutDialog():
    app.infoBox("About", "App1")
 
 
def showHelpDialog():
    app.infoBox("Help", "simple\nhelp\nmessage")
 
 
def showInfoDialog(buttonName):
    app.infoBox("Help", "You clicked on: " + buttonName)
 
 
def reallyQuit():
    return app.yesNoBox("Really quit?", "Really you really want to quit?")
 
 
def onToolbarButtonPress(buttonName):
    if buttonName == "Off":
        if reallyQuit():
            app.stop()
    elif buttonName == "About":
        showAboutDialog()
    elif buttonName == "Help":
        showHelpDialog()
    else:
        showInfoDialog(buttonName)
 
 
tools = ["About", "Alarm", "Computer", "Construction", "Refresh",
         "Open", "Close", "Save", "Display", "Files",
         "New", "Settings", "Print", "Printer", "Search", "Undo",
         "Redo", "Preferences", "Home", "Help", "Calendar",
         "Web", "Spaceship", "Wizard", "Off"]
app = gui()
 
app.addToolbar(tools, onToolbarButtonPress, findIcon=True)
 
app.go()

13. Toolbar s vlastními ikonami

Sada standardních ikon dodávaných ke knihovně appJar samozřejmě nemusí vyhovovat každému. Navíc může dojít (a velmi často dojde) k situaci, kdy je nutné použít takovou ikonu, která ani ve standardní sadě není přítomna. V takových případech lze načíst vlastní ikonu, a to z rastrového obrázku uloženého ve formátu PNG či GIF, přičemž preferován je formát GIF, který je pro Tkinter a tím pádem i pro knihovnu appJar považován za formát „nativní“. Podívejme se nyní na způsob použití vlastních ikon na toolbaru. Nejprve toolbar vytvoříme tak, jak to již známe z předchozích kapitol:

tools = ["About", "Help", "Off"]
 
app.addToolbar(tools, onToolbarButtonPress, findIcon=True)

Následně načteme ikony z externích souborů:

app.setToolbarImage("Off", "icons/application-exit.gif")
app.setToolbarImage("About", "icons/about.png")
app.setToolbarImage("Help", "icons/help.png")

Obrázek 18: Toolbar s vlastními ikonami.

Podívejme se na celý příklad, v němž jsou použity tři ikony, které naleznete v repositáři s demonstračními příklady:

#!/usr/bin/env python
 
from appJar import gui
 
 
def showAboutDialog():
    app.infoBox("About", "App1")
 
 
def showHelpDialog():
    app.infoBox("Help", "simple\nhelp\nmessage")
 
 
def reallyQuit():
    return app.yesNoBox("Really quit?", "Really you really want to quit?")
 
 
def onToolbarButtonPress(buttonName):
    if buttonName == "Off":
        if reallyQuit():
            app.stop()
    elif buttonName == "About":
        showAboutDialog()
    else:
        showHelpDialog()
 
 
tools = ["About", "Help", "Off"]
 
app = gui()
 
app.addToolbar(tools, onToolbarButtonPress, findIcon=True)
 
app.setToolbarImage("Off", "icons/application-exit.gif")
app.setToolbarImage("About", "icons/about.png")
app.setToolbarImage("Help", "icons/help.png")
 
app.go()

14. Systém menu

Nabídky (menu) patří mezi nedílnou součást prakticky každého složitějšího programu s grafickým uživatelským rozhraním. Systém menu zobrazuje uživateli GUI aplikace jednu nebo více zobrazitelných nabídek, které je možné vybrat, nastavit nebo přepnout; na rozdíl od dialogů však menu na ploše obrazovky zabírá jen minimální místo či dokonce žádné místo v případě kontextových menu. Bývá dobrým zvykem, že menu ve své struktuře obsahuje všechny příkazy a parametry provozované aplikace (ne všechny příkazy musí být samozřejmě po celou dobu běhu aplikace dostupné, někdy mohou být „zašedlé“).

Podporu pro vytvoření menu samozřejmě nalezneme i v knihovně appJar, přičemž základní menu lze vytvořit velmi snadno. Zkusme si například vytvořit dvě položky menu umístěné na hlavní liště. Tyto položky se budou jmenovat „File“ a „Help“ a pod každou položkou se bude nabízet několik voleb. Nejprve nadeklarujeme dva seznamy, které budou představovat jednotlivé položky obou menu. Jedná se o běžné seznamy podporované jazykem Python:

fileMenu = ["Open", "Save", "-", "Close"]
helpMenu = ["About", "Help"]

Ze seznamů vytvoříme menu a ty propojíme s položkami „File“ a „Help“ na hlavní liště:

app.addMenuList("File", fileMenu, onMenuItemSelect)
app.addMenuList("Help", helpMenu, onMenuItemSelect)

V obou případech se při výběru položky bude volat jediná společná callback funkce, které se předá vybraný příkaz (ve formě řetězce):

def onMenuItemSelect(menuItem):
    if menuItem == "Close":
        if reallyQuit():
            app.stop()
    elif menuItem == "About":
        showAboutDialog()
    elif menuItem == "Help":
        showHelpDialog()

Obrázek 19: Klasické hlavní menu vytvořené v knihovně appJar.

15. Vytvoření jednoduchého menu

Postup, který jsme si popsali v předchozí kapitole nyní použijeme v demonstračním příkladu, který po svém spuštění zobrazí okno s hlavním menu a vodorovným oddělovačem (ten je do okna vložen jen proto, aby se změnila jeho šířka):

#!/usr/bin/env python
 
from appJar import gui
 
 
def showAboutDialog():
    app.infoBox("About", "App1")
 
 
def showHelpDialog():
    app.infoBox("Help", "simple\nhelp\nmessage")
 
 
def reallyQuit():
    return app.yesNoBox("Really quit?", "Really you really want to quit?")
 
 
def onMenuItemSelect(menuItem):
    if menuItem == "Close":
        if reallyQuit():
            app.stop()
    elif menuItem == "About":
        showAboutDialog()
    elif menuItem == "Help":
        showHelpDialog()
 
 
fileMenu = ["Open", "Save", "-", "Close"]
helpMenu = ["About", "Help"]
 
app = gui()
 
app.setSticky("news")
app.setPadding(10, 2)
 
app.addMenuList("File", fileMenu, onMenuItemSelect)
app.addMenuList("Help", helpMenu, onMenuItemSelect)
 
app.addHorizontalSeparator(0, 1, colspan=2)
 
app.go()

16. Zavolání separátních callback funkcí pro každou položku menu

Pro jednotlivé položky menu samozřejmě není nutné volat jedinou společnou callback funkci, v níž se následně budeme rozhodovat, jaký příkaz se vlastně má provést. Pokud totiž metodě addMenuList předáme seznam položek menu a stejně dlouhý seznam callback funkcí, bude se při výběru n-té položky uživatelem volat n-tá callback funkce, což samozřejmě zjednoduší návrh aplikace. V praxi to může vypadat například takto:

fileMenu = ["Open", "Save", "Close"]
helpMenu = ["About", "Help"]

První seznam položek obsahuje tři prvky, druhý seznam prvky dva, čemuž musí odpovídat i třetí parametr předaný do metody addMenuList:

app.addMenuList("File", fileMenu, [none, none, closeItemSelected])
app.addMenuList("Help", helpMenu, [showHelpDialog, showAboutDialog])

Opět si ukažme zdrojový kód úplného příkladu, v němž je tento princip použit:

#!/usr/bin/env python
 
from appJar import gui
 
 
def showAboutDialog(menuItem):
    app.infoBox("About", "App1")
 
 
def showHelpDialog(menuItem):
    app.infoBox("Help", "simple\nhelp\nmessage")
 
 
def reallyQuit():
    return app.yesNoBox("Really quit?", "Really you really want to quit?")
 
 
def none(menuItem):
    pass
 
 
def closeItemSelected(menuItem):
    if reallyQuit():
        app.stop()
 
 
fileMenu = ["Open", "Save", "Close"]
helpMenu = ["About", "Help"]
 
app = gui()
 
app.setSticky("news")
app.setPadding(10, 2)
 
app.addMenuList("File", fileMenu, [none, none, closeItemSelected])
app.addMenuList("Help", helpMenu, [showHelpDialog, showAboutDialog])
 
app.addHorizontalSeparator(0, 1, colspan=2)
 
app.go()

17. Menu s přepínacími tlačítky

Do menu je možné přidat i přepínací tlačítka (radio buttons) a dokonce i zatrhávací tlačítka (check box). Podívejme se nyní na způsob vytvoření přepínacích tlačítek a aby to nebylo tak jednoduché, bude jejich deklarace provedena v programové smyčce. Konkrétně se bude jednat o tlačítka (resp. přesněji řečeno o přepínací položky menu) určená pro výběr zvětšení v rozsahu 1× až 9×. Tyto položky můžeme vytvořit v programové smyčce. Povšimněte si, že se tlačítka přidají do menu „Zoom“ a všechny spadají do stejné skupiny pojmenované „zoom“ (můžete si však zvolit libovolné jméno):

for i in range(1, 10):
    app.addMenuRadioButton("Zoom", "zoom", "{i}\u00d7".format(i=i),
                           lambda item, i=i: zoomFunction(i))

Trik s přiřazením i=i je zde nutný, aby se skutečně vytvořil uzávěr nad aktuální hodnotou proměnné i. Samotná callback funkce zavolaná po výběru jedné položky menu je již triviální:

def zoomFunction(zoom):
    print(zoom)
    app.setLabel("zoomLbl", "{z}\u00d7".format(z=zoom))

Následuje výpis úplného zdrojového kódu příkladu:

#!/usr/bin/env python
 
from appJar import gui
 
 
def showAboutDialog(menuItem):
    app.infoBox("About", "App1")
 
 
def showHelpDialog(menuItem):
    app.infoBox("Help", "simple\nhelp\nmessage")
 
 
def reallyQuit():
    return app.yesNoBox("Really quit?", "Really you really want to quit?")
 
 
def none(menuItem):
    pass
 
 
def closeItemSelected(menuItem):
    if reallyQuit():
        app.stop()
 
 
def zoomFunction(zoom):
    print(zoom)
    app.setLabel("zoomLbl", "{z}\u00d7".format(z=zoom))
 
 
fileMenu = ["Open", "Save", "Close"]
helpMenu = ["About", "Help"]
 
app = gui()
 
app.setSticky("news")
app.setPadding(10, 2)
 
app.addMenuList("File", fileMenu, [none, none, closeItemSelected])
 
app.createMenu("Zoom")
 
for i in range(1, 10):
    app.addMenuRadioButton("Zoom", "zoom", "{i}\u00d7".format(i=i),
                           lambda item, i=i: zoomFunction(i))
 
app.createMenu("Config")
 
for i in range(5):
    app.addMenuCheckBox("Config", "Size 1{s}".format(s=i))
 
app.addMenuList("Help", helpMenu, [showHelpDialog, showAboutDialog])
 
app.addHorizontalSeparator(0, 1, colspan=2)
 
app.addLabel("Zoom", "zoom", 1, 1)
app.addLabel("zoomLbl", "1\u00d7", 1, 2)
 
app.setGeometry("250x80")
app.go()

18. Menu s toolbarem ve společném okně

Toolbar je samozřejmě možné zkombinovat s menu, přičemž je pevně nastaveno, že se menu zobrazí nad toolbarem, nezávisle na tom, který z těchto prvků je deklarován dříve:

app.addToolbar(tools, onToolbarButtonPress, findIcon=True)
app.addMenuList("File", fileMenu, onMenuItemSelect)
app.addMenuList("Help", helpMenu, onMenuItemSelect)

Obrázek 20: Menu společně s nástrojovým pruhem ve společném okně.

Úplný zdrojový kód příkladu s menu a současně i s toolbarem:

#!/usr/bin/env python
 
from appJar import gui
 
 
def showAboutDialog():
    app.infoBox("About", "App1")
 
 
def showHelpDialog():
    app.infoBox("Help", "simple\nhelp\nmessage")
 
 
def reallyQuit():
    return app.yesNoBox("Really quit?", "Really you really want to quit?")
 
 
def onMenuItemSelect(menuItem):
    if menuItem == "Close":
        if reallyQuit():
            app.stop()
    elif menuItem == "About":
        showAboutDialog()
    elif menuItem == "Help":
        showHelpDialog()
 
 
def onToolbarButtonPress(buttonName):
    if buttonName == "Off":
        if reallyQuit():
            app.stop()
    elif buttonName == "About":
        showAboutDialog()
    else:
        showHelpDialog()
 
 
tools = ["About", "Help", "Off"]
 
fileMenu = ["Open", "Save", "-", "Close"]
helpMenu = ["About", "Help"]
 
app = gui()
 
app.setSticky("news")
app.setPadding(10, 2)
 
app.addToolbar(tools, onToolbarButtonPress, findIcon=True)
app.addMenuList("File", fileMenu, onMenuItemSelect)
app.addMenuList("Help", helpMenu, onMenuItemSelect)
 
app.setToolbarImage("Off", "icons/application-exit.gif")
app.setToolbarImage("About", "icons/about.png")
app.setToolbarImage("Help", "icons/help.png")
 
app.addHorizontalSeparator(0, 1, colspan=2)
 
app.go()

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

Zdrojové kódy všech čtrnácti dnes popsaných demonstračních příkladů naleznete pod následujícími odkazy. Kromě toho jsou v tabulce uvedeny odkazy na rastrové obrázky použité pro ikony v toolbaru:

Příklad Adresa
41_entry.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/41_entry.py
42_labelentry.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/42_labelen­try.py
43_secret_entry.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/43_secret_en­try.py
44_other_entries.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/44_other_en­tries.py
45_on_change.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/45_on_chan­ge.py
46_dialogs.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/46_dialogs­.py
47_questions.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/47_question­s.py
48_toolbar.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/48_toolbar­.py
49_bigger_toolbar.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/49_bigger_to­olbar.py
50_images_in_toolbar.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/50_images_in_to­olbar.py
51_menubar.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/51_menubar­.py
52_menubar_separate_functions.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/52_menubar_se­parate_functions.py
53_menu_radio_buttons.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/53_menu_ra­dio_buttons.py
54_menubar_toolbar.py https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/54_menubar_to­olbar.py
   
icons/about.png https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/icons/abou­t.png
icons/application-exit.gif https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/icons/appli­cation-exit.gif
icons/help.png https://github.com/tisnik/pre­sentations/blob/master/Pyt­hon_GUI/appJar/icons/help­.png

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 úvodním článku.

20. Odkazy na Internetu

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