Obsah
1. Tvorba GUI v Pythonu s využitím frameworku PySide: signály a sloty, správci rozložení komponent
2. Navázání signálu s využitím funkce QtCore.QObject.connect
3. Navázání signálu s využitím metody QtGui.QAbstractButton.clicked
4. Použití dekorátoru u slotu, zavolání metody namísto funkce při příjmu signálu
5. Umístění většího množství tlačítek do okna
6. Absolutní pozicování ovládacích prvků
7. Správci rozložení ovládacích prvků v kontejneru
8. Správce rozložení QHBoxLayout
9. Ukázka použití správce rozložení QHBoxLayout
10. Vložení pružných mezer mezi ovládací prvky
11. Správce rozložení QVBoxLayout
12. Ukázka použití správce rozložení QVBoxLayout
13. Správce rozložení QGridLayout
14. Ukázka použití správce rozložení QGridLayout
15. Komponenty umístěné do mřížky přes větší množství řádků či sloupců
16. Horizontální a vertikální zarovnání komponent v mřížce
17. Repositář s demonstračními příklady
1. Tvorba GUI v Pythonu s využitím frameworku PySide: signály a sloty, správci rozložení komponent
V první části dnešního článku se budeme zabývat způsobem práce s takzvanými signály a sloty. V knihovně PySide může každý ovládací prvek (widget) generovat signály, a to konkrétně v případě vzniku nějaké události (tou může být například stisk tlačítka, změna pozice posuvníku, změna velikosti okna atd.) nebo změny stavu widgetu. Signály mohou být napojené na takzvané sloty, což je pojmenování pro funkce, které mohou reagovat na příchozí signál. V knihovně Qt, nad níž je PySide postaven, jsou signály zpracovávány nativními funkcemi, PySide ovšem celý koncept signálů a slotů dokáže „obalit“ takovým způsobem, že sloty jsou běžnými pythonovskými funkcemi či metodami. Musíme však explicitně specifikovat propojení mezi signálem (resp. jeho typem) a slotem. To lze zajistit dvěma způsoby popsanými v navazujících kapitolách.
Poznámka: ve skutečnosti se signály a sloty nepoužívají pouze pro práci s GUI, ale jedná se o univerzálnější koncept, který je v PySide používaný i pro další činnosti.
2. Navázání signálu s využitím funkce QtCore.QObject.connect
První způsob navázání signálu na slot používá funkci QtCore.QObject.connect. Této funkci se předávají tři parametry, které přesně specifikují propojení mezi signálem vyslaným určitým widgetem se slotem:
Parametr | Význam parametru |
---|---|
sender | widget, který bude signál vysílat (například tlačítko) |
signal | přesná specifikace typu signálu |
method | metoda nebo funkce, která bude zavolána při přijetí signálu |
Význam prvního i třetího parametru je zřejmý, ještě si však musíme vysvětlit, jakým způsobem se získá typ signálu. Pro tento účel se používá funkce QtCore.SIGNAL, která dokáže převést název signálu reprezentovaný textovým řetězcem do formátu akceptovatelného funkcí QtCore.QObject.connect. Pokud tedy budeme chtít, aby se po stisku tlačítka reprezentovatelného objektem button zavolala metoda closeApplication, provedeme to následovně:
QtCore.QObject.connect(button, QtCore.SIGNAL('clicked()'), closeApplication)
Nám již známý příklad z předchozího článku, v němž je vytvořeno okno s jediným tlačítkem, nyní upravíme tak, aby stisk tlačítka aplikaci ukončil. Nejdříve vytvoříme funkci, která bude reagovat na signál:
def closeApplication(): print("Closing...") sys.exit(0)
Dále vytvoříme tlačítko, nastavíme jeho vlastnosti a nakonfigurujeme příjemce signálu vyslaného po stisku tlačítka:
# tlačítko button = QtGui.QPushButton("Quit", self) button.resize(button.sizeHint()) button.setToolTip("Immediately quit this application") # starý způsob navázání signálu, který není příliš Python-friendly QtCore.QObject.connect(button, QtCore.SIGNAL ('clicked()'), closeApplication)
Obrázek 1: Tlačítko umístěné v oknu dnešního prvního demonstračního příkladu.
Úplný zdrojový kód příkladu bude vypadat následovně:
#!/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 # callback funkce def closeApplication(): print("Closing...") sys.exit(0) # 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") # starý způsob navázání signálu, který není příliš Python-friendly QtCore.QObject.connect(button, QtCore.SIGNAL ('clicked()'), closeApplication) 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()
3. Navázání signálu s využitím metody QtGui.QAbstractButton.clicked
Volání funkce QtCore.QObject.connect sice velmi přesně reflektuje způsob, jakým se signály a sloty konfigurují v knihovně Qt a v jazyku C++, ovšem v PySide můžeme použít i přímočařejší a z hlediska zápisu kratší způsob. Konkrétně u tlačítek, tedy u widgetů typu QPushButton, můžeme použít trojici metod pro registraci příjemce příslušného signálu:
Metoda | Stručný popis |
---|---|
QPushButton.clicked() | kliknutí na tlačítko či stisk mezerníku, pokud má tlačítko fokus |
QPushButton.pressed() | stisk tlačítka (používáno méně často) |
QPushButton.released() | puštění tlačítka (opět používáno méně často) |
Pokud konfigurujeme signál vyslaný po stisku widgetu představovaného objektem button, můžeme namísto volání:
QtCore.QObject.connect(button, QtCore.SIGNAL('clicked()'), closeApplication)
použít kratší a přehlednější zápis:
button.clicked.connect(closeApplication)
Opět se podívejme, jak bude příklad upraven. Jedná se vlastně o změnu jediného řádku:
#!/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 # callback funkce def closeApplication(): print("Closing...") sys.exit(0) # 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(closeApplication) 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. Použití dekorátoru u slotu, zavolání metody namísto funkce při příjmu signálu
Často se také setkáme s tím, že příjemce signálu, tj. funkce vystupující v roli slotu, je označen dekorátorem. Může to vypadat takto:
# callback funkce @QtCore.Slot() def closeApplication(): print("Closing...") sys.exit(0)
Zde sice není použití dekorátoru nutné, ovšem obecně jejich využití vede k menší spotřebě operační paměti a k nepatrně rychlejšímu běhu aplikace. V dalších příkladech prozatím tento dekorátor používat nebudeme, ale ještě se k jeho funkci vrátíme ve chvíli, kdy si budeme ukazovat způsob vytvoření vlastního nového typu signálu.
Dále si ukažme, že příjemcem signálu nemusí být jen funkce, ale i metoda, což je ostatně užitečnější. Namísto registrace funkce:
button.clicked.connect(closeApplication)
můžeme zaregistrovat metodu:
button.clicked.connect(self.quit)
Tato metoda musí mít hlavičku:
def quit(self):
Samozřejmě i zde lze přidat dekorátor:
@QtCore.Slot() def quit(self):
Pokud metoda nepřistupuje k atributům objektu, může být statická (nepředává se jí self):
button.clicked.connect(MainWindow.quit) @QtCore.Slot() @staticmethod def quit(): print("Closing...")
V dalším příkladu již používáme korektní způsob ukončení aplikace v metodě MainWindow.quit:
def quit(self): print("Closing...") self.close()
Úplný zdrojový kód tohoto příkladu vypadá následovně:
#!/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(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() @QtCore.Slot() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
5. Umístění většího množství tlačítek do okna
Ve druhé části dnešního článku se budeme zabývat tím, jak se vlastně jednotlivé ovládací prvky (widgety) umisťují do okna či do dalšího kontejneru. Tento problém je nutné vyřešit u všech aplikací, v nichž se nachází okna a dialogy s více než jedním widgetem. Abychom si ukázali, že se jedná o důležité téma, nebudeme se v dalším příkladu o umístění komponent do hlavního okna nijak starat a budeme jen pozorovat, jaké je implicitní chování. Vytvoříme pět tlačítek a umístíme je do hlavního okna:
# tlačítka button1 = QtGui.QPushButton("One", self) button2 = QtGui.QPushButton("Two", self) button3 = QtGui.QPushButton("Three", self) button4 = QtGui.QPushButton("Four", self) button5 = QtGui.QPushButton("Five", self)
Výsledek po spuštění bude vypadat následovně – všechna tlačítka jsou zobrazena na stejném místě, konkrétně v levém horním rohu hlavního okna:
Obrázek 2: Všechna tlačítka se vměstnala na stejné místo v hlavním oknu, konkrétně do levého horního rohu.
Zdrojový kód příkladu, v němž se do hlavního okna vykreslí pět tlačítek (na stejném místě) vypadá takto:
#!/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čítka button1 = QtGui.QPushButton("One", self) button2 = QtGui.QPushButton("Two", self) button3 = QtGui.QPushButton("Three", self) button4 = QtGui.QPushButton("Four", self) button5 = QtGui.QPushButton("Five", self) # navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
Mimochodem: povšimněte si, že všechna tlačítka mají signály navázané na shodnou callback funkci:
# navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit)
6. Absolutní pozicování ovládacích prvků
Jednou z možností rozmístění ovládacích prvků (widgetů) na ploše okna je použití tzv. absolutního pozicování. To jistě znají pamětníci RAD nástrojů typu Delphi či Visual Basic. Při použití absolutního pozicování se explicitně specifikují pozice a rozměry každého widgetu, nezávisle na widgetech ostatních a taktéž nezávisle na rozlišení, globálního nastavení velikosti fontů, rozměrů okna či dialogu atd. Tento způsob rozmístění widgetů může být dosti problematický zejména ve chvíli, kdy je aplikace provozována na počítači s jinak nastaveným prostředím (stačí se jen přepnout do režimu pro slabozraké atd.). Ovšem pro úplnost si ukažme, jak je možné absolutního pozicování docílit. Není to nic složitého, protože každý widget podporuje metodu move, které se předává dvojice souřadnic [x,y] platných v rámci lokálního souřadného systému okna.
Ukažme si nyní, jak se přesunou čtyři tlačítka z naší pětice tak, aby si navzájem nepřekážela. Je to snadné:
# přesun tlačítek na absolutní pozice button2.move(30, 30) button3.move(60, 60) button4.move(90, 90) button5.move(120, 120)
Výsledek naší snahy je vidět na dalším screenshotu:
Obrázek 3: Čtyři tlačítka (z pěti) byla přemístěna na absolutní souřadnice.
Opět si ukažme úplný zdrojový kód takto upraveného demonstračního příkladu s vyznačením provedených změn:
#!/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čítka button1 = QtGui.QPushButton("One", self) button2 = QtGui.QPushButton("Two", self) button3 = QtGui.QPushButton("Three", self) button4 = QtGui.QPushButton("Four", self) button5 = QtGui.QPushButton("Five", self) # přesun tlačítek na absolutní pozice button2.move(30, 30) button3.move(60, 60) button4.move(90, 90) button5.move(120, 120) # navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
7. Správci rozložení ovládacích prvků v kontejneru
U většiny aplikací je vhodnější namísto absolutního pozicování jednotlivých ovládacích prvků použít již připravené správce rozložení (layout manager), kteří se sami starají o umístění komponent na základě jejich vzájemných vztahů. Při použití knihovny PySide si můžeme vybrat hned z několika typů správců, popř. lze správce kombinovat, a to díky tomu, že do okna je možné vložit další komponentu ve funkci kontejneru pro další komponenty. Seznam správců rozložení podporovaných knihovnou PySide naleznete v další tabulce:
Správce rozložení |
---|
QHBoxLayout |
QVBoxLayout |
QGridLayout |
QStackedLayout |
QFormLayout |
V dalších kapitolách si popíšeme první tři správce, tj. QHBoxLayout, QVBoxLayout a QGridLayout.
8. Správce rozložení QHBoxLayout
Velmi jednoduchým správcem rozložení komponent v okně/dialogu je QHBoxLayout, který zajišťuje, že se jednotlivé ovládací prvky uspořádají horizontálně za sebe do jedné řady (v našich podmínkách zleva doprava). Komponenty se při použití tohoto správce přidávají metodou addWidget, které se v nejjednodušším případě pouze předá reference na přidávaný widget. Podívejme se nyní na způsob použití tohoto správce.
Nejprve vytvoříme novou instanci správce QHBoxLayout:
layout = QtGui.QHBoxLayout()
Následně již můžeme začít přidávat jednotlivé widgety, přičemž první widget bude umístěn zcela vlevo, další napravo od něj atd. atd.:
layout.addWidget(button1) layout.addWidget(button2) layout.addWidget(button3) layout.addWidget(button4) layout.addWidget(button5)
Následně nesmíme zapomenout oznámit hlavnímu oknu, že má použít právě vytvořený a nakonfigurovaný správce rozložení. Pokud na tento krok zapomeneme, komponenty se nevykreslí!
self.setLayout(layout)
Použití je tedy velmi podobné správci geometrie pack z knihovny Tkinter.
Obrázek 4: Pětice tlačítek umístěných do okna s využitím QHBoxLayout.
Do metody QHBoxLayout.addWidget lze předat i další dva nepovinné parametry nazvané stretch a alignment. Parametr stretch je celočíselný a slouží k řízení míry rozpínání widgetů v okně či dialogu, pokud se mění jeho velikost. Implicitní hodnotou je nula. Parametr alignment je možné využít ke specifikaci zarovnání ovládacích prvků (v rámci jednoho řádku):
Hodnota alignment | Význam |
---|---|
QtCore.Qt.AlignTop | zarovnání widgetu nahoru |
QtCore.Qt.AlignBottom | zarovnání widgetu dolů |
QtCore.Qt.AlignVCenter | vertikální vycentrování widgetu |
QtCore.Qt.AlignCenter | vycentrování widgetu ve vertikálním i horizontálním směru |
9. Ukázka použití správce rozložení QHBoxLayout
Podívejme se nyní, jak je možné správce rozložení QHBoxLayout použít v praktickém 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čítka button1 = QtGui.QPushButton("One") button2 = QtGui.QPushButton("Two") button3 = QtGui.QPushButton("Three") button4 = QtGui.QPushButton("Four") button5 = QtGui.QPushButton("Five") # vytvoření správce geometrie a umístění widgetů layout = QtGui.QHBoxLayout() layout.addWidget(button1) layout.addWidget(button2) layout.addWidget(button3) layout.addWidget(button4) layout.addWidget(button5) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) # navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
10. Vložení pružných mezer mezi ovládací prvky
Mezi jednotlivé viditelné widgety je možné vkládat pružné mezery s využitím metody addStretch. Vliv těchto mezer se samozřejmě projeví ve chvíli, kdy se mění velikost okna nebo dialogu. Zkusme si nejdříve vložit pružnou mezeru před první tlačítko, tj. mezera bude vložena doleva. U malého okna bude mít mezera nulovou šířku, která se ale bude se zvětšováním postupně měnit:
# vytvoření správce geometrie a umístění widgetů layout = QtGui.QHBoxLayout() # pružná mezera layout.addStretch(1) layout.addWidget(button1) layout.addWidget(button2) layout.addWidget(button3) layout.addWidget(button4) layout.addWidget(button5)
Obrázek 5: Pružná mezera vložená před první tlačítko (okno v původní velikosti).
Obrázek 6: Pružná mezera vložená před první tlačítko (okno po manuálním zvětšení).
Pro úplnost – zdrojový text příkladu nyní vypadá takto:
#!/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čítka button1 = QtGui.QPushButton("One") button2 = QtGui.QPushButton("Two") button3 = QtGui.QPushButton("Three") button4 = QtGui.QPushButton("Four") button5 = QtGui.QPushButton("Five") # vytvoření správce geometrie a umístění widgetů layout = QtGui.QHBoxLayout() # pružná mezera layout.addStretch(1) layout.addWidget(button1) layout.addWidget(button2) layout.addWidget(button3) layout.addWidget(button4) layout.addWidget(button5) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) # navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
Nyní se podívejme, co se stane při vložení pružné mezery až za druhé tlačítko:
# vytvoření správce geometrie a umístění widgetů layout = QtGui.QHBoxLayout() layout.addWidget(button1) layout.addWidget(button2) # pružná mezera layout.addStretch(1) layout.addWidget(button3) layout.addWidget(button4) layout.addWidget(button5)
Obrázek 7: Pružná mezera vložená až za druhé tlačítko (okno v původní velikosti).
Obrázek 8: Pružná mezera vložená až za druhé tlačítko (okno po manuálním zvětšení).
Opět následuje výpis upraveného kódu 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čítka button1 = QtGui.QPushButton("One") button2 = QtGui.QPushButton("Two") button3 = QtGui.QPushButton("Three") button4 = QtGui.QPushButton("Four") button5 = QtGui.QPushButton("Five") # vytvoření správce geometrie a umístění widgetů layout = QtGui.QHBoxLayout() layout.addWidget(button1) layout.addWidget(button2) # pružná mezera layout.addStretch(1) layout.addWidget(button3) layout.addWidget(button4) layout.addWidget(button5) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) # navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
11. Správce rozložení QVBoxLayout
Správce rozložení nazvaný QVBoxLayout umisťuje jednotlivé ovládací prvky (komponenty) pod sebe, tedy vertikálně. Jeho použití je prakticky shodné s výše popsaným správcem QHBoxLayout, takže si na tomto místě pouze uveďme základní příklad použití:
# vytvoření správce geometrie a umístění widgetů layout = QtGui.QVBoxLayout() layout.addWidget(button1) layout.addWidget(button2) layout.addWidget(button3) layout.addWidget(button4) layout.addWidget(button5) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout)
Obrázek 9: Pětice tlačítek rozmístěných do okna pomocí QVBoxLayout.
Ve skutečnosti je do metody QVBoxLayout.addWidget opět možné předat i další dva nepovinné parametry nazvané stretch a alignment. Parametr stretch je celočíselný a slouží k řízení míry rozpínání widgetů v okně či dialogu, pokud se mění jeho velikost. Implicitní hodnotou je nula. Parametr alignment je možné využít ke specifikaci zarovnání ovládacích prvků (pod sebe):
Hodnota alignment | Význam |
---|---|
QtCore.Qt.AlignLeft | zarovnání widgetu doleva |
QtCore.Qt.AlignRight | zarovnání widgetu doprava |
QtCore.Qt.AlignHCenter | horizontální vycentrování widgetu |
QtCore.Qt.AlignCenter | vycentrování widgetu ve vertikálním i horizontálním směru |
Poznámka: kromě QtCore.Qt.AlignCenter se hodnoty zarovnání odlišují od správce QHBoxLayout.
12. Ukázka použití správce rozložení QVBoxLayout
Z následujícího zdrojového kódu je patrné, že se správce rozložení QVBoxLayout používá naprosto stejným způsobem, jako již popsaný správce rozložení QHBoxLayout, i když výsledná podoba hlavního okna je samozřejmě odlišná:
#!/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): # velikost není potřeba specifikovat # self.resize(320, 240) self.setWindowTitle("Quit Button") # tlačítka button1 = QtGui.QPushButton("One") button2 = QtGui.QPushButton("Two") button3 = QtGui.QPushButton("Three") button4 = QtGui.QPushButton("Four") button5 = QtGui.QPushButton("Five") # vytvoření správce geometrie a umístění widgetů layout = QtGui.QVBoxLayout() layout.addWidget(button1) layout.addWidget(button2) layout.addWidget(button3) layout.addWidget(button4) layout.addWidget(button5) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) # navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
13. Správce rozložení QGridLayout
Zatímco správci rozložení QHBoxLayout a QVBoxLayout mohou posloužit pro tvorbu spíše jednodušších dialogů popř. pro hierarchickou tvorbu GUI s využitím kontejnerů umístěných v jiných kontejnerech, je správce rozvržení nazvaný QGridLayout mnohem univerzálnější. Podobně jako správce Grid z knihovny Tkinter umožňuje QGridLayout umístit komponenty do pomyslné mřížky, jejíž velikost (počet sloupců a řádků) i rozměry buněk závisí na komponentách, které se do mřížky vloží. V tom nejjednodušším případě je pro každou komponentu vyhrazena jedna buňka mřížky, jejíž koordináty se specifikují při vkládání komponenty do GUI. První číslo odpovídá řádku, druhé sloupci:
# vytvoření správce geometrie a umístění widgetů layout = QtGui.QGridLayout() # komponenta řádek sloupec layout.addWidget(button1, 1, 1) layout.addWidget(button2, 2, 2) layout.addWidget(button3, 2, 3) layout.addWidget(button4, 3, 2) layout.addWidget(button5, 4, 1) layout.addWidget(button6, 4, 2) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout)
Obrázek 10: Šest tlačítek umístěných do pomyslné mřížky.
14. Ukázka použití správce rozložení QGridLayout
Upravme si nyní náš demonstrační příklad takovým způsobem, aby se v něm namísto správce QHBoxLayout či QVBoxLayout využil právě správce QGridLayout. Úprava je velmi snadná:
#!/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): # velikost není potřeba specifikovat # self.resize(320, 240) self.setWindowTitle("Quit Button") # tlačítka button1 = QtGui.QPushButton("One") button2 = QtGui.QPushButton("Two") button3 = QtGui.QPushButton("Three") button4 = QtGui.QPushButton("Four") button5 = QtGui.QPushButton("Five") button6 = QtGui.QPushButton("Six") # vytvoření správce geometrie a umístění widgetů layout = QtGui.QGridLayout() layout.addWidget(button1, 1, 1) layout.addWidget(button2, 2, 2) layout.addWidget(button3, 2, 3) layout.addWidget(button4, 3, 2) layout.addWidget(button5, 4, 1) layout.addWidget(button6, 4, 2) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) # navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit) button6.clicked.connect(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
15. Komponenty umístěné do mřížky přes větší množství řádků či sloupců
Pro složitější okna a dialogy si nevystačíme s pouhým umístěním komponent do jednotlivých buněk tabulky, ale budeme muset některé komponenty umístit přes větší množství buněk. I to je samozřejmě možné, protože při vkládání komponenty do mřížky je možné použít další dva nepovinné parametry, jimiž se zadává počet sloučených řádků a počet sloučených buněk. Pokud nejsou tyto parametry explicitně zapsány, předpokládá se, že jsou rovny jedné:
# vytvoření správce geometrie a umístění widgetů layout = QtGui.QGridLayout() # komponenta řádek sloupec slouč. řádků slouč. sloupců layout.addWidget(button1, 1, 1, 1, 2) layout.addWidget(button2, 2, 2) layout.addWidget(button3, 2, 3) layout.addWidget(button4, 3, 2, 1, 2) layout.addWidget(button5, 3, 1, 2, 1) layout.addWidget(button6, 4, 2) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout)
Obrázek 11: Šest tlačítek, z nichž některá jsou umístěná do několika sloučených buněk.
Opět se podívejme na výpis úplného zdrojového kódu demonstračního příkladu se zvýrazněním provedených změn:
#!/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): # velikost není potřeba specifikovat # self.resize(320, 240) self.setWindowTitle("Quit Button") # tlačítka button1 = QtGui.QPushButton("One") button2 = QtGui.QPushButton("Two") button3 = QtGui.QPushButton("Three") button4 = QtGui.QPushButton("Four") button5 = QtGui.QPushButton("Five") button6 = QtGui.QPushButton("Six") # vytvoření správce geometrie a umístění widgetů layout = QtGui.QGridLayout() layout.addWidget(button1, 1, 1, 1, 2) layout.addWidget(button2, 2, 2) layout.addWidget(button3, 2, 3) layout.addWidget(button4, 3, 2, 1, 2) layout.addWidget(button5, 3, 1, 2, 1) layout.addWidget(button6, 4, 2) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) # navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit) button6.clicked.connect(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
16. Horizontální a vertikální zarovnání komponent v mřížce
S problematikou zarovnání komponent jsme se již setkali, takže si jen doplňme, že i při použití správce rozvržení QGridLayout je možné specifikovat, jak se jednotlivé komponenty zarovnají:
# vytvoření správce geometrie a umístění widgetů layout = QtGui.QGridLayout() layout.setHorizontalSpacing(0) layout.setVerticalSpacing(20) # komponenta řádek sloupec slouč. řádků slouč. sloupců zarovnání layout.addWidget(button1, 1, 1) layout.addWidget(button2, 2, 1) layout.addWidget(button3, 3, 1) layout.addWidget(button4, 4, 1) layout.addWidget(button5, 1, 2, 2, 1, QtCore.Qt.AlignTop) layout.addWidget(button6, 3, 2, 2, 1, QtCore.Qt.AlignBottom)
Obrázek 12: Šest tlačítek, z nichž některá jsou umístěna do několika sloučených buněk. Dvě tlačítka ve druhém sloupci jsou explicitně zarovnána na horní resp. dolní okraj sloučených buněk.
Nyní však máme k dispozici ucelený repertoár zarovnání – jak horizontálního, tak i vertikálního:
Hodnota alignment | Význam |
---|---|
QtCore.Qt.AlignLeft | zarovnání widgetu doleva |
QtCore.Qt.AlignRight | zarovnání widgetu doprava |
QtCore.Qt.AlignHCenter | horizontální vycentrování widgetu |
QtCore.Qt.AlignTop | zarovnání widgetu nahoru |
QtCore.Qt.AlignBottom | zarovnání widgetu dolů |
QtCore.Qt.AlignVCenter | vertikální vycentrování widgetu |
QtCore.Qt.AlignCenter | vycentrování widgetu ve vertikálním i horizontálním směru |
Opět se podívejme na příklad, dnes již poslední. V příkladu navíc explicitně nastavujeme mezery mezi komponentami:
layout.setHorizontalSpacing(0) layout.setVerticalSpacing(20)
Zdrojový kód:
#!/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): # velikost není potřeba specifikovat # self.resize(320, 240) self.setWindowTitle("Quit Button") # tlačítka button1 = QtGui.QPushButton("One") button2 = QtGui.QPushButton("Two") button3 = QtGui.QPushButton("Three") button4 = QtGui.QPushButton("Four") button5 = QtGui.QPushButton("Five") button6 = QtGui.QPushButton("Six") # vytvoření správce geometrie a umístění widgetů layout = QtGui.QGridLayout() layout.setHorizontalSpacing(0) layout.setVerticalSpacing(20) layout.addWidget(button1, 1, 1) layout.addWidget(button2, 2, 1) layout.addWidget(button3, 3, 1) layout.addWidget(button4, 4, 1) layout.addWidget(button5, 1, 2, 2, 1, QtCore.Qt.AlignTop) layout.addWidget(button6, 3, 2, 2, 1, QtCore.Qt.AlignBottom) # nastavení správce geometrie a vložení všech komponent do okna self.setLayout(layout) # navázání akce na signál button1.clicked.connect(self.quit) button2.clicked.connect(self.quit) button3.clicked.connect(self.quit) button4.clicked.connect(self.quit) button5.clicked.connect(self.quit) button6.clicked.connect(self.quit) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def quit(self): print("Closing...") self.close() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
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/ - QLayout
https://srinikom.github.io/pyside-docs/PySide/QtGui/QLayout.html - QStackedLayout
https://srinikom.github.io/pyside-docs/PySide/QtGui/QStackedLayout.html - QFormLayout
https://srinikom.github.io/pyside-docs/PySide/QtGui/QFormLayout.html - QBoxLayout
https://srinikom.github.io/pyside-docs/PySide/QtGui/QBoxLayout.html - QHBoxLayout
https://srinikom.github.io/pyside-docs/PySide/QtGui/QHBoxLayout.html - QVBoxLayout
https://srinikom.github.io/pyside-docs/PySide/QtGui/QVBoxLayout.html - QGridLayout
https://srinikom.github.io/pyside-docs/PySide/QtGui/QGridLayout.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/ - 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/