Obsah
1. Pokročilejší ovládací prvky nabízené knihovnou appJar
2. Ovládací prvek grid – jednoduchá tabulka
3. První příklad – použití prvku grid pro zobrazení klávesnice telefonu
4. Vytvoření tabulky s použitím n-tic a generátorů
5. První řádek tabulky s nadpisy sloupců
6. Zobrazení tabulky s malou násobilkou
7. Získání informací o vybraných políčkách tabulky
8. Zpracování slovníku s informacemi o vybraných políčkách tabulky
9. Tlačítka zobrazená u každého řádku tabulky a reakce na jejich stisk
10. Zobrazení vstupních políček určených pro rozšíření tabulky
11. Kombinace předchozích dvou příkladů – rozšíření tabulky a výpočty nad daty v řádku
12. Ovládací prvek tree – strom zobrazující strukturu XML
13. Zobrazení jednoduchého stromu
14. Konfigurace barev použitých při vykreslení stromu
15. Strom s editovatelnými koncovými uzly
16. Získání obsahu vybraného uzlu
17. Repositář s demonstračními příklady
1. Pokročilejší ovládací prvky nabízené knihovnou appJar
Všechny základní ovládací prvky (widgety) grafického uživatelského rozhraní, které jsou nabízeny knihovnou appJar, jsme si popsali v předchozích čtyřech částech tohoto seriálu. Již popsané widgety mohou být dostačující pro tvorbu jednodušších aplikací i pro výuku základních konceptů používaných v GUI (ostatně právě pro tento účel knihovna appJar vznikla), ovšem mnoho složitějších aplikací vyžaduje použití dalších ovládacích prvků. Mezi tyto vyžadované prvky patří zejména tabulka (zde nazývaná grid) a taktéž prvek pro zobrazení stromové struktury (tree). Tyto ovládací prvky nám knihovna appJar taktéž nabízí, ovšem s tím, že v současné verzi knihovny appJar jsou možnosti těchto prvků omezeny, což bude ostatně patrné i při spuštění demonstračních příkladů. Kromě toho knihovna appJar nabízí i několik dalších spíše jednoúčelových prvků, o nichž se samozřejmě taktéž ve stručnosti zmíníme.
2. Ovládací prvek grid – jednoduchá tabulka
Prvním ovládacím prvkem, s nímž se dnes seznámíme, je tabulka, která je v knihovně appJar nazývána grid. Při konstrukci gridu se konstruktoru představovanému metodou addGrid předává dvourozměrná datová struktura, která je v Pythonu reprezentována tím nejjednodušším možným způsobem, tj. seznamem obsahujícím jednotlivé řádky tabulky, přičemž každý řádek je taktéž tvořen seznamem:
tabulka = [ ["prvky, "na", "prvním", "řádku"], ["prvky, "na", "druhém", "řádku"], ["prvky, "na", "třetím", "řádku"] ]
Pokud všechny podseznamy obsahují shodný počet prvků, vytvoří se pravidelná tabulka; v opačném případě budou některá políčka výsledné tabulky nevyplněna. Ve skutečnosti je však při konstrukci gridu možné namísto seznamů použít i n-tice (tuple) nebo generátory (generator), což si samozřejmě taktéž ukážeme v demonstračních příkladech.
3. První příklad – použití prvku grid pro zobrazení klávesnice telefonu
Ukažme si nyní, jakým způsobem je možné zobrazit jednoduchou tabulku s využitím ovládacího prvku grid. Nejprve vytvoříme zdrojová data, tj. seznam seznamů obsahujících jednotlivé prvky, které mají být v tabulce zobrazeny, a předáme ji konstruktoru ovládacího prvku grid. Prvek musí být pojmenován, což využijeme v dalších příkladech:
app.addGrid("grid", [[' 1 ', ' 2 ', ' 3 '], [' 4 ', ' 5 ', ' 6 '], [' 7 ', ' 8 ', ' 9 '], [' * ', ' 0 ', ' # ']])
Obrázek 1: Prozatím jen velmi primitivní tabulka s klávesnicí ze starých (dobrých) tlačítkových telefonů. Povšimněte si, že první řádek tabulky tvoří nadpisy sloupců.
Do okna navíc přidáme menu s příkazem Quit, což je téma, kterému jsme se již věnovali minule:
#!/usr/bin/env python from appJar import gui def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addGrid("grid", [[' 1 ', ' 2 ', ' 3 '], [' 4 ', ' 5 ', ' 6 '], [' 7 ', ' 8 ', ' 9 '], [' * ', ' 0 ', ' # ']]) app.go()
4. Vytvoření tabulky s použitím n-tic a generátorů
Ve druhé kapitole jsme si řekli, že při tvorbě tabulky je možné zdrojová data, která mají být zobrazena, vytvořit jako seznam seznamů, popř. že je možné použít n-tice (tuple) nebo generátory. Nejprve se podívejme na použití n-tic. Je to stejně snadné jako použití seznamů:
app.addGrid("grid", ((' 1 ', ' 2 ', ' 3 '), (' 4 ', ' 5 ', ' 6 '), (' 7 ', ' 8 ', ' 9 '), (' * ', ' 0 ', ' # ')))
Poznámka: n-tice jsou neměnitelné, což v některých případech může zjednodušit pochopení zdrojového kódu.
Obrázek 2: Prakticky stejná aplikace, která však nyní používá n-tice a nikoli seznamy pro zdrojová data tabulky.
Upravený zdrojový kód příkladu:
#!/usr/bin/env python from appJar import gui def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addGrid("grid", ((' 1 ', ' 2 ', ' 3 '), (' 4 ', ' 5 ', ' 6 '), (' 7 ', ' 8 ', ' 9 '), (' * ', ' 0 ', ' # '))) app.go()
Použití generátorů si vysvětlíme na známé konstrukci range. Zdrojová data tabulky lze v případě potřeby (rozsáhlé tabulky) vytvořit i následujícím způsobem:
app.addGrid("grid", (range(1, 4), range(4, 7), range(7, 10), (' * ', ' 0 ', ' # ')))
Upravený zdrojový kód příkladu:
#!/usr/bin/env python from appJar import gui def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addGrid("grid", (range(1, 4), range(4, 7), range(7, 10), (' * ', ' 0 ', ' # '))) app.go()
5. První řádek tabulky s nadpisy sloupců
Z předchozího screenshotu je patrné, že první řádek tabulky je zvýrazněn, protože slouží pro zobrazení nadpisů. V současné verzi knihovny appJar ovšem není možné nadpisy eliminovat, což znamená, že pokud nechceme, aby se první řádek tlačítek telefonu zobrazil v prvním řádku, musíme explicitně ve zdrojové tabulce vytvořit prázdný řádek, což není příliš elegantní řešení:
app.addGrid("grid", [['', '', ''], [' 1 ', ' 2 ', ' 3 '], [' 4 ', ' 5 ', ' 6 '], [' 7 ', ' 8 ', ' 9 '], [' * ', ' 0 ', ' # ']])
Obrázek 3: Nyní je řádek s nadpisy prázdný, ale stále viditelný.
Zdrojový text demonstračního příkladu se změní jen nepatrně:
#!/usr/bin/env python from appJar import gui def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addGrid("grid", [['', '', ''], [' 1 ', ' 2 ', ' 3 '], [' 4 ', ' 5 ', ' 6 '], [' 7 ', ' 8 ', ' 9 '], [' * ', ' 0 ', ' # ']]) app.go()
6. Zobrazení tabulky s malou násobilkou
V dalším příkladu si zobrazíme klasickou tabulku s malou násobilkou (resp. libovolně velkou tabulku s násobilkou, nemusíme se totiž omezit na činitele od jedné do deseti). Pro deklaraci zdrojových dat je možné použít hned několik metod pro vytvoření seznamu seznamů s hodnotami malé násobilky. Nejkratší je zápis využívající generátorovou notaci seznamu neboli list comprehension. Náš úkol je však nepatrně složitější, protože výsledkem nemá být jeden seznam, ale seznam seznamů, takže budeme postupovat tak, že nejdříve vytvoříme podseznamy, které představují řádky tabulky a z nich vytvoříme výsledný seznam řádků. Pro každý následující řádek se zvýší hodnota počitadla i. Funkci, která vrátí výslednou tabulku, je možné zapsat následovně:
def createTable(n): return [[i*j for i in range(1, n+1)] for j in range(1, n+1)]
Volání výše deklarované funkce createTable samozřejmě můžeme umístit přímo do konstruktoru tabulky:
app.addGrid("grid", createTable(10))
Obrázek 4: Malá násobilka.
Opět si ukažme, jak může vypadat úplný zdrojový kód tohoto příkladu:
#!/usr/bin/env python from appJar import gui def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() def createTable(n): return [[i*j for i in range(1, n+1)] for j in range(1, n+1)] app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addGrid("grid", createTable(10)) app.go()
7. Získání informací o vybraných políčkách tabulky
Tabulka zobrazená ve widgetu grid není určena pouze pro zobrazení předem připravených hodnot, ale je ji možné použít i pro další operace, i když se samozřejmě v žádném případě nejedná o plnohodnotný tabulkový procesor. Při testování předchozích příkladů jste si pravděpodobně všimli, že jednotlivá políčka tabulek je možné vybírat pomocí myši. Pro získání informací o vybraných políčkách se používá metoda getGridSelectedCells, která vrací slovník, jehož klíči jsou adresy políček a hodnotami True pro vybrané políčko a False pro políčko nevybrané. Zkusme si nyní příklad upravit tak, aby dokázal zobrazit vybraná políčka. Nejdříve do okna přidáme nové tlačítko:
app.addButton("Show selected cells", onButtonPress)
Na stisk tohoto tlačítka je navázána callback funkce nazvaná onButtonPress, která přečte informace o tabulce pomocí výše zmíněné metody getGridSelectedCells a získanou informaci bez dalšího zpracování vytiskne na standardní výstup:
def onButtonPress(buttonName): cells = app.getGridSelectedCells("grid") print(cells)
Obrázek 5: Výběr buněk v tabulce.
Zdrojový kód takto upraveného příkladu vypadá následovně:
#!/usr/bin/env python from appJar import gui def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() def onButtonPress(buttonName): cells = app.getGridSelectedCells("grid") print(cells) def createTable(n): return [[i*j for i in range(1, n+1)] for j in range(1, n+1)] app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addGrid("grid", createTable(10)) app.addButton("Show selected cells", onButtonPress) app.go()
8. Zpracování slovníku s informacemi o vybraných políčkách tabulky
Podívejme se nyní na příklad slovníku, který byl získán metodou getGridSelectedCells:
{ '4-6': True, '4-7': False, '0-8': False, '7-8': False, '5-5': False, '8-7': False, '3-4': False, '7-7': False, '6-7': False, '7-4': False, '1-2': False, '2-5': True, '5-6': False, '0-3': False, '6-4': False, '6-9': False, '2-9': False, '5-1': False, '8-3': False, '0-7': False, '7-9': False, '3-2': False, '5-4': False, '5-3': False, '3-3': False, '1-7': False, '2-2': False, '4-4': False, '8-0': False, '8-9': True, '6-2': False, '7-3': False, '6-3': False, '8-2': False, '6-0': False, '5-9': False, '4-9': False, '4-3': False, '7-5': False, '0-9': False, '5-0': False, '8-5': False, '8-8': False, '0-5': False, '7-6': False, '1-5': False, '2-8': False, '1-9': False, '5-7': False, '1-3': False, '4-0': False, '2-3': False, '3-8': False, '5-2': False, '3-0': False, '7-2': False, '1-8': False, '3-6': False, '3-5': False, '0-0': False, '8-4': False, '1-1': False, '5-8': False, '0-4': False, '1-4': False, '2-6': False, '1-6': False, '6-5': False, '3-9': False, '4-2': False, '8-1': False, '8-6': False, '6-8': False, '3-1': False, '2-4': False, '6-6': False, '0-2': False, '6-1': False, '4-5': False, '1-0': False, '4-8': False, '0-6': False, '0-1': True, '4-1': False, '7-1': False, '2-0': False, '3-7': False, '7-0': False, '2-1': False, '2-7': False }
Vidíme, že klíče ve slovníku představují adresu políčka zapsanou v řetězci, v němž je řádek a sloupec oddělen pomlčkou. Hodnoty pak skutečně nabývají hodnot True pro vybrané políčko a False pro políčko, které nebylo vybráno. Toho lze využít pro snadné získání adres pouze těch buněk, které jsou vybrány. Opět využijeme generátorovou notaci seznamu; tentokrát je ovšem iterační smyčka doplněna o podmínku na hodnotu záznamu ve slovníku:
cells = app.getGridSelectedCells("grid") selectedCells = [c for c, v in cells.items() if v]
Příklad použití – výpis vybraných buněk tabulky:
def onButtonPress(buttonName): cells = app.getGridSelectedCells("grid") selectedCells = [c for c, v in cells.items() if v] selectedCells.sort() print(selectedCells) if selectedCells: message = " ".join(selectedCells) app.infoBox("Selected cells", message) else: app.warningBox("Warning", "Please select at least one cell")
V případě potřeby lze adresu políčka (řádek, sloupec) z řetězce získat například jednoduchým zpracováním řetězce s adresou (pro jednoduchost neprovádím v kódu žádné kontroly na korektnost vstupu):
def parseCellAddress(cell): a = cell.split("-") return (int(a[0]), int(a[1])) def onButtonPress(buttonName): cells = app.getGridSelectedCells("grid") selectedCells = [c for c, v in cells.items() if v] selectedCells.sort() print(selectedCells) if selectedCells: message = " ".join(selectedCells) app.infoBox("Selected cells", message) else: app.warningBox("Warning", "Please select at least one cell") addresses = [parseCellAddress(c) for c in selectedCells] print(addresses)
Obrázek 6: Zobrazení vybraných buněk v tabulce.
Opět se podívejme na celý zdrojový kód příkladu:
#!/usr/bin/env python from appJar import gui def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() def onButtonPress(buttonName): cells = app.getGridSelectedCells("grid") selectedCells = [c for c, v in cells.items() if v] selectedCells.sort() print(selectedCells) if selectedCells: message = " ".join(selectedCells) app.infoBox("Selected cells", message) else: app.warningBox("Warning", "Please select at least one cell") def createTable(n): return [[i*j for i in range(1, n+1)] for j in range(1, n+1)] app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addGrid("grid", createTable(10)) app.addButton("Show selected cells", onButtonPress) app.go()
9. Tlačítka zobrazená u každého řádku tabulky a reakce na jejich stisk
U každého řádku tabulky je možné zobrazit tlačítko, které po svém stisku zavolá callback funkci, jíž se předá obsah daného řádku tabulky. Ukažme si použití těchto tlačítek na jednoduché tabulce, ve které je zobrazen počet odpracovaných hodin pro jednotlivé zaměstnance:
table = [["Name", "Work hours"], ["Petr", 160], ["Pavel", 90], ["Honza", 120]]
Zobrazení tlačítek a registrace callback funkce se provede takto:
app.addGrid("grid", table, action=onGridButton)
V callback funkci pak z předaného parametru získáme obsah obou buněk na vybraném řádku tabulky (jména a počtu odpracovaných hodin):
name = values[0] hours = int(values[1])
Z těchto údajů snadno vypočteme odpracované procento z normohodin a zobrazíme ho uživateli v dialogu:
norm = 160 utilization = 100.0 * hours / norm message = "{h} of {n} hours: {u:.0f} %".format(h=hours, n=norm, u=utilization) app.infoBox("Work hours for " + name, message)
Obrázek 7: Výpočet procent z pracovní doby.
Opět si ukažme, jak vypadá úplný demonstrační příklad:
#!/usr/bin/env python from appJar import gui def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() def onButtonPress(buttonName): cells = app.getGridSelectedCells("grid") selectedCells = [c for c, v in cells.items() if v] selectedCells.sort() print(selectedCells) if selectedCells: message = " ".join(selectedCells) app.infoBox("Selected cells", message) else: app.warningBox("Warning", "Please select at least one cell") def onGridButton(values): name = values[0] hours = int(values[1]) norm = 160 utilization = 100.0 * hours / norm message = "{h} of {n} hours: {u:.0f} %".format(h=hours, n=norm, u=utilization) app.infoBox("Work hours for " + name, message) app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) table = [["Name", "Work hours"], ["Petr", 160], ["Pavel", 90], ["Honza", 120]] app.addGrid("grid", table, action=onGridButton) app.addButton("Show selected cells", onButtonPress) app.go()
10. Zobrazení vstupních políček určených pro rozšíření tabulky
Na poslední řádek tabulky je možné umístit vstupní políčka, která umožní rozšířit tabulku o další řádky. Postupovat budeme následovně. Nejprve povolíme zobrazení vstupních políček (a navíc i potvrzovacího tlačítka):
app.addGrid("grid", table, action=onAddRow, addRow=True)
Obrázek 8: Vyplnění dat pro nový řádek tabulky.
Dále připravíme callback funkci onAddRow tak, aby se zkontrolovala data zadaná uživatelem a pokud jsou data v pořádku, aby se do tabulky vložil nový řádek s těmito údaji. Povšimněte si, že se této callback funkci předává řetězec „newRow“, protože se jedná o stejnou callback funkci, jakou jsme použili v předchozím příkladu (což je, pravda, poněkud podivné). Uživatelem zadaná data se získají metodou getGridEntries:
def onAddRow(data): if data == "newRow": values = app.getGridEntries("grid") print(values) if values[0] and values[1]: app.addGridRow("grid", values)
Obrázek 9: Nová data byla skutečně do tabulky přidána.
Použití této callback funkce v příkladu může vypadat následovně:
#!/usr/bin/env python from appJar import gui def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() def onButtonPress(buttonName): cells = app.getGridSelectedCells("grid") selectedCells = [c for c, v in cells.items() if v] selectedCells.sort() print(selectedCells) if selectedCells: message = " ".join(selectedCells) app.infoBox("Selected cells", message) else: app.warningBox("Warning", "Please select at least one cell") def onAddRow(data): if data == "newRow": values = app.getGridEntries("grid") print(values) if values[0] and values[1]: app.addGridRow("grid", values) app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) table = [["Name", "Work hours"], ["Petr", 160], ["Pavel", 90], ["Honza", 120]] app.addGrid("grid", table, action=onAddRow, addRow=True) app.addButton("Show selected cells", onButtonPress) app.go()
11. Kombinace předchozích dvou příkladů – rozšíření tabulky a výpočty nad daty v řádku
Předchozí dva demonstrační příklady používaly shodnou callback funkci, ovšem v prvním příkladu byla tato funkce zavolána ve chvíli, kdy se měly zobrazit informace o odpracovaných hodinách zvoleného zaměstnance, zatímco v příkladu druhém sloužila tatáž callback funkce pro přidání nového řádku do tabulky. Pokud budeme chtít využít obě operace – výpočet i přidání nového řádku, lze postupovat například takto:
def onAddRow(data): if data == "newRow": addNewRow(data) else: showWorkHours(data)
Obrázek 10: Vyplnění dat pro nový řádek tabulky.
Tato podmínka bude fungovat vždy, a to i ve chvíli, kdyby tabulka měla jen jeden sloupec a uživatel zadal do vstupního pole taktéž řetězec „newRow“. V takovém případě by se callback funkci předal seznam s jediným prvkem. Další zpracování již známe:
def showWorkHours(values): name = values[0] hours = int(values[1]) norm = 160 utilization = 100.0 * hours / norm message = "{h} of {n} hours: {u:.0f} %".format(h=hours, n=norm, u=utilization) app.infoBox("Work hours for " + name, message) def addNewRow(values): values = app.getGridEntries("grid") print(values) if values[0] and values[1] and re.search('\d', values[1]): app.addGridRow("grid", values)
Obrázek 11: Výpočet procent z pracovní doby pro nový řádek tabulky.
Úplný kód tohoto příkladu vypadá následovně:
#!/usr/bin/env python from appJar import gui import re def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() def onButtonPress(buttonName): cells = app.getGridSelectedCells("grid") selectedCells = [c for c, v in cells.items() if v] selectedCells.sort() print(selectedCells) if selectedCells: message = " ".join(selectedCells) app.infoBox("Selected cells", message) else: app.warningBox("Warning", "Please select at least one cell") def showWorkHours(values): name = values[0] hours = int(values[1]) norm = 160 utilization = 100.0 * hours / norm message = "{h} of {n} hours: {u:.0f} %".format(h=hours, n=norm, u=utilization) app.infoBox("Work hours for " + name, message) def addNewRow(values): values = app.getGridEntries("grid") print(values) if values[0] and values[1] and re.search('\d', values[1]): app.addGridRow("grid", values) def onAddRow(data): if data == "newRow": addNewRow(data) else: showWorkHours(data) app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) table = [["Name", "Work hours"], ["Petr", 160], ["Pavel", 90], ["Honza", 120]] app.addGrid("grid", table, action=onAddRow, addRow=True) app.addButton("Show selected cells", onButtonPress) app.go()
12. Ovládací prvek tree – strom zobrazující strukturu XML
Další ovládací prvek, s nímž se v dnešním článku seznámíme, se jmenuje tree, takže už jeho název napovídá, že se jedná o widget určený pro zobrazení stromové struktury. Teoreticky by se mělo jednat o nejsložitější ovládací prvek vůbec (viz například poměrně těžko ovladatelný widget JTree z knihovny Swing pro Javu). Ve skutečnosti je však práce s widgetem tree velmi jednoduchá, protože zdrojem dat je prakticky jakýkoli řetězec obsahující validní XML. Takový řetězec může být zapsán přímo v programu, nebo ho je možné načíst z externího souboru, získat z webové služby apod. Navíc ovládací prvek tree umožňuje editaci listů stromu, tj. koncových uzlů. Pokud jsou například zdrojová data reprezentována tímto XML:
<html> <head> <title>Titulek</title> </head> <body> <div>První odstavec</div> <div>Druhý odstavec</div> <div>Třetí odstavec</div> </body> </html>
vytvoří se strom, jehož kořen se bude jmenovat html, bude obsahovat dva poduzly head a body atd. Listy stromu pak budou obsahovat titulek a texty zapsané do odstavců:
Obrázek 12: Strom vytvořený z předchozího XML.
13. Zobrazení jednoduchého stromu
Zkusme si nyní podobný strom zobrazit v naší aplikaci. Nejdříve nadeklarujeme řetězec s XML:
treeContent = """ <html> <head> <title>Titulek</title> </head> <body> <div>První odstavec</div> <div>Druhý odstavec</div> <div>Třetí odstavec</div> </body> </html> """
Obrázek 13: Zabalený strom.
Následně z těchto dat vytvoříme strom a zakážeme editaci jeho listů:
app.addTree("tree", treeContent) app.setTreeEditable("tree", False)
A to je vše…
Obrázek 14: Strom po rozbalení některých uzlů.
Další příklad po svém spuštění zobrazí strom a taktéž menu s příkazem pro ukončení aplikace:
#!/usr/bin/env python from appJar import gui import re from appJar import gui app = gui() treeContent = """ <html> <head> <title>Titulek</title> </head> <body> <div>První odstavec</div> <div>Druhý odstavec</div> <div>Třetí odstavec</div> </body> </html> """ def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addTree("tree", treeContent) app.setTreeEditable("tree", False) app.go()
14. Konfigurace barev použitých při vykreslení stromu
Knihovna appJar nabízí několik metod určených pro konfiguraci barev použitých při vykreslení stromu. Nastavit je možné barvu textu uzlů, pozadí celého stromu, barvu vybraného uzlu a taktéž barvu pozadí vybraného uzlu. Všechny čtyři zmíněné barvy lze – v tomto pořadí – specifikovat v metodě setTreeColours:
app.setTreeColours("tree", "black", "#aaffaa", "red", "yellow")
Obrázek 15: Strom se změněnou barvou pozadí.
Alternativně je možné barvy nastavit postupně jednou z těchto metod:
Metoda | Význam |
---|---|
setTreeFg | barva nevybraných uzlů |
setTreeBg | pozadí stromu |
setTreeHighlightFg | barva textu vybraného uzlu |
setTreeHighlightBg | pozadí vybraného uzlu |
Obrázek 16: Změnila se i barva textu a pozadí vybraného uzlu.
Upravme si nyní náš demonstrační příklad takovým způsobem, aby pozadí stromu bylo světle zelené a vybrané uzly měly červený text a žluté pozadí:
#!/usr/bin/env python from appJar import gui import re from appJar import gui app = gui() treeContent = """ <html> <head> <title>Titulek</title> </head> <body> <div>První odstavec</div> <div>Druhý odstavec</div> <div>Třetí odstavec</div> </body> </html> """ def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addTree("tree", treeContent) app.setTreeEditable("tree", False) app.setTreeColours("tree", "black", "#aaffaa", "red", "yellow") app.go()
15. Strom s editovatelnými koncovými uzly
Pokud je zapotřebí zařídit, aby mohl uživatel editovat koncové uzly stromu (listy), postačuje zavolat následující metodu:
app.setTreeEditable("tree", True)
Poznámka: ve skutečnosti je strom editovatelný už ve výchozím nastavení, uzly je možné editovat po dvojkliku.
Obrázek 17: Editace uzlu stromu.
Úprava programu je zcela triviální (viz zvýrazněný příkaz):
#!/usr/bin/env python from appJar import gui import re from appJar import gui app = gui() treeContent = """ <html> <head> <title>Titulek</title> </head> <body> <h1>Kapitola</h1> <div>První odstavec</div> <div>Druhý odstavec</div> <div>Třetí odstavec</div> </body> </html> """ def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addTree("tree", treeContent) app.setTreeEditable("tree", True) app.go()
Obrázek 18: Strom po editaci.
16. Získání obsahu vybraného uzlu
V dnešním posledním demonstračním příkladu si ukážeme, jakým způsobem se získá obsah (text) vybraného uzlu. K tomu poslouží metoda nazvaná getTreeSelected, které se pouze předá jednoznačný identifikátor widgetu:
item = app.getTreeSelected("tree")
Tuto metodu použijeme v callback funkci zavolané po stisku tlačítka přidaného do okna aplikace. V callback funkci jen zkontrolujeme, jestli je vůbec nějaký uzel stromu vybrán:
def onButtonPress(buttonName): item = app.getTreeSelected("tree") if item is None: app.warningBox("Warning", "No item (node) selected") else: app.infoBox("Selected item", item)
Registrace callback funkce se provádí nám již známým konstruktorem tlačítka:
app.addButton("Show selected item", onButtonPress)
Obrázek 19: Zobrazení obsahu vybraného uzlu stromu.
Opět následuje výpis zdrojového kódu celého příkladu:
#!/usr/bin/env python from appJar import gui import re from appJar import gui app = gui() treeContent = """ <html> <head> <title>Titulek</title> </head> <body> <h1>Kapitola</h1> <div>První odstavec</div> <div>Druhý odstavec</div> <div>Třetí odstavec</div> </body> </html> """ def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() def onButtonPress(buttonName): item = app.getTreeSelected("tree") if item is None: app.warningBox("Warning", "No item (node) selected") else: app.infoBox("Selected item", item) app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addTree("tree", treeContent) app.setTreeEditable("tree", True) app.addButton("Show selected item", onButtonPress) app.go()
17. Repositář s demonstračními příklady
Zdrojové kódy všech čtrnácti dnes popsaných demonstračních příkladů byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. Pokud nechcete klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
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 o instalaci jsme si řekli v úvodním článku.
18. 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/ - The Java™ Tutorials: How to Use Trees
https://docs.oracle.com/javase/tutorial/uiswing/components/tree.html - Tree (data structure)
https://en.wikipedia.org/wiki/Tree_%28data_structure%29 - 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/