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
5. Další podporované varianty widgetu pro vstup údajů, nastavení fokusu na textový widget
6. Reakce na změnu textu zapisovaného do widgetu Entry
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
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
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-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 | 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 | 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:
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
- 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/