Obsah
1. Tvorba grafického uživatelského rozhraní v Pythonu s využitím frameworku PySide
3. Moduly frameworku Qt dostupné přes PySide vývojářům používajícím Python
4. Základní widgety a jejich hierarchie
5. Nejjednodušší aplikace používající PySide: prázdné okno
6. Zjednodušený import modulu QtGui
7. Kdy přesně se vlastně zobrazí hlavní okno aplikace?
8. Nastavení titulku a rozměrů okna
9. Okno založené na komponentě QLabel a nikoli na QWidget
10. Odvození vlastní třídy z komponenty QLabel
11. Styl textu aneb použití vybraných HTML značek
12. Složitější okno ve funkci kontejneru pro další widget(y)
14. Uzavření aplikace po stisku tlačítka – použití metody QCoreApplication.quit
15. Přidání nápovědy (tooltip) k tlačítku
16. Signály a události: reakce na stisk tlačítka podruhé
17. Repositář s demonstračními příklady
1. Tvorba grafického uživatelského rozhraní v Pythonu s využitím frameworku PySide
V předchozích sedmnácti částech seriálu o tvorbě aplikací s grafickým uživatelským rozhraním v Pythonu jsme si nejprve popsali knihovnu Tkinter a následně pak i knihovnu nazvanou appJar, která je na Tkinteru založena. Samozřejmě se však nejedná o jediné knihovny, které je možné pro tvorbu grafického uživatelského rozhraní využít. Velmi často se například setkáme s aplikacemi psanými v Pythonu, které používají vazbu na populární framework Qt. Přitom existuje hned několik možností, jak propojit Python a Qt. Základ tvoří knihovny PyQt (s duální licencí GNU GPL v3 a Riverbank Commercial License) a PySide (s licencí LGPL) pro Qt ≤ 4.8, další možností je právě vyvíjená PySide2 určená pro Qt 5.x.
Obrázek 1: Příkladem aplikace postavené na PyQt je integrované vývojové prostředí Eric.
Podobně jako knihovna PyGTK, která vývojářům zajišťuje rozhraní mezi programovacím jazykem Python a knihovnou GTK+, je tomu podobně i u knihovny nazvané PyQt, samozřejmě ovšem s tím „nepatrným“ rozdílem, že PyQt představuje rozhraní pro Qt, která je používána (nejenom) v desktopovém prostředí KDE (ve skutečnosti se s Qt setkáme i iOS či Androidu). Ve skutečnosti však Qt není pouze knihovna určená pro tvorbu grafického uživatelského rozhraní, ale ucelený framework, což mj. znamená, že v PyQt mají vývojáři k dispozici rozhraní se zhruba 440 třídami a 6000 funkcemi i metodami, s jejichž využitím je možné vytvářet grafické uživatelské rozhraní (i s použitím deklarativního jazyka QML), používat známý widget QScintilla používaný v textových editorech a procesorech, pracovat s relačními databázemi, používat vektorový grafický formát SVG, pracovat se soubory XML apod.
Obrázek 2: GUI nabízené integrovaným vývojovým prostředím Eric.
2. PyQt vs. PySide
Kvůli problémům s duální licencí PyQt (resp. přesněji řečeno v nemožnosti využít LGPL či podobnou licenci vhodnější pro knihovny) vznikla na bázi PyQt knihovna PySide používající, jak jsme se již dozvěděli v první kapitole, licenci LGPL. Díky tomu lze PySide bez problémů použít jak v uzavřených komerčních aplikacích, tak i v open source programech. Další – tentokrát již technologické – rozdíly mezi PyQt a PySide naleznete na stránce Differences Between PySide and PyQt. Všechny další demonstrační příklady, které si postupně popíšeme, jsou otestovány vůči PySide; později si ukážeme možnost přechodu na PySide2.
Obrázek 3: Poměrně dobrým příkladem aplikace, která využívá možnosti komponenty QScintilla, je outline editor Leo (screenshot byl získán z domovské stránky tohoto programu).
Před otestováním příkladů si nainstalujte metabalíček, který je obvykle nazvaný python-pyside popř. python3-pyside (pozor na to, že v některých distribucích je první balíček určen pro Python 3 a druhý tím pádem neexistuje, v dalších distribucích naopak zatím neexistuje balíček pro Python 3 apod.).
Obrázek 4: Informace o metabalíčku python-pyside v Linux Mintu.
3. Moduly frameworku Qt dostupné přes PySide vývojářům používajícím Python
V následující tabulce jsou vypsány moduly frameworku Qt, které jsou díky PySide dostupné i vývojářům používajícím programovací jazyk Python:
Modul | Stručný popis |
---|---|
QtCore | kromě dalšího i implementace smyčky událostí a mechanizmu signálů a slotů (používáno především v GUI) |
QtGui | většina tříd používaných v GUI, kromě tříd zmíněných výše |
QtMultimedia | nízkoúrovňové multimediální funkce |
QtOpenGL | rozhraní k OpenGL |
QtSvg | podpora pro zobrazení SVG (vektorová grafika) |
Phonon | multimediální funkce (audio, video) |
QtUiTools | zpracování formulářů a dialogů vytvořených Qt Designerem |
QtDeclarative | umožňuje deklarativní tvorbu UI |
QtHelp | integrace nápovědy do aplikací |
QtNetwork | tvorba klientů a serverů komunikujících přes TCP/IP |
QtSql | rozhraní k (relačním) databázím |
QtXml | zpracování XML dokumentů (proudové i založené na DOM) |
QtWebkit | známý engine pro použití (nejenom) ve webových prohlížečích |
QtScript | společně s dalším modulem umožňuje tvorbu skriptovatelných aplikací |
QtScriptTools | společně s předchozím modulem umožňuje tvorbu skriptovatelných aplikací |
Na tomto místě je vhodné poznamenat, že nativní programové rozhraní (API) modulů frameworku Qt je pro účely PySide upraveno, takže se zjednodušila například práce se signály atd. Podrobnosti o této problematice se samozřejmě ve správném okamžiku :-) dozvíme. Na druhou stranu nejsou změny tak velké, aby se při studiu jednotlivých modulů nebylo možné odkazovat na původní dokumentaci ke Qt.
Obrázek 5: Další příklad vhodné kombinace frameworku Qt a programovacího jazyka Python – Qt Console aneb vylepšený interaktivní terminál s možnostmi grafického výstupu (a mnoha dalšími vylepšeními).
4. Základní widgety a jejich hierarchie
Modul QtGui je založen na základní komponentě grafického uživatelského rozhraní, která se jmenuje jednoduše QWidget. Tato komponenta může vystupovat skutečně v roli ovládacího prvku vloženého do nějakého okna či dialogu, nebo je možné tuto komponentu využít jako vlastní okno, tj. jako kontejner pro další widgety. QWidget také může přijímat a zpracovávat události od uživatele (myš, klávesnice) či od správce oken. Samotný QWidget má jen minimalistický vzhled – jedná se totiž o obdélníkovou plochu s konfigurovatelným pozadím. Od QWidgetu se odvozují další – specializovanější – ovládací prvky, zejména pak:
Komponenta | Stručný popis |
---|---|
QFrame | komponenta s volitelným okrajem, odvozují se od ní další widgety |
QLabel | textové či grafické (obrázek, video) návěští |
QPushButton | klasické tlačítko |
QToolButton | tlačítko obvykle umisťované na toolbar |
QRadioButton | přepínací tlačítko |
QCheckBox | zaškrtávací tlačítko |
QListView | seznam prvků |
QComboBox | kombo box (vstupní prvek + seznam prvků) |
QLineEdit | vstup jednoho řádku textu |
QPlainTextEdit | jednoduché vstupní víceřádkové textové pole |
QTextEdit | vstupní textové pole (umožňuje práci s tabulkami, obrázky atd.) |
QTextBrowser | komponenta s textem, který může obsahovat hypertextové odkazy |
QSvgWidget | komponenta pro zobrazení SVG (vektorová grafika) |
QGLWidget | komponenta pro zobrazení 2D/3D výstupu generovaného přes OpenGL |
Tyto komponenty si postupně popíšeme a samozřejmě nezapomeneme ani na komponenty složitější (hotové dialogy, zobrazení stromové struktury atd.).
Obrázek 6: Další příklad možností Qt Console (pro IPython).
5. Nejjednodušší aplikace používající PySide: prázdné okno
Podívejme se nyní na způsob implementace té nejjednodušší aplikace s grafickým uživatelským rozhraním. Tato aplikace je tvořena jediným oknem, které je odvozeno od obecného widgetu QWidget. Tento ovládací prvek nemá žádného předka, tudíž je zobrazen přímo na obrazovce a nikoli v jiném widgetu:
# konstrukce obecného widgetu bez předka window = PySide.QtGui.QWidget() # zobrazení widgetu na obrazovce window.show()
Povšimněte si především toho, že je zapotřebí vytvořit takzvaný aplikační objekt a poté zajistit vstup do smyčky událostí zavoláním metody exec_ tohoto objektu:
# konstrukce výchozího tzv. aplikačního objektu app = PySide.QtGui.QApplication(sys.argv) ... ... ... # vstup do smyčky událostí (event loop) app.exec_()
Typicky bývá volání app.exec_() poslední explicitně volanou funkcí v aplikaci; zbylá funkcionalita je řešena přes smyčku událostí (navíc se na některých systémech řízení z této funkce vůbec nemusí vrátit zpět do volajícího kódu!).
První demonstrační příklad používající PySide může vypadat následovně:
#!/usr/bin/env python # vim: set fileencoding=utf-8 # prozatím budeme využívat jen modul QtGui import PySide.QtGui # konstrukce výchozího tzv. aplikačního objektu app = PySide.QtGui.QApplication(sys.argv) # konstrukce obecného widgetu bez předka window = PySide.QtGui.QWidget() # zobrazení widgetu na obrazovce window.show() # vstup do smyčky událostí (event loop) app.exec_()
Obrázek 7: Poněkud nudné GUI naší první aplikace.
Poznámka: ve skutečnosti se tímto způsobem složitější aplikace nepíšou. Lepší je využít principů OOP, o čemž se zmíníme v navazujících kapitolách.
Zbývá nám ještě odpovědět na otázku, proč se vlastně metoda exec_ zapisuje s podtržítkem. V Pythonu 2.x (ale už ne ve verzi 3.x) je totiž slovo exec (bez podtržítka) rezervovaným klíčovým slovem, o čemž se lze snadno přesvědčit:
$ python Python 2.7.6 (default, Oct 26 2016, 20:30:19) [GCC 4.8.4] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import keyword >>> keyword.kwlist ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
V Pythonu 3 získáme nepatrně odlišný seznam, nyní již bez exec:
$ python3 Python 3.4.3 (default, Nov 17 2016, 01:08:31) [GCC 4.8.4] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import keyword >>> keyword.kwlist ['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
6. Zjednodušený import modulu QtGui
Předchozí demonstrační příklad ještě nepatrně upravíme. Především se namísto importu ve tvaru:
import PySide.QtGui
použije následující zápis zajišťující, že nebudeme muset explicitně zapisovat jmenný prostor u objektů typu QApplication, QWidget atd. (někteří programátoři tento způsob preferují, ovšem při větším množství importovaných modulů je to spíše na obtíž):
from PySide.QtGui import *
Dále ještě zajistíme explicitní ukončení a předání návratové hodnoty z aplikace, samozřejmě ovšem jen na těch systémech, v nichž skutečně dochází k návratu z metody exec_:
sys.exit(app.exec_())
Upravená druhá verze příkladu vypadá následovně:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # prozatím budeme využívat jen modul QtGui from PySide.QtGui import * # konstrukce výchozího tzv. aplikačního objektu app = QApplication(sys.argv) # konstrukce obecného widgetu bez předka window = QWidget() # zobrazení widgetu na obrazovce window.show() # vstup do smyčky událostí (event loop) sys.exit(app.exec_())
7. Kdy přesně se vlastně zobrazí hlavní okno aplikace?
V předchozích dvou variantách demonstračního příkladu jsme volali metodu pojmenovanou window.show pro zobrazení widgetu představujícího hlavní okno aplikace na obrazovce. Ve skutečnosti však widget není ihned po zavolání této metody zobrazen, protože aplikace ještě nevstoupila do smyčky obsluhy událostí (event loop), v níž se interně zpracuje událost se žádostí o překreslení widgetu. O tom, že je toto tvrzení pravdivé, se můžeme přesvědčit velmi snadno, a to konkrétně vložením viditelného zpoždění mezi volání window.show a app.exec_:
# zobrazení widgetu na obrazovce window.show() print('after window.show()') sleep(5) # vstup do smyčky událostí (event loop) app.exec_()
Funkce zajišťující kýžené (pětisekundové) zpoždění může vypadat například takto:
def sleep(seconds): print('sleeping') for i in range(seconds, 0, -1): print(i) time.sleep(1) print('waking up')
Po spuštění příkladu se nejdříve spustí odpočítávání a až po přibližně pěti sekundách je okno aplikace zobrazeno, i když metoda window.show byla zavolána před funkcí sleep:
Obrázek 8: Okno se (až na titulek) nijak neodlišuje od okna vytvořeného prvním demonstračním příkladem.
Zdrojový kód tohoto příkladu se změnil takto:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys import time # prozatím budeme využívat jen modul QtGui from PySide.QtGui import * def sleep(seconds): print('sleeping') for i in range(seconds, 0, -1): print(i) time.sleep(1) print('waking up') # konstrukce výchozího tzv. aplikačního objektu app = QApplication(sys.argv) # konstrukce obecného widgetu bez předka window = QWidget() print('before window.show()') # zobrazení widgetu na obrazovce window.show() print('after window.show()') sleep(5) # vstup do smyčky událostí (event loop) app.exec_()
8. Nastavení titulku a rozměrů okna
Widgety nabízené modulem QtGui je možné různým způsobem modifikovat, ať již se to týká jejich vzhledu či chování. Prozatím si ukážeme jen základní modifikaci spočívající v nastavení titulku okna přes vlastnost (property) setWindowTitle a změnu rozměrů okna metodou resize. Použití je snadné, minimálně ve chvíli, kdy měníme vlastnosti widgetů ještě před vstupem do smyčky obsluhy událostí:
# nastavení titulku window.setWindowTitle('Hello world!') # změna rozměrů okna window.resize(400, 300)
Obrázek 9: Na tomto screenshotu můžeme vidět, že se okno aplikace skutečně zmenšilo a má korektně nastavený titulek.
Upravený zdrojový kód demonstračního příkladu nyní bude vypadat následovně:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # prozatím budeme využívat jen modul QtGui from PySide.QtGui import * # konstrukce výchozího tzv. aplikačního objektu app = QApplication(sys.argv) # konstrukce obecného widgetu bez předka window = QWidget() # nastavení titulku a rozměrů okna window.setWindowTitle('Hello world!') window.resize(400, 300) # zobrazení widgetu na obrazovce window.show() # vstup do smyčky událostí (event loop) app.exec_()
9. Okno založené na komponentě QLabel a nikoli na QWidget
Pro vytvoření hlavního okna aplikace nemusíme použít jen obecný widget představovaný instancí třídy QWidget. Můžeme totiž využít i některého potomka této třídy, například QLabel. Ten může být pro první kroky s PySide užitečný, protože příslušný widget dokáže na své ploše zobrazit zvolený text, který navíc může mít uživatelsky nastavený styl (to ale zdaleka není vše, protože tento widget zobrazuje i rastrový obrázek nebo video). Zkusme si tedy předchozí příklady upravit takovým způsobem, aby byl ovládací prvek QWidget nahrazen za QLabel. Navíc využijeme faktu, že při konstrukci návěští můžeme nově vzniklému widgetu předat text, který má být tímto ovládacím prvkem zobrazen:
# konstrukce návěští bez předka label = QLabel('Hello world!') # zobrazení widgetu na obrazovce label.show()
Obrázek 10: Velikost okna se zmenší, protože okno bude nastaveno na vypočtenou velikost návěští.
Ostatní části příkladu se vlastně nebudou odlišovat od příkladu druhého:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # prozatím budeme využívat jen modul QtGui from PySide.QtGui import * # konstrukce výchozího tzv. aplikačního objektu app = QApplication(sys.argv) # konstrukce návěští bez předka label = QLabel('Hello world!') # zobrazení widgetu na obrazovce label.show() # vstup do smyčky událostí (event loop) sys.exit(app.exec_())
Zajímavé bude zjistit, do jaké míry se tento zdrojový kód bude podobat aplikaci s GUI naprogramované s využitím již popsané knihovny appJar. Zkusme si příklady nepatrně upravit (na stejný počet řádků) a porovnat v textovém editoru:
Obrázek 11: Porovnání podobných aplikací – jedna používá knihovnu appJar, druhá PySide (pro lepší čitelnost na obrázek klikněte, aby se zvětšil na původní velikost).
10. Odvození vlastní třídy z komponenty QLabel
Všechny předchozí demonstrační příklady byly schválně vytvořeny co nejjednodušším způsobem, takže se vlastně jednalo o pouhou sérii volání několika konstruktorů a metod. Ovšem při tvorbě rozsáhlejších aplikací je nutné aplikovat jiný styl programování. Ten je ukázán v dalším demonstračním příkladu, v němž je deklarována třída HelloWorldLabel, jejímž předkem je třída QLabel. Tím vlastně říkáme, že rozšiřujeme a také specializujeme možnosti původního obecného návěští. V první verzi příkladu voláme konstruktor předka „starým“ způsobem, s nímž se však ještě můžeme v praxi setkat. Novější způsob již využívá super:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # prozatím budeme využívat jen modul QtGui from PySide.QtGui import * # konstrukce výchozího tzv. aplikačního objektu app = QApplication(sys.argv) # nový widget bude odvozen od standardního návěští class HelloWorldLabel(QLabel): def __init__(self): # zavoláme konstruktor předka (nedoporučovaná varianta) QLabel.__init__(self, "Hello world!") def run(self): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() HelloWorldLabel().run()
Obrázek 12: Po spuštění aplikace s výše zobrazeným zdrojovým kódem by se mělo ukázat toto malé okno.
Ještě lepší bude „schovat“ proměnnou s aplikačním objektem do funkce main, která se zavolá buď explicitně (v případě, že skript spouštíme přímo z×příkazového řádku) nebo nepřímo z libovolného jiného modulu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # prozatím budeme využívat jen modul QtGui from PySide import QtGui # nový widget bude odvozen od standardního návěští class HelloWorldLabel(QtGui.QLabel): def __init__(self): # zavoláme konstruktor předka # varianta pro Python 2.x i pro Python 3.x super(HelloWorldLabel, self).__init__("Hello world!") # varianta pro Python 3.x # super().__init__("Hello world!") 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) HelloWorldLabel().run(app) if __name__ == '__main__': main()
11. Styl textu aneb použití vybraných HTML značek
Návěští představované v knihovně PySide objektem typu QLabel je až pozoruhodně flexibilní, protože kromě obyčejného jednořádkového textu dokáže pracovat i s rastrovými obrázky, víceřádkovým textem, textem s formátováním atd. Ukažme si nyní způsob použití naformátovaného textu, který se v dokumentaci označuje termínem „rich text document“. Pro specifikaci formátu můžeme použít některé vybrané HTML značky, což je výhodné, protože tvůrci Qt/PySide nemuseli vymýšlet nový značkovací jazyk a programátoři se ho nemusí učit:
def __init__(self): # text pro návěští labelText = "normal text<br><b>bold</b><br><i>italic</i>" # zavoláme konstruktor předka # varianta pro Python 2.x i pro Python 3.x super(HelloWorldLabel, self).__init__(labelText) # varianta pro Python 3.x # super().__init__(labelText)
Obrázek 13: Návěští zobrazující třířádkový text s formátováním.
Jak ale vlastně návěští pozná, že má například namísto textu „<br>“ tento text interpretovat jako konec řádku? Konkrétní chování se řídí pomocí metody QLabel.setTextFormat, které se předá jedna z následujících konstant z výčtového typu QtCore.Qt.TextFormat:
- Qt.PlainText
- Qt.RichText
- Qt.AutoText
Implicitně je nastaven Qt.AutoText, který na základě návratové hodnoty metody Qt.mightBeRichText rozhodne, jak se má řetězec interpretovat. Samozřejmě můžete v případě potřeby pro jistotu nastavit přímo hodnotu Qt.RichText, což však v našem případě nebylo nutné.
Opět následuje výpis celého zdrojového kódu demonstračního příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # prozatím budeme využívat jen modul QtGui from PySide import QtGui # nový widget bude odvozen od standardního návěští class HelloWorldLabel(QtGui.QLabel): def __init__(self): # text pro návěští labelText = "normal text<br><b>bold</b><br><i>italic</i>" # zavoláme konstruktor předka # varianta pro Python 2.x i pro Python 3.x super(HelloWorldLabel, self).__init__(labelText) # varianta pro Python 3.x # super().__init__(labelText) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) sys.exit(app.exec_()) def main(): app = QtGui.QApplication(sys.argv) HelloWorldLabel().run(app) if __name__ == '__main__': main()
12. Složitější okno ve funkci kontejneru pro další widget(y)
Naši testovací aplikaci si dále upravíme, a to konkrétně takovým způsobem, že do hlavního okna (což byl prozatím náš jediný widget) přidáme další ovládací prvek. Je to jednoduché, protože okno slouží jako kontejner a při konstrukci návěští pouze musíme do konstruktoru předat referenci na tento kontejner. Podívejme se, jak se to provádí. Metoda prepareGUI je deklarována ve třídě HelloWorldLabel, takže referenci na kontejner máme k dispozici – je jím hodnota parametru self:
def prepareGUI(self): self.resize(320, 240) self.setWindowTitle("Window and label") # návěští label = QtGui.QLabel("Hello world!", self) # posun v rámci nadřazeného widgetu label.move(100, 100)
Návěští je v rámci svého kontejneru posunuto na souřadnice 100, 100. V další části tohoto seriálu si ukážeme, jak se dá tato operace provést mnohem lépe a s ohledem na různá rozlišení displejů.
Obrázek 14: Okno (kontejner) obsahující další widget, v tomto případě návěští.
Opět se podívejme na celý zdrojový kód příkladu, po jehož spuštění získáme okno z obrázku číslo 14:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # prozatím budeme využívat jen modul QtGui from PySide import QtGui # nový widget bude odvozen od obecného widgetu class MainWindow(QtGui.QWidget): 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(320, 240) self.setWindowTitle("Window and label") # návěští label = QtGui.QLabel("Hello world!", self) # posun v rámci nadřazeného widgetu label.move(100, 100) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) sys.exit(app.exec_()) def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
13. Okno s tlačítkem
Konečně se dostáváme k zajímavější oblasti, konkrétně k ovládacím prvkům, které jsou aktivní. Návěští je totiž, i přes svou flexibilitu, jen pasivním prvkem, který většinou nijak nereaguje na činnost uživatele. Nejjednodušším, ale velmi důležitým, aktivním widgetem jsou tlačítka, přičemž tlačítko se základním chováním je představováno objekty typu QPushButton. Upravme si tedy naši metodu prepareGUI tak, aby se do okna namísto návěští vložilo právě tlačítko. Povšimněte si, že tlačítku opět předáváme referenci na kontejner, do kterého má být umístěno. Ihned po vytvoření také změníme velikost tlačítka, ovšem nikam ho neposuneme, takže se tlačítko umístí do levého horního rohu svého kontejneru:
def prepareGUI(self): self.resize(320, 240) self.setWindowTitle("Simple Button") # tlačítko button = QtGui.QPushButton("Button", self) button.resize(button.sizeHint())
Obrázek 15: Okno (kontejner) obsahující další widget, v tomto případě tlačítko.
Kromě výše uvedené změny je zbytek zdrojového kódu shodný s předchozím příkladem:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # prozatím budeme využívat jen modul QtGui from PySide import QtGui # nový widget bude odvozen od obecného widgetu class MainWindow(QtGui.QWidget): 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(320, 240) self.setWindowTitle("Simple Button") # tlačítko button = QtGui.QPushButton("Button", self) button.resize(button.sizeHint()) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) sys.exit(app.exec_()) def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
14. Uzavření aplikace po stisku tlačítka – použití metody QCoreApplication.quit
Knihovna PySide samozřejmě podporuje i práci s takzvanými signály a sloty, což je koncept, kterému se budeme podrobněji věnovat příště. Dnes si pouze ukážeme, jakým způsobem je možné navázat akci typu „kliknutí na tlačítko“ se zavoláním nějaké funkce či metody. Následující příkaz zajistí, že se po kliknutí na jediné tlačítko v aplikaci zavolá metoda QCoreApplication.quit. Nejprve ovšem musíme získat referenci na objekt typu QtGui.QApplication:
# navázání akce na signál button.clicked.connect(QtCore.QCoreApplication.instance().quit)
Opět si ukažme, jak vypadá úplný spustitelný kód další varianty 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 MainWindow(QtGui.QWidget): 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(320, 240) self.setWindowTitle("Quit Button") # tlačítko button = QtGui.QPushButton("Quit", self) button.resize(button.sizeHint()) # navázání akce na signál button.clicked.connect(QtCore.QCoreApplication.instance().quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) sys.exit(app.exec_()) def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
Ve skutečnosti můžeme poněkud neobratné volání QtCore.QCoreApplication.instance() nahradit za app, ovšem to si vyžádá nepatrné úpravy kódu:
#!/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 MainWindow(QtGui.QWidget): def __init__(self, app): self.app = app # zavoláme konstruktor předka super(MainWindow, self).__init__() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareGUI(self): self.resize(320, 240) self.setWindowTitle("Quit Button") # tlačítko button = QtGui.QPushButton("Quit", self) button.resize(button.sizeHint()) # navázání akce na signál button.clicked.connect(self.app.quit) def run(self): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) sys.exit(self.app.exec_()) def main(): app = QtGui.QApplication(sys.argv) MainWindow(app).run() if __name__ == '__main__': main()
15. Přidání nápovědy (tooltip) k tlačítku
Velmi jednoduše (na rozdíl od knihovny Tkinter, kde je to o mnoho komplikovanější), také můžeme k tlačítku přidat bublinovou nápovědu (tooltip, hint) zobrazenou ve chvíli, kdy se nad tlačítkem na určitou (konfigurovatelnou dobu) zastaví kurzor myši. Podívejme se, jak to lze provést. Použijeme metodu QWidget.setToolTip:
# tlačítko button = QtGui.QPushButton("Quit", self) button.resize(button.sizeHint()) button.setToolTip("Immediately quit this application")
Obrázek 16: Nápověda k tlačítku zobrazená ve chvíli, kdy se nad ním zastaví kurzor myši.
Příklad se jen nepatrně prodlouží:
#!/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 MainWindow(QtGui.QWidget): 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(320, 240) self.setWindowTitle("Quit Button") # tlačítko button = QtGui.QPushButton("Quit", self) button.resize(button.sizeHint()) button.setToolTip("Immediately quit this application") # navázání akce na signál button.clicked.connect(QtCore.QCoreApplication.instance().quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) sys.exit(app.exec_()) def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
16. Signály a události: reakce na stisk tlačítka podruhé
Na nějakou událost je samozřejmě možné navázat i volání uživatelské funkce, například funkce, která aplikaci ihned (a nutno říci, že nepříliš čistě) uzavře:
# callback funkce def closeApplication(): print("Closing...") sys.exit(0)
Existují hned dva způsoby propojení události a callback funkce. Ten starší poměrně přesně reflektuje způsob registrace události pomocí metody QObject.connect tak, jak by mohl být naprogramován s využitím Qt a C++:
# starý způsob navázání signálu, který není příliš Python-friendly QtCore.QObject.connect(button, QtCore.SIGNAL ('clicked()'), closeApplication)
Novější způsob již nevyžaduje přesnou specifikaci typu signálu, protože je určeno přímo jeho jménem v deklaraci třídy QAbstractButton. Pro tuto třídu existují čtyři signály: pressed, released, clicked a toggled:
# navázání akce (callback funkce) na signál - zavolání běžné funkce button.clicked.connect(closeApplication)
Taktéž je možné po stisku tlačítka zavolat metodu; v tomto případě nezapomeňte na self.jméno_metody (pokud je metoda deklarována ve stejné třídě, jako samotné tlačítko):
# navázání akce (callback funkce) na signál - zavolání metody button.clicked.connect(self.quit)
Podrobnosti o konceptu signálů, tzv. slotů a s nimi související konfigurace aplikace, si řekneme příště.
17. Repositář s demonstračními příklady
Zdrojové kódy všech dvanácti dnes popsaných demonstračních příkladů byly opět 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:
18. Odkazy na Internetu
- PySide documentation
https://srinikom.github.io/pyside-docs/index.html - Differences Between PySide and PyQt
https://wiki.qt.io/Differences_Between_PySide_and_PyQt - PySide tutorials
https://srinikom.github.io/pyside-docs/tutorials/index.html - PySide tutorial
http://zetcode.com/gui/pysidetutorial/ - 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/ - QWidget
https://srinikom.github.io/pyside-docs/PySide/QtGui/QWidget.html - 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/