Tvorba grafického uživatelského rozhraní v Pythonu s využitím frameworku PySide

28. 11. 2017
Doba čtení: 25 minut

Sdílet

V seriálu o tvorbě aplikací s využitím jazyka Python postoupíme k další GUI knihovně. Tentokrát se začneme zabývat praktickým použitím PySide, jenž zprostředkovává propojení Pythonu s multiplatformním frameworkem Qt.

Obsah

1. Tvorba grafického uživatelského rozhraní v Pythonu s využitím frameworku PySide

2. PyQt vs. 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)

13. Okno s tlačítkem

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

18. Odkazy na Internetu

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:

  1. Qt.PlainText
  2. Qt.RichText
  3. 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):

ict ve školství 24

# 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/pre­sentations. 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

  1. PySide documentation
    https://srinikom.github.io/pyside-docs/index.html
  2. Differences Between PySide and PyQt
    https://wiki.qt.io/Differen­ces_Between_PySide_and_PyQt
  3. PySide tutorials
    https://srinikom.github.io/pyside-docs/tutorials/index.html
  4. PySide tutorial
    http://zetcode.com/gui/py­sidetutorial/
  5. Signals & Slots
    http://doc.qt.io/qt-4.8/signalsandslots.html
  6. Signals and Slots in PySide
    http://wiki.qt.io/Signals_an­d_Slots_in_PySide
  7. Intro to PySide/PyQt: Basic Widgets and Hello, World!
    http://www.pythoncentral.io/intro-to-pysidepyqt-basic-widgets-and-hello-world/
  8. QWidget
    https://srinikom.github.io/pyside-docs/PySide/QtGui/QWidget.html
  9. Leo editor
    http://leoeditor.com/
  10. IPython Qt Console aneb vylepšený pseudoterminál
    https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-ipython-a-ipython-notebook/#k06
  11. Vývojová prostředí ve Fedoře (4. díl)
    https://mojefedora.cz/vyvojova-prostredi-ve-fedore-4-dil/
  12. Seriál Letní škola programovacího jazyka Logo
    http://www.root.cz/serialy/letni-skola-programovaciho-jazyka-logo/
  13. Educational programming language
    http://en.wikipedia.org/wi­ki/Educational_programmin­g_language
  14. Logo Tree Project:
    http://www.elica.net/downlo­ad/papers/LogoTreeProject­.pdf
  15. Hra Breakout napísaná v Tkinteri
    https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/
  16. Hra Snake naprogramovaná v Pythone s pomocou Tkinter
    https://www.root.cz/clanky/hra-snake-naprogramovana-v-pythone-s-pomocou-tkinter/
  17. 24.1. turtle — Turtle graphics
    https://docs.python.org/3­.5/library/turtle.html#mo­dule-turtle
  18. TkDND
    http://freecode.com/projects/tkdnd
  19. Python Tkinter Fonts
    https://www.tutorialspoin­t.com/python/tk_fonts.htm
  20. The Tkinter Canvas Widget
    http://effbot.org/tkinter­book/canvas.htm
  21. Ovládací prvek (Wikipedia)
    https://cs.wikipedia.org/wi­ki/Ovl%C3%A1dac%C3%AD_prvek_­%28po%C4%8D%C3%ADta%C4%8D%29
  22. Rezervovaná klíčová slova v Pythonu
    https://docs.python.org/3/re­ference/lexical_analysis.html#ke­ywords
  23. TkDocs: Styles and Themes
    http://www.tkdocs.com/tuto­rial/styles.html
  24. Drawing in Tkinter
    http://zetcode.com/gui/tkin­ter/drawing/
  25. Changing ttk widget text color (StackOverflow)
    https://stackoverflow.com/qu­estions/16240477/changing-ttk-widget-text-color
  26. The Hitchhiker's Guide to Pyhton: GUI Applications
    http://docs.python-guide.org/en/latest/scenarios/gui/
  27. 7 Top Python GUI Frameworks for 2017
    http://insights.dice.com/2014/11/26/5-top-python-guis-for-2015/
  28. GUI Programming in Python
    https://wiki.python.org/mo­in/GuiProgramming
  29. Cameron Laird's personal notes on Python GUIs
    http://phaseit.net/claird/com­p.lang.python/python_GUI.html
  30. Python GUI development
    http://pythoncentral.io/introduction-python-gui-development/
  31. Graphic User Interface FAQ
    https://docs.python.org/2/faq/gu­i.html#graphic-user-interface-faq
  32. TkInter
    https://wiki.python.org/moin/TkInter
  33. Tkinter 8.5 reference: a GUI for Python
    http://infohost.nmt.edu/tcc/hel­p/pubs/tkinter/web/index.html
  34. TkInter (Wikipedia)
    https://en.wikipedia.org/wiki/Tkinter
  35. appJar
    http://appjar.info/
  36. appJar (Wikipedia)
    https://en.wikipedia.org/wiki/AppJar
  37. appJar na Pythonhosted
    http://pythonhosted.org/appJar/
  38. appJar widgets
    http://appjar.info/pythonWidgets/
  39. Stránky projektu PyGTK
    http://www.pygtk.org/
  40. PyGTK (Wikipedia)
    https://cs.wikipedia.org/wiki/PyGTK
  41. Stránky projektu PyGObject
    https://wiki.gnome.org/Pro­jects/PyGObject
  42. Stránky projektu Kivy
    https://kivy.org/#home
  43. Stránky projektu PyQt
    https://riverbankcomputin­g.com/software/pyqt/intro
  44. PyQt (Wikipedia)
    https://cs.wikipedia.org/wiki/PyGTK
  45. Stránky projektu PySide
    https://wiki.qt.io/PySide
  46. PySide (Wikipedia)
    https://en.wikipedia.org/wiki/PySide
  47. Stránky projektu Kivy
    https://kivy.org/#home
  48. Kivy (framework, Wikipedia)
    https://en.wikipedia.org/wi­ki/Kivy_(framework)
  49. QML Applications
    http://doc.qt.io/qt-5/qmlapplications.html
  50. KDE
    https://www.kde.org/
  51. Qt
    https://www.qt.io/
  52. GNOME
    https://en.wikipedia.org/wiki/GNOME
  53. Category:Software that uses PyGTK
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_PyGTK
  54. Category:Software that uses PyGObject
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_PyGObject
  55. Category:Software that uses wxWidgets
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_wxWidgets
  56. GIO
    https://developer.gnome.or­g/gio/stable/
  57. GStreamer
    https://gstreamer.freedesktop.org/
  58. GStreamer (Wikipedia)
    https://en.wikipedia.org/wi­ki/GStreamer
  59. Wax Gui Toolkit
    https://wiki.python.org/moin/Wax
  60. Python Imaging Library (PIL)
    http://infohost.nmt.edu/tcc/hel­p/pubs/pil/
  61. 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/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.