Obsah
1. Složitější ovládací prvky ve frameworku PySide: tabulky a stromy
2. Ovládací prvek typu QTableWidget aneb konfigurovatelná tabulka
3. První demonstrační příklad: jednoduchá tabulka s rozměry 5×5 buněk
4. Druhý demonstrační příklad: alternativní nastavení rozměrů tabulky
5. Programová a manuální modifikace buněk tabulky
6. Bublinová nápověda přiřazená k jednotlivým buňkám tabulky
7. Třetí demonstrační příklad: nastavení obsahu jednotlivých buněk tabulky
8. Reakce na události vznikající při práci s tabulkou
9. Čtvrtý demonstrační příklad: reakce na základní typy událostí
10. Zpracování událostí při kliknutí na titulky sloupců nebo řádků
11. Pátý demonstrační příklad: reakce na kliknutí na titulky sloupců nebo řádků
12. Ovládací prvek typu QTreeWidget aneb strom
13. Vytvoření prázdného stromu a specifikace popisu sloupců
14. Vložení prvků typu QTreeWidgetItem do stromu
15. Šestý demonstrační příklad: strom, jehož prvky mají pouze jednu hodnotu
16. Obsah následující části seriálu
17. Repositář s demonstračními příklady
1. Složitější ovládací prvky ve frameworku PySide: tabulky a stromy
Kromě již popsaných relativně jednoduchých ovládacích prvků nabízí framework PySide programátorům (a nepřímo i uživatelům) několik složitějších widgetů. V první řadě se jedná o widget QTextEdit, s nímž jsme se seznámili minule a k jehož podrobnějšímu popisu se v tomto seriálu ještě vrátíme. Ovšem existují i další složitější a přitom velmi užitečné a často využívané ovládací prvky.

Obrázek 1: S widgetem zobrazujícím tabulku jsme se již setkali při popisu knihovny appJar.
Mezi ně patří především tabulka a strom. Tabulku je možné vytvořit s využitím ovládacího prvku nazvaného QTableWidget (popř. odvozením nové třídy od QTableWidget), strom je představován prvkem pojmenovaným QTreeWidget. Dnes si ukážeme základy použití těchto dvou typů widgetů, ovšem pro jejich plné využití je nutné použít model MVC (model-view-controller), v němž tabulka či strom slouží jako pohled na data.
Obrázek 2: Knihovna appJar obsahuje i podporu pro widget zobrazující stromovou datovou strukturu, i když se nejedná o tak dobře konfigurovatelný ovládací prvek, jako je tomu v případě frameworku PySide a widgetu QTreeWidget.
2. Ovládací prvek typu QTableWidget aneb konfigurovatelná tabulka
Prvním widgetem, s nímž se v dnešním článku setkáme, je ovládací prvek určený pro zobrazení tabulky s měnitelným obsahem. Tento ovládací prvek se jmenuje příhodně QTableWidget. Ve výchozím nastavení je zobrazena tabulka obsahující titulky sloupců a řádků, což vlastně není nic nového, protože stejnou funkcionalitu nabízí všechny tabulkové procesory. A podobně jako u tabulkových procesorů je možné myší (nebo i programově) vybírat jednotlivé buňky, vybírat celé sloupce nebo řádky, editovat obsah buněk, měnit šířku řádků a výšku sloupců apod.

Obrázek 3: Změna šířky sloupce a výšky řádku.
Tabulka navíc ve chvíli, kdy se její obsah nevejde do vybrané plochy okna (či jiného kontejneru), automaticky zobrazí scrollbary, což je užitečné, protože se o tuto funkcionalitu nemusí starat programátor. Kromě toho se při práci s tabulkou může generovat množství různých signálů, na které lze programově reagovat a například setřídit tabulku podle toho sloupce, na který uživatel klikl.

Obrázek 4: Tabulka se zobrazenými scrollbary.
3. První demonstrační příklad: jednoduchá tabulka s rozměry 5×5 buněk
V dnešním prvním demonstračním příkladu je ukázáno, jakým způsobem je možné vytvořit jednoduchou tabulku a následně ji vložit do okna aplikace. Samotné vytvoření tabulky je triviální, protože se o vše podstatné postará konstruktor QTableWidget volaný z metody prepareTable. Tomuto konstruktoru předáme rozměry tabulky a taktéž referenci na kontejner, do kterého má být tabulka vložena:
def prepareTable(self): # vytvoření tabulky return QtGui.QTableWidget(5, 5, self)
Umístění tabulky do okna zajistí nám již známý správce geometrie:
table = self.prepareTable() quitButton = self.prepareQuitButton() # vytvoření správce geometrie layout = QtGui.QVBoxLayout() # umístění widgetů do okna layout.addWidget(table) layout.addWidget(quitButton) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout)
Výsledkem by mělo být toto okno:

Obrázek 5: Screenshot prvního demonstračního příkladu.
Následuje výpis zdrojového kódu prvního demonstračního příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # nový widget bude odvozen od obecného widgetu class MainWindowContent(QtGui.QWidget): def __init__(self): # zavoláme konstruktor předka super(MainWindowContent, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): # vytvoření widgetů vkládaných do okna table = self.prepareTable() quitButton = self.prepareQuitButton() # vytvoření správce geometrie layout = QtGui.QVBoxLayout() # umístění widgetů do okna layout.addWidget(table) layout.addWidget(quitButton) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) def prepareQuitButton(self): # tlačítko quitButton = QtGui.QPushButton('Quit', self) quitButton.resize(quitButton.sizeHint()) # navázání akce na signál quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit) return quitButton def prepareTable(self): # vytvoření tabulky return QtGui.QTableWidget(5, 5, self) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): self.resize(450, 300) self.setWindowTitle("QTableWidget") # vložení komponenty do okna self.setCentralWidget(MainWindowContent()) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
4. Druhý demonstrační příklad: alternativní nastavení rozměrů tabulky
Druhý demonstrační příklad si popíšeme pouze velmi stručně, protože se od příkladu prvního odlišuje jen v tom, jakým způsobem se specifikují rozměry tabulky. Namísto volání konstruktoru s předáním rozměrů tabulky:
table = QtGui.QTableWidget(5, 5, self)
se nejprve tabulka vytvoří a teprve poté se explicitně nastaví počet řádků a sloupců:
table = QtGui.QTableWidget(self) table.setColumnCount(5) table.setRowCount(5)
Výsledek se nijak neliší od příkladu prvního:

Obrázek 6: Screenshot druhého demonstračního příkladu.
Opět následuje výpis zdrojového kódu tohoto příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # nový widget bude odvozen od obecného widgetu class MainWindowContent(QtGui.QWidget): def __init__(self): # zavoláme konstruktor předka super(MainWindowContent, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): # vytvoření widgetů vkládaných do okna table = self.prepareTable() quitButton = self.prepareQuitButton() # vytvoření správce geometrie layout = QtGui.QVBoxLayout() # umístění widgetů do okna layout.addWidget(table) layout.addWidget(quitButton) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) def prepareQuitButton(self): # tlačítko quitButton = QtGui.QPushButton('Quit', self) quitButton.resize(quitButton.sizeHint()) # navázání akce na signál quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit) return quitButton def prepareTable(self): # vytvoření tabulky table = QtGui.QTableWidget(self) table.setColumnCount(5) table.setRowCount(5) return table # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): self.resize(450, 300) self.setWindowTitle("QTableWidget") # vložení komponenty do okna self.setCentralWidget(MainWindowContent()) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
5. Programová a manuální modifikace buněk tabulky
Tabulka vytvořená s využitím frameworku PySide není v žádném případě pouze statickým prvkem grafického uživatelského rozhraní. Je tomu právě naopak, protože jednotlivé buňky tabulky je možné modifikovat, a to jak uživatelem, tak i programově. Modifikace prováděná uživatelem se používají snadno: dvojklikem na buňku, výběrem buňky a stiskem klávesy F2, popř. (což je nejjednodušší řešení) výběrem buňky následovaným libovolnou sekvencí alfanumerických znaků, které se ihned začnou do vybrané části tabulky vkládat.
Programová změna tabulky je založena na použití metody QTableWidget.setItem(). Této metodě je nutné předat index řádku a index sloupce (indexy se začínají počítat nuly) a taktéž referenci na objekt typu QTableWidgetItem. V nejjednodušším případě se tento objekt vytvoří konstruktorem, kterému se předá řetězec, jenž se má v tabulce zobrazit:
item = QtGui.QTableWidgetItem("pokus") table.setItem(j, i, item)
6. Bublinová nápověda přiřazená k jednotlivým buňkám tabulky
Při přesunu kurzoru myši nad tabulku mohou jednotlivé buňky tabulky zobrazit bublinovou nápovědu. Ta se ve skutečnosti nastavuje velmi jednoduše metodou QTreeWidgetItem.setToolTip(). Podívejme se na příklad, který vše osvětlí:
item = QtGui.QTableWidgetItem("pokus") item.setToolTip("toto je nápověda k buňce 'pokus'") table.setItem(j, i, item)
Bublinovou nápovědu použijeme i ve třetím demonstračním příkladu, který je popsán v navazující kapitole.

Obrázek 7: Způsob zobrazení bublinové nápovědy.
7. Třetí demonstrační příklad: nastavení obsahu jednotlivých buněk tabulky
Výše popsaná metoda QTableWidget.setItem() je použita ve třetím příkladu pro vytvoření tabulky o rozměrech 10×10 buněk. Do této tabulky jsou programově vypsány hodnoty malé násobilky. Navíc se u každé hodnoty nastaví bublinová nápověda zmíněná v předchozí kapitole:
def prepareTable(self): # vytvoření tabulky table = QtGui.QTableWidget(self) table.setColumnCount(10) table.setRowCount(10) # naplnění tabulky for j in range(1, 11): for i in range(1, 11): item = QtGui.QTableWidgetItem(str(i*j)) tooltip = u"výsledek součinu {x}×{y}".format(x=i, y=j) item.setToolTip(tooltip) table.setItem(j-1, i-1, item) return table

Obrázek 8: Screenshot třetího demonstračního příkladu.
Opět se podívejme na výpis zdrojového kódu dnešního třetího demonstračního příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # nový widget bude odvozen od obecného widgetu class MainWindowContent(QtGui.QWidget): def __init__(self): # zavoláme konstruktor předka super(MainWindowContent, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): # vytvoření widgetů vkládaných do okna table = self.prepareTable() quitButton = self.prepareQuitButton() # vytvoření správce geometrie layout = QtGui.QVBoxLayout() # umístění widgetů do okna layout.addWidget(table) layout.addWidget(quitButton) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) def prepareQuitButton(self): # tlačítko quitButton = QtGui.QPushButton('Quit', self) quitButton.resize(quitButton.sizeHint()) # navázání akce na signál quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit) return quitButton def prepareTable(self): # vytvoření tabulky table = QtGui.QTableWidget(self) table.setColumnCount(10) table.setRowCount(10) # naplnění tabulky for j in range(1, 11): for i in range(1, 11): item = QtGui.QTableWidgetItem(str(i*j)) tooltip = u"výsledek součinu {x}×{y}".format(x=i, y=j) item.setToolTip(tooltip) table.setItem(j-1, i-1, item) return table # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): self.resize(450, 300) self.setWindowTitle("QTableWidget") # vložení komponenty do okna self.setCentralWidget(MainWindowContent()) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
8. Reakce na události vznikající při práci s tabulkou
Již ve druhé kapitole jsme si řekli, že se při práci s tabulkou generuje poměrně velké množství signálů, které je možné navázat na callback funkce či metody. Ve chvíli, kdy dojde k události reprezentované daným signálem, bude příslušná callback funkce/metoda zavolána a programátor tak může adekvátně zareagovat na akce prováděné uživatelem. Podívejme se na nejjednodušší a možná i nejčastěji používanou událost – výběr buňky v tabulce. Příslušný signál spojíme s callback metodou takto:
table.cellClicked.connect(self.onCellClicked)
Callback metoda musí v tomto případě akceptovat tři argumenty:
- Referenci na objekt (self, this). Kdyby se nejednalo o metodu, tento objekt by se nepoužil.
- Index řádku, v němž se nachází vybraná buňka.
- Index sloupce, v němž se nachází vybraná buňka.
Callback metoda tedy může vypadat takto:
def onCellClicked(self, row, column): message = u"kliknuto na buňku [{x}, {y}]".format(x=column, y=row) self.textEdit.appendPlainText(message)
Další typ události je nepatrně složitější. Tato událost vznikne ve chvíli, když se změní fokus vybrané buňky (ne její obsah, ale výběr):
table.currentCellChanged.connect(self.onCurrentCellChanged)
V tomto případě musí callback metoda akceptovat jak souřadnice původní buňky, tak i souřadnice nově vybrané (fokusované) buňky:
def onCurrentCellChanged(self, row1, column1, row2, column2): message = u"změna fokusu z buňky [{x2}, {y2}] na buňku [{x1}, {y1}]".format( x1=column1, y1=row1, x2=column2, y2=row2) self.textEdit.appendPlainText(message)
Mohlo by se zdát, že není nutné mít zaregistrované oba typy událostí, ovšem ve skutečnosti nemusí nastat situace, kdy při akcích prováděných uživatelem dojde k oběma typům událostí. Ostatně se podívejte na následující dvojici screenshotů:

Obrázek 9: Při výběru jednotlivých buněk se generují oba typy událostí.

Obrázek 10: Při výběru celých sloupců se generuje jen jeden typ události.
Další často používanou událostí je událost, která nastane ve chvíli, kdy uživatel změní obsah nějaké buňky tabulky:
table.cellChanged.connect(self.onCellChanged)
Handler této události musí (nezávisle na tom, zda se jedná o metodu či o obyčejnou funkci) akceptovat mj. i souřadnice modifikované buňky. Její nový obsah se získá snadno metodou QTableWidget.item(řádek, sloupec):
def onCellChanged(self, row, column): value = self.table.item(row, column).text() message = u"změna obsahu buňky [{x}, {y}] na hodnotu {v}".format( x=column, y=row, v=value) self.textEdit.appendPlainText(message)
9. Čtvrtý demonstrační příklad: reakce na základní typy událostí
V dnešním čtvrtém demonstračním příkladu je realizována programová reakce na základní typy událostí, které mohou při práci s tabulkou nastat. Pro všechny tři základní události, tj. pro signály cellClicked, currentCellChanged a cellChanged jsou zaregistrovány příslušné handlery, které po svém zavolání vypíšou informace do několikařádkového textového widgetu. Ten je vytvořen jednoduše (viz též předchozí část tohoto seriálu):
def prepareTextEdit(self): # víceřádkové vstupní textové pole textEdit = QtGui.QPlainTextEdit(self) textEdit.setReadOnly(True) return textEdit
Poznámka: vzhledem k tomu, že potřebujeme čistý textový výstup (bez dalšího formátování), je použit widget QPlainTextEdit a nikoli QTextEdit.

Obrázek 11: Screenshot čtvrtého demonstračního příkladu.
Opět následuje výpis zdrojového kódu tohoto demonstračního příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # nový widget bude odvozen od obecného widgetu class MainWindowContent(QtGui.QWidget): def __init__(self): # zavoláme konstruktor předka super(MainWindowContent, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): # vytvoření widgetů vkládaných do okna self.table = self.prepareTable() self.textEdit = self.prepareTextEdit() quitButton = self.prepareQuitButton() # vytvoření správce geometrie layout = QtGui.QVBoxLayout() # umístění widgetů do okna layout.addWidget(self.table) layout.addWidget(self.textEdit) layout.addWidget(quitButton) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) def prepareQuitButton(self): # tlačítko quitButton = QtGui.QPushButton('Quit', self) quitButton.resize(quitButton.sizeHint()) # navázání akce na signál quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit) return quitButton def prepareTextEdit(self): # víceřádkové vstupní textové pole textEdit = QtGui.QPlainTextEdit(self) textEdit.setReadOnly(True) return textEdit def prepareTable(self): # vytvoření tabulky table = QtGui.QTableWidget(self) table.setColumnCount(10) table.setRowCount(10) # naplnění tabulky for j in range(1, 11): for i in range(1, 11): item = QtGui.QTableWidgetItem(str(i*j)) tooltip = u"výsledek součinu {x}×{y}".format(x=i, y=j) item.setToolTip(tooltip) table.setItem(j-1, i-1, item) # registrace callback funkcí table.cellClicked.connect(self.onCellClicked) table.currentCellChanged.connect(self.onCurrentCellChanged) table.cellChanged.connect(self.onCellChanged) return table def onCellClicked(self, row, column): message = u"kliknuto na buňku [{x}, {y}]".format(x=column, y=row) self.textEdit.appendPlainText(message) def onCurrentCellChanged(self, row1, column1, row2, column2): message = u"změna fokusu z buňky [{x2}, {y2}] na buňku [{x1}, {y1}]".format( x1=column1, y1=row1, x2=column2, y2=row2) self.textEdit.appendPlainText(message) def onCellChanged(self, row, column): value = self.table.item(row, column).text() message = u"změna obsahu buňky [{x}, {y}] na hodnotu {v}".format( x=column, y=row, v=value) self.textEdit.appendPlainText(message) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): self.resize(450, 450) self.setWindowTitle("QTableWidget") # vložení komponenty do okna self.setCentralWidget(MainWindowContent()) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
10. Zpracování událostí při kliknutí na titulky sloupců nebo řádků
Poměrně často se setkáme s nutností seřazení tabulky podle určitého sloupce. Tuto operaci je možné implementovat relativně jednoduše – postačí jen zareagovat na kliknutí na titulek daného sloupce. Nejprve tedy propojíme příslušný signál s handlerem:
table.horizontalHeader().sectionClicked.connect(self.onHorizontalHeaderClicked)
A následně můžeme napsat tělo handleru:
def onHorizontalHeaderClicked(self, c): message = u"vybrán sloupec {c}".format(c=c) self.textEdit.appendPlainText(message)
Podobným způsobem je možné reagovat na kliknutí na titulek jednotlivých řádků:
table.verticalHeader().sectionClicked.connect(self.onVerticalHeaderClicked)
Opět se podívejme, jak může vypadat tělo handleru této události:
def onVerticalHeaderClicked(self, r): message = u"vybrán řádek {r}".format(r=r) self.textEdit.appendPlainText(message)
11. Pátý demonstrační příklad: reakce na kliknutí na titulky sloupců nebo řádků
Pátý příklad vznikl rozšířením příkladu čtvrtého o dvě další callback funkce, které se zavolají ve chvíli, kdy uživatel klikne na titulek sloupce, popř. na titulek řádku. Callback funkce pouze vypíšou zprávu o výběru, ovšem v praxi by například mohlo dojít k seřazení dat podle vybraného sloupce atd.:

Obrázek 12: Výpis informace ve chvíli, kdy je vybrán sloupec, popř. řádek kliknutím na titulek.
Podívejme se na výpis zdrojového kódu dnešního pátého demonstračního příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # nový widget bude odvozen od obecného widgetu class MainWindowContent(QtGui.QWidget): def __init__(self): # zavoláme konstruktor předka super(MainWindowContent, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): # vytvoření widgetů vkládaných do okna self.table = self.prepareTable() self.textEdit = self.prepareTextEdit() quitButton = self.prepareQuitButton() # vytvoření správce geometrie layout = QtGui.QVBoxLayout() # umístění widgetů do okna layout.addWidget(self.table) layout.addWidget(self.textEdit) layout.addWidget(quitButton) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) def prepareQuitButton(self): # tlačítko quitButton = QtGui.QPushButton('Quit', self) quitButton.resize(quitButton.sizeHint()) # navázání akce na signál quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit) return quitButton def prepareTextEdit(self): # víceřádkové vstupní textové pole textEdit = QtGui.QPlainTextEdit(self) textEdit.setReadOnly(True) return textEdit def prepareTable(self): # vytvoření tabulky table = QtGui.QTableWidget(self) table.setColumnCount(10) table.setRowCount(10) # naplnění tabulky for j in range(1, 11): for i in range(1, 11): item = QtGui.QTableWidgetItem(str(i*j)) tooltip = u"výsledek součinu {x}×{y}".format(x=i, y=j) item.setToolTip(tooltip) table.setItem(j-1, i-1, item) # registrace callback funkcí table.cellClicked.connect(self.onCellClicked) table.currentCellChanged.connect(self.onCurrentCellChanged) table.cellChanged.connect(self.onCellChanged) # callback funkce při výběru sloupců nebo celých řádků table.horizontalHeader().sectionClicked.connect(self.onHorizontalHeaderClicked) table.verticalHeader().sectionClicked.connect(self.onVerticalHeaderClicked) return table def onCellClicked(self, row, column): message = u"kliknuto na buňku [{x}, {y}]".format(x=column, y=row) self.textEdit.appendPlainText(message) def onCurrentCellChanged(self, row1, column1, row2, column2): message = u"změna fokusu z buňky [{x2}, {y2}] na buňku [{x1}, {y1}]".format( x1=column1, y1=row1, x2=column2, y2=row2) self.textEdit.appendPlainText(message) def onCellChanged(self, row, column): value = self.table.item(row, column).text() message = u"změna obsahu buňky [{x}, {y}] na hodnotu {v}".format( x=column, y=row, v=value) self.textEdit.appendPlainText(message) def onHorizontalHeaderClicked(self, c): message = u"vybrán sloupec {c}".format(c=c) self.textEdit.appendPlainText(message) def onVerticalHeaderClicked(self, r): message = u"vybrán řádek {r}".format(r=r) self.textEdit.appendPlainText(message) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): self.resize(450, 450) self.setWindowTitle("QTableWidget") # vložení komponenty do okna self.setCentralWidget(MainWindowContent()) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
12. Ovládací prvek typu QTreeWidget aneb strom
Druhým ovládacím prvkem, s nímž se v dnešním článku alespoň ve stručnosti seznámíme, je strom. Ten je představovaný třídou QTreeWidget nebo jejími potomky. S ovládacím prvkem, který dokáže zobrazit stromovou strukturu, jsme se již setkali při popisu knihovny appJar, v níž ovšem bylo možné použít jen jediný typ zdrojových dat – soubor ve formátu XML. Naproti tomu je widget typu QTreeWidget obecnější, neboť se v něm stromová struktura skládá z prvků typu QTreeWidgetItem, které opět mohou obsahovat další prvky téhož typu. V nejjednodušším případě jsou prvky představovány jednořádkovým textem, ovšem je možné použít i ikony popř. složitější struktury (například lze zobrazit několik sloupců s hodnotami).

Obrázek 13: Strom vytvořený z XML v knihovně appJar.
13. Vytvoření prázdného stromu a specifikace popisu sloupců
Vytvoření prázdného stromu je ještě jednodušší, než vytvoření prázdné tabulky, protože není nutné specifikovat žádné rozměry:
tree = QtGui.QTreeWidget(self)

Obrázek 14: Výchozí podoba stromu není příliš pěkná.
Implicitně se i v případě stromu zobrazuje titulkový pruh, který můžeme snadno upravit. Navíc můžeme (resp. většinou musíme) specifikovat počet sloupců. Následující dva příklady nastaví titulek zobrazený na pruhu a současně se specifikuje, že se má použít jen jediný sloupec:
tree.setHeaderLabel("strom") tree.setColumnCount(1)

Obrázek 15: Nastavení nadpisu jediného sloupce.
14. Vložení prvků typu QTreeWidgetItem do stromu
Prvky vkládané do stromu jsou typu QTreeWidgetItem. V nejjednodušším případě mohou jednotlivé prvky reprezentovat pouhý text, i tak ale tento text musíme předávat jako jednoprvkový seznam, protože jsme si již v předchozí kapitole řekli, že se prvky stromu mohou zobrazit v několika sloupcích. Prvním parametrem konstruktoru je předek daného prvku:
items = [] for i in range(1, 11): item = QtGui.QTreeWidgetItem(None, ["prvek #{i}".format(i=i)]) items.append(item) tree.insertTopLevelItems(0, items)

Obrázek 16: Strom s několika prvky (uzly) na nejvyšší úrovni.
Relativně snadno můžeme vytvořit skutečnou stromovou strukturu, v níž je každý prvek na nejvyšší úrovni uzlem obsahujícím (například) tři další poduzly:
# vytvoření stromu tree = QtGui.QTreeWidget(self) tree.setHeaderLabel("strom") tree.setColumnCount(1) items = [] for i in range(1, 11): item = QtGui.QTreeWidgetItem(None, ["prvek #{i}".format(i=i)]) items.append(item) QtGui.QTreeWidgetItem(item, ["podprvek A"]) QtGui.QTreeWidgetItem(item, ["podprvek B"]) QtGui.QTreeWidgetItem(item, ["podprvek C"]) tree.insertTopLevelItems(0, items)

Obrázek 17: Strom s uzly, z nichž každý obsahuje tři poduzly.
15. Šestý demonstrační příklad: strom, jehož prvky mají pouze jednu hodnotu
Předchozí úryvek kódu je použit v dnešním šestém a sedmém příkladu. Tyto příklady se od sebe liší jen jednou maličkostí – zda se pro každý uzel na nejvyšší úrovni vytváří i poduzly, či nikoli:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # nový widget bude odvozen od obecného widgetu class MainWindowContent(QtGui.QWidget): def __init__(self): # zavoláme konstruktor předka super(MainWindowContent, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): # vytvoření widgetů vkládaných do okna self.tree = self.prepareTree() quitButton = self.prepareQuitButton() # vytvoření správce geometrie layout = QtGui.QVBoxLayout() # umístění widgetů do okna layout.addWidget(self.tree) layout.addWidget(quitButton) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) def prepareQuitButton(self): # tlačítko quitButton = QtGui.QPushButton('Quit', self) quitButton.resize(quitButton.sizeHint()) # navázání akce na signál quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit) return quitButton def prepareTree(self): # vytvoření stromu tree = QtGui.QTreeWidget(self) tree.setHeaderLabel("strom") tree.setColumnCount(1) items = [] for i in range(1, 11): item = QtGui.QTreeWidgetItem(None, ["prvek #{i}".format(i=i)]) items.append(item) tree.insertTopLevelItems(0, items) return tree # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): self.resize(450, 450) self.setWindowTitle("QTreeWidget") # vložení komponenty do okna self.setCentralWidget(MainWindowContent()) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
16. Obsah následující části seriálu
V navazující části seriálu o tvorbě aplikací s grafickým uživatelským rozhraním v Pythonu se zaměříme na podrobnější popis tří složitějších widgetů, s nimiž jsme se setkali minule a dnes:
- QTextEdit
- QTableWidget
- QTreeWidget
Nejvíce času budeme věnovat použití ovládacího prvku zobrazujícího stromové struktury, protože se tento prvek používá poměrně často a současně je příprava dat pro něj poněkud složitější.
17. Repositář s demonstračními příklady
Zdrojové kódy všech sedmi dnes popsaných demonstračních příkladů byly opět, podobně jako tomu bylo i v předchozích článcích, 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:
Příklad | Adresa |
---|---|
100_qtable_widget.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/100_qtable_widget.py |
101_qtable_widget_setsize.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/101_qtable_widget_setsize.py |
102_qtable_widget_setitem.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/102_qtable_widget_setitem.py |
103_qtable_widget_signals.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/103_qtable_widget_signals.py |
104_qtable_widget_select_column_row.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/104_qtable_widget_select_column_row.py |
105_qtree_widget.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/105_qtree_widget.py |
106_qtree_widget_structure.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/106_qtree_widget_structure.py |
18. Odkazy na Internetu
- PySide 1.2.1 documentation
https://pyside.github.io/docs/pyside/index.html - QImage
https://pyside.github.io/docs/pyside/PySide/QtGui/QImage.html - QPixmap
https://pyside.github.io/docs/pyside/PySide/QtGui/QPixmap.html - QBitmap
https://pyside.github.io/docs/pyside/PySide/QtGui/QBitmap.html - QPaintDevice
https://pyside.github.io/docs/pyside/PySide/QtGui/QPaintDevice.html - QPicture
https://pyside.github.io/docs/pyside/PySide/QtGui/QPicture.html - QPainter
https://pyside.github.io/docs/pyside/PySide/QtGui/QPainter.html - QPainterPath
https://pyside.github.io/docs/pyside/PySide/QtGui/QPainterPath.html - QGradient
https://pyside.github.io/docs/pyside/PySide/QtGui/QGradient.html - QLinearGradient
https://pyside.github.io/docs/pyside/PySide/QtGui/QLinearGradient.html - QRadialGradient
https://pyside.github.io/docs/pyside/PySide/QtGui/QRadialGradient.html - QTableWidget
https://pyside.github.io/docs/pyside/PySide/QtGui/QTableWidget.html - QTableWidgetItem
https://pyside.github.io/docs/pyside/PySide/QtGui/QTableWidgetItem.html - QTreeWidget
https://pyside.github.io/docs/pyside/PySide/QtGui/QTreeWidget.html - QTreeWidgetItem
https://pyside.github.io/docs/pyside/PySide/QtGui/QTreeWidgetItem.html - Afinní zobrazení
https://cs.wikipedia.org/wiki/Afinn%C3%AD_zobrazen%C3%AD - Differences Between PySide and PyQt
https://wiki.qt.io/Differences_Between_PySide_and_PyQt - PySide 1.2.1 tutorials
https://pyside.github.io/docs/pyside/tutorials/index.html - PySide tutorial
http://zetcode.com/gui/pysidetutorial/ - Drawing in PySide
http://zetcode.com/gui/pysidetutorial/drawing/ - Qt Core
https://pyside.github.io/docs/pyside/PySide/QtCore/Qt.html - QLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QLayout.html - QValidator
https://pyside.github.io/docs/pyside/PySide/QtGui/QValidator.html - QStackedLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QStackedLayout.html - QFormLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QFormLayout.html - QBoxLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QBoxLayout.html - QHBoxLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QHBoxLayout.html - QVBoxLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QVBoxLayout.html - QGridLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QGridLayout.html - QAction
https://pyside.github.io/docs/pyside/PySide/QtGui/QAction.html - QMessageBox
https://pyside.github.io/docs/pyside/PySide/QtGui/QMessageBox.html - QListWidget
https://pyside.github.io/docs/pyside/PySide/QtGui/QListWidget.html - Signals & Slots
http://doc.qt.io/qt-4.8/signalsandslots.html - Signals and Slots in PySide
http://wiki.qt.io/Signals_and_Slots_in_PySide - Intro to PySide/PyQt: Basic Widgets and Hello, World!
http://www.pythoncentral.io/intro-to-pysidepyqt-basic-widgets-and-hello-world/ - QLineEdit
https://pyside.github.io/docs/pyside/PySide/QtGui/QLineEdit.html - QTextEdit
https://pyside.github.io/docs/pyside/PySide/QtGui/QTextEdit.html - QValidator
https://pyside.github.io/docs/pyside/PySide/QtGui/QValidator.html - QIntValidator
https://pyside.github.io/docs/pyside/PySide/QtGui/QIntValidator.html - QRegExpValidator
https://pyside.github.io/docs/pyside/PySide/QtGui/QRegExpValidator.html - QWidget
https://pyside.github.io/docs/pyside/PySide/QtGui/QWidget.html - QMainWindow
https://pyside.github.io/docs/pyside/PySide/QtGui/QMainWindow.html - QLabel
https://pyside.github.io/docs/pyside/PySide/QtGui/QLabel.html - QAbstractButton
https://pyside.github.io/docs/pyside/PySide/QtGui/QAbstractButton.html - QCheckBox
https://pyside.github.io/docs/pyside/PySide/QtGui/QCheckBox.html - QRadioButton
https://pyside.github.io/docs/pyside/PySide/QtGui/QRadioButton.html - QButtonGroup
https://pyside.github.io/docs/pyside/PySide/QtGui/QButtonGroup.html - QFrame
https://pyside.github.io/docs/pyside/PySide/QtGui/QFrame.html#PySide.QtGui.PySide.QtGui.QFrame - QFrame.frameStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QFrame.html#PySide.QtGui.PySide.QtGui.QFrame.frameStyle - Leo editor
http://leoeditor.com/ - IPython Qt Console aneb vylepšený pseudoterminál
https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-ipython-a-ipython-notebook/#k06 - Vývojová prostředí ve Fedoře (4. díl)
https://mojefedora.cz/vyvojova-prostredi-ve-fedore-4-dil/ - Seriál Letní škola programovacího jazyka Logo
http://www.root.cz/serialy/letni-skola-programovaciho-jazyka-logo/ - Educational programming language
http://en.wikipedia.org/wiki/Educational_programming_language - Logo Tree Project:
http://www.elica.net/download/papers/LogoTreeProject.pdf - 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/ - 24.1. turtle — Turtle graphics
https://docs.python.org/3.5/library/turtle.html#module-turtle - 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/