Hlavní navigace

Tvorba GUI v Pythonu s PySide: přepínací tlačítka a tvorba hlavních oken aplikací

Pavel Tišnovský

V úvodní části článku si ukážeme použití přepínacích tlačítek (radio buttons), v části druhé způsob deklarace klávesových zkratek pro widgety a v části poslední se budeme zabývat popisem třídy QMainWindow.

Doba čtení: 34 minut

11. Pátý demonstrační příklad

12. Explicitní klávesové zkratky (Ctrl+?, Shift+?, Alt+?)

13. Šestý demonstrační příklad

14. Třída QMainWindow a její odlišnosti od QWidget

15. Zobrazení hlavního okna

16. Přidání tlačítka do centrální části hlavního okna

17. Lepší přístup – odvození vlastního widgetu pro centrální část hlavního okna

18. Složitější layout komponent, stavový řádek a další vylepšení

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Přepínací tlačítka (radio buttons)

Minule jsme si popsali způsob práce se standardními tlačítky představovanými třídou QPushButton (popř. jejími potomky) a taktéž se zaškrtávacími tlačítky, které jsou v knihovně PySide představována třídou QCheckBox. Připomeňme si jen, že zaškrtávací tlačítka mohou být použita pro výběr ze dvou či ze tří stavů a že je možné je spojovat do skupin, v nichž je možné zaškrtnout pouze jediné tlačítko. A právě přes tuto vlastnost se dostáváme ke třetí variantě tlačítka. Touto variantou je přepínač neboli radio button. Jedná se o zcela běžný prvek grafických uživatelských rozhraní, který se používá především ve chvíli, kdy je nutné zajistit, aby byl vybrán jen jediný prvek z dané množiny n prvků. Množina voleb by jen měla být dostatečně malá a ideálně předem známá, jinak je výhodnější použít widget typu seznam.

Obrázek 1: Se skupinami přepínacích tlačítek jsme se již setkali při popisu možností knihovny Tkinter.

V knihovně PySide jsou přepínací tlačítka představována instancemi tříd typu QRadioButton. Předkem této třídy je QAbstractButton, podobně jako u všech dalších typů tlačítek.

Obrázek 2: Hierarchie widgetů odvozených obecného ovládacího prvku QWidget.

2. Chování jediného přepínacího tlačítka v okně/dialogu

Přepínací tlačítka se typicky a v naprosté většině případů spojují do skupin, ovšem v extrémním případě může skupina obsahovat jen jediné tlačítko. Zajímavé je, že při použití jediného přepínacího tlačítka se různé toolkity (knihovny a frameworky pro tvorbu grafického uživatelského rozhraní) chovají odlišně. Nejprve si ukažme chování standardního Pythonovského GUI toolkitu, tedy knihovny Tkinter. Aplikace s oknem, v němž je (kromě dalších ovládacích prvků) jen jediné přepínací tlačítko, vypadá následovně:

#!/usr/bin/env python
 
import tkinter
 
import sys
 
 
def print_state():
    print(radio_var.get())
 
 
root = tkinter.Tk()
 
radio_var = tkinter.StringVar()
 
radio1 = tkinter.Radiobutton(root, variable=radio_var, value="Radio button",
                             text="Radio button")
 
testButton = tkinter.Button(root, text="Print state", command=print_state)
 
quitButton = tkinter.Button(root, text="Quit", command=exit)
 
radio1.grid(column=1, row=1)
testButton.grid(column=1, row=2, sticky="we", padx=6, pady=6)
quitButton.grid(column=1, row=3, sticky="we", padx=6, pady=6)
 
root.mainloop()

Po spuštění této aplikace se zobrazí toto okno:

Obrázek 3: Okno s jediným přepínacím tlačítkem po spuštění aplikace naprogramované s využitím knihovny Tkinter.

Přepínací tlačítko je samozřejmě možné vybrat (nebude již zašedlé), ale výběr již nelze zrušit (pokud samozřejmě nenaprogramujeme odlišné chování):

Obrázek 4: Přepínací tlačítko je sice možné vybrat, ale již ne „odvybrat“.

V knihovně PySide je chování aplikace (resp. přesněji řečeno okna či dialogu) s jediným přepínacím tlačítkem odlišné, protože se tento widget začne chovat jako běžné zaškrtávací tlačítko. Vzhled je ovšem samozřejmě odlišný:

Obrázek 5: Aplikace naprogramovaná v PySide; výchozí stav.

Obrázek 6: Přepínací tlačítko je možné vybrat…

Obrázek 7: …a výběr lze také zrušit.

V knihovně PySide se přepínací tlačítko vytvoří velmi jednoduše; postačuje totiž zavolat konstruktor třídy QRadioButton a předat mu text, který má být na tlačítku zobrazen (i když to tak vizuálně nevypadá, zahrnuje aktivní plocha tlačítka i celý text, nejenom vlastní přepínací „kolečko“):

testRadioButton = QtGui.QRadioButton("radio button")

Kdykoli později můžeme otestovat, jestli je dané tlačítko vybráno, zavoláním metody isChecked, která vrací pravdivostní (booleovskou) hodnotu:

testRadioButton.isChecked()

Kromě toho je možné pracovat se všemi čtyřmi signály:

Jméno signálu Význam
clicked vyslán po stisku a puštění tlačítka
pressed vyslán ve chvíli, kdy došlo ke stlačení
released vyslán ve chvíli, kdy došlo k puštění tlačítka
toggled vyslán po změně stavu tlačítka (zapnuto/vypnuto)

3. První demonstrační příklad

Podívejme se nyní na způsob implementace jednoduché aplikace, po jejímž spuštění se zobrazí okno obsahující trojici aktivních ovládacích prvků – přepínacího tlačítka, běžného tlačítka pro zjištění stavu aplikace a dalšího běžného tlačítka sloužícího k ukončení běhu aplikace. Vzhledem k jednoduchosti GUI aplikace použijeme pro rozmístění komponent obyčejný QVBoxLayout. Logika pro zjištění stavu přepínacího tlačítka je připravena na situaci, kdy do okna přidáme další ovládací prvky:

#!/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("Single radio button")
 
        # testovací přepínací tlačítko
        self.testRadioButton = QtGui.QRadioButton("radio button")
 
        # tlačítko pro zjištění stavů přepínačů
        testButton = QtGui.QPushButton("Print state")
 
        # tlačítko pro ukončení aplikace
        quitButton = QtGui.QPushButton("Quit")
 
        # vytvoření správce geometrie
        layout = QtGui.QVBoxLayout()
 
        # umístění widgetů do okna
        layout.addWidget(self.testRadioButton)
        layout.addWidget(testButton)
        layout.addWidget(quitButton)
 
        # nastavení správce geometrie a vložení všech komponent do okna
        self.setLayout(layout)
 
        # po stisku testovacího tlačítka se zavolá metoda
        testButton.clicked.connect(self.printState)
 
        # navázání akce na stisk tlačítka pro ukončení aplikace
        quitButton.clicked.connect(self.quit)
 
    def printState(self):
        print("-" * 50)
        MainWindow.printStateForRadioButton("radio button", self.testRadioButton)
 
    @staticmethod
    def printStateForRadioButton(name, radioButton):
        state = "checked" if radioButton.isChecked() else "unchecked"
        print("Radio button {name} is {state}".format(name=name, state=state))
 
    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()

4. Použití přepínacích tlačítek, které nejsou sdruženy do jediné skupiny

Jediné přepínací tlačítko se samozřejmě většinou nepoužívá; setkáme se spíše se skupinou dvou až (přibližně) deseti tlačítek. Pokud tato tlačítka přímo vložíme do okna aplikace, bude jejich chování záviset na nastavení vlastnosti autoExclusive. Implicitně je tato vlastnost u přepínacích tlačítek nastavena, takže je možné vybrat maximálně jediné tlačítko ze skupiny (nemusí být ovšem vybráno žádné tlačítko). Skupinu ve skutečnosti není zapotřebí explicitně nastavovat – pokud to totiž neuděláme, budou tlačítka sdružena do skupin podle toho widgetu, na jehož plochu jsou umístěna. A vzhledem k tomu, že tlačítka umístíme přímo do okna, budou všechna implicitně patřit do jediné skupiny. Můžeme se o tom snadno přesvědčit.

Nejprve vytvoříme sadu šesti přepínacích tlačítek:

# testovací přepínací tlačítka
testRadioButton1 = QtGui.QRadioButton("radio button #1")
testRadioButton2 = QtGui.QRadioButton("radio button #2")
testRadioButton3 = QtGui.QRadioButton("radio button #3")
testRadioButton4 = QtGui.QRadioButton("radio button #4")
testRadioButton5 = QtGui.QRadioButton("radio button #5")
testRadioButton6 = QtGui.QRadioButton("radio button #6")

Následně (není to ovšem povinnost) jedno z tlačítek vybereme:

# které tlačítko bude vybráno
testRadioButton3.setChecked(True)

Nakonec vložíme tlačítka přímo na plochu okna (zde konkrétně přes správce rozvržení QVBoxLayout):

# umístění widgetů do okna
layout.addWidget(testRadioButton1)
layout.addWidget(testRadioButton2)
layout.addWidget(testRadioButton3)
layout.addWidget(testRadioButton4)
layout.addWidget(testRadioButton5)
layout.addWidget(testRadioButton6)

Obrázek 8: Výchozí stav aplikace, kdy je vybráno třetí tlačítko.

Obrázek 9: Přepnutí na první tlačítko (myší či klávesnicí).

Pokud zakomentujeme tento řádek, nebude ve výchozím stavu vybráno žádné tlačítko:

self.testRadioButton3.setChecked(True)

Obrázek 10: Výchozí stav ve chvíli, kdy není vybráno žádné tlačítko.

5. Druhý demonstrační příklad

Ve druhém demonstračním příkladu je ukázán způsob použití většího množství přepínacích tlačítek vložených do jediného okna aplikace. U tlačítek není specifikována žádná skupina, což povede k tomu, že se tlačítka automaticky přepnou do režimu exkluzivity výběru (tj. bude možné vybrat vždy maximálně jedno tlačítko v celém oknu):

#!/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("Six radio buttons")
 
        # testovací přepínací tlačítka
        self.testRadioButton1 = QtGui.QRadioButton("radio button #1")
        self.testRadioButton2 = QtGui.QRadioButton("radio button #2")
        self.testRadioButton3 = QtGui.QRadioButton("radio button #3")
        self.testRadioButton4 = QtGui.QRadioButton("radio button #4")
        self.testRadioButton5 = QtGui.QRadioButton("radio button #5")
        self.testRadioButton6 = QtGui.QRadioButton("radio button #6")
 
        # které tlačítko bude vybráno
        self.testRadioButton3.setChecked(True)
 
        # tlačítko pro zjištění stavů přepínačů
        testButton = QtGui.QPushButton("Print state")
 
        # tlačítko pro ukončení aplikace
        quitButton = QtGui.QPushButton("Quit")
 
        # vytvoření správce geometrie
        layout = QtGui.QVBoxLayout()
 
        # umístění widgetů do okna
        layout.addWidget(self.testRadioButton1)
        layout.addWidget(self.testRadioButton2)
        layout.addWidget(self.testRadioButton3)
        layout.addWidget(self.testRadioButton4)
        layout.addWidget(self.testRadioButton5)
        layout.addWidget(self.testRadioButton6)
        layout.addWidget(testButton)
        layout.addWidget(quitButton)
 
        # nastavení správce geometrie a vložení všech komponent do okna
        self.setLayout(layout)
 
        # po stisku testovacího tlačítka se zavolá metoda
        testButton.clicked.connect(self.printState)
 
        # navázání akce na stisk tlačítka pro ukončení aplikace
        quitButton.clicked.connect(self.quit)
 
    def printState(self):
        print("-" * 50)
        MainWindow.printStateForRadioButton("#1", self.testRadioButton1)
        MainWindow.printStateForRadioButton("#2", self.testRadioButton2)
        MainWindow.printStateForRadioButton("#3", self.testRadioButton3)
        MainWindow.printStateForRadioButton("#4", self.testRadioButton4)
        MainWindow.printStateForRadioButton("#5", self.testRadioButton5)
        MainWindow.printStateForRadioButton("#6", self.testRadioButton6)
 
    @staticmethod
    def printStateForRadioButton(name, radioButton):
        state = "checked" if radioButton.isChecked() else "unchecked"
        print("Radio button {name} is {state}".format(name=name, state=state))
 
    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()

6. Vypnutí režimu automatického sdružování přepínacích tlačítek do jedné skupiny

Výše popsaný režim automatického sdružování přepínacích tlačítek do jedné skupiny můžeme vypnout, a to buď pro všechna tlačítka, nebo jen pro tlačítka vybraná. Vyzkoušejme si druhou možnost. Nejprve opět vytvoříme šest přepínacích tlačítek:

# testovací přepínací tlačítka
testRadioButton1 = QtGui.QRadioButton("radio button #1")
testRadioButton2 = QtGui.QRadioButton("radio button #2")
testRadioButton3 = QtGui.QRadioButton("radio button #3")
testRadioButton4 = QtGui.QRadioButton("radio button #4")
testRadioButton5 = QtGui.QRadioButton("radio button #5")
testRadioButton6 = QtGui.QRadioButton("radio button #6")

Poté u prvních tří tlačítek vypneme režim automatického zajišťování exkluzivity výběru:

# první tři tlačítka nebudou automaticky přidána do společné skupiny
testRadioButton1.setAutoExclusive(False)
testRadioButton2.setAutoExclusive(False)
testRadioButton3.setAutoExclusive(False)

Chování je ukázáno na následující sekvenci screenshotů:

Obrázek 11: Výchozí stav aplikace, kdy není vybráno žádné tlačítko.

Obrázek 12: Ve druhé (spodní) trojici lze vždy vybrat maximálně jedno tlačítko.

Obrázek 13: První (horní) trojice umožňuje libovolnou kombinaci výběru.

7. Třetí demonstrační příklad

Třetí demonstrační příklad se do značné míry podobá příkladu druhému, s nímž jsme se seznámili v páté kapitole. Jediný podstatný rozdíl spočívá v tom, že první tři přepínací tlačítka nemají povolen režim exkluzivního výběru. Tato tlačítka jsou od další trojice vizuálně oddělena:

#!/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("Non exclusive selection")
 
        # testovací přepínací tlačítka
        self.testRadioButton1 = QtGui.QRadioButton("radio button #1")
        self.testRadioButton2 = QtGui.QRadioButton("radio button #2")
        self.testRadioButton3 = QtGui.QRadioButton("radio button #3")
        self.testRadioButton4 = QtGui.QRadioButton("radio button #4")
        self.testRadioButton5 = QtGui.QRadioButton("radio button #5")
        self.testRadioButton6 = QtGui.QRadioButton("radio button #6")
 
        # první tři tlačítka nebudou automaticky přidána do společné skupiny
        self.testRadioButton1.setAutoExclusive(False)
        self.testRadioButton2.setAutoExclusive(False)
        self.testRadioButton3.setAutoExclusive(False)
 
        # tlačítko pro zjištění stavů přepínačů
        testButton = QtGui.QPushButton("Print state")
 
        # tlačítko pro ukončení aplikace
        quitButton = QtGui.QPushButton("Quit")
 
        # horizontální oddělovač
        horizontalLine = QtGui.QLabel()
        horizontalLine.setFrameStyle(QtGui.QFrame.HLine)
 
        # vytvoření správce geometrie
        layout = QtGui.QVBoxLayout()
 
        # umístění widgetů do okna
        layout.addWidget(self.testRadioButton1)
        layout.addWidget(self.testRadioButton2)
        layout.addWidget(self.testRadioButton3)
        layout.addWidget(horizontalLine)
        layout.addWidget(self.testRadioButton4)
        layout.addWidget(self.testRadioButton5)
        layout.addWidget(self.testRadioButton6)
        layout.addWidget(testButton)
        layout.addWidget(quitButton)
 
        # nastavení správce geometrie a vložení všech komponent do okna
        self.setLayout(layout)
 
        # po stisku testovacího tlačítka se zavolá metoda
        testButton.clicked.connect(self.printState)
 
        # navázání akce na stisk tlačítka pro ukončení aplikace
        quitButton.clicked.connect(self.quit)
 
    def printState(self):
        print("-" * 50)
        MainWindow.printStateForRadioButton("#1", self.testRadioButton1)
        MainWindow.printStateForRadioButton("#2", self.testRadioButton2)
        MainWindow.printStateForRadioButton("#3", self.testRadioButton3)
        MainWindow.printStateForRadioButton("#4", self.testRadioButton4)
        MainWindow.printStateForRadioButton("#5", self.testRadioButton5)
        MainWindow.printStateForRadioButton("#6", self.testRadioButton6)
 
    @staticmethod
    def printStateForRadioButton(name, radioButton):
        state = "checked" if radioButton.isChecked() else "unchecked"
        print("Radio button {name} is {state}".format(name=name, state=state))
 
    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()

8. Vytvoření explicitních skupin přepínacích tlačítek

V případě, že se mají v dialogu objevit přepínací tlačítka tvořící větší množství skupin, již musíme jednotlivé skupiny explicitně definovat. Není to nic těžkého, ostatně jsme se s tímto problémem již setkali minule při popisu tvorby skupiny zaškrtávacích tlačítek. Podívejme se tedy na postup ve chvíli, kdy budeme potřebovat vytvořit tři skupiny přepínacích tlačítek, přičemž v každé skupině budou pro jednoduchost pouze dvě tlačítka. Nejprve jednotlivá tlačítka vytvoříme, což už dobře známe:

# testovací přepínací tlačítka
 
# první skupina
testRadioButton1 = QtGui.QRadioButton("radio button #1")
testRadioButton2 = QtGui.QRadioButton("radio button #2")
 
# druhá skupina
testRadioButton3 = QtGui.QRadioButton("radio button #3")
testRadioButton4 = QtGui.QRadioButton("radio button #4")
 
# třetí skupina
testRadioButton5 = QtGui.QRadioButton("radio button #5")
testRadioButton6 = QtGui.QRadioButton("radio button #6")

Dále musíme tlačítka sloučit do skupin, takže si jednotlivé skupiny vytvoříme. Stačí nám přitom dvě skupiny, a to z toho důvodu, že zbylá tlačítka budou patřit přímo do okna a vytvoří tedy automaticky skupinu vlastní:

# dvě explicitní skupiny tlačítek
buttonGroup1and2 = QtGui.QButtonGroup()
buttonGroup3and4 = QtGui.QButtonGroup()

Nastavíme chování tlačítek v jednotlivých skupinách:

# chování tlačítek ve skupinách
buttonGroup1and2.setExclusive(True)
buttonGroup3and4.setExclusive(True)

Přidáme dvě tlačítka do první skupiny:

# přidání přepínacích tlačítek do skupin
buttonGroup1and2.addButton(testRadioButton1)
buttonGroup1and2.addButton(testRadioButton2)

A další dvě tlačítka do skupiny druhé:

# přidání přepínacích tlačítek do skupin
buttonGroup3and4.addButton(testRadioButton3)
buttonGroup3and4.addButton(testRadioButton4)

Následně již jednotlivá tlačítka vložíme do okna, samozřejmě společně s vizuálním oddělovačem jednotlivých skupin:

# umístění widgetů do okna
layout.addWidget(testRadioButton1)
layout.addWidget(testRadioButton2)
 
layout.addWidget(horizontalLine1)
 
layout.addWidget(testRadioButton3)
layout.addWidget(testRadioButton4)
 
layout.addWidget(horizontalLine2)
 
layout.addWidget(testRadioButton5)
layout.addWidget(testRadioButton6)

Chování takto upravené aplikace je ukázáno na další sérii screenshotů:

Obrázek 14: Výchozí stav přepínačů – žádné tlačítko není vybráno.

Obrázek 15: Přepínaní je možné provádět v každé skupině zvlášť.

Obrázek 16: Samozřejmě nezávisle na ostatních skupinách.

Obrázek 17: Stav přepínačů zjištěných ve chvíli, kdy je aplikace ve stavu zobrazeném na předchozím screenshotu.

9. Čtvrtý demonstrační příklad

V dnešním třetím demonstračním příkladu je ukázáno, jakým způsobem je možné v případě potřeby rozdělit přepínací tlačítka do skupin. Zde se konkrétně používají dvě explicitně definované skupiny tlačítek, přičemž zbylá přepínací tlačítka mají vlastní (výchozí) skupinu:

#!/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("Custom radio button groups")
 
        # dvě explicitní skupiny tlačítek
        self.buttonGroup1and2 = QtGui.QButtonGroup()
        self.buttonGroup3and4 = QtGui.QButtonGroup()
 
        # chování tlačítek ve skupinách
        self.buttonGroup1and2.setExclusive(True)
        self.buttonGroup3and4.setExclusive(True)
 
        # testovací přepínací tlačítka
        self.testRadioButton1 = QtGui.QRadioButton("radio button #1")
        self.testRadioButton2 = QtGui.QRadioButton("radio button #2")
        self.testRadioButton3 = QtGui.QRadioButton("radio button #3")
        self.testRadioButton4 = QtGui.QRadioButton("radio button #4")
        self.testRadioButton5 = QtGui.QRadioButton("radio button #5")
        self.testRadioButton6 = QtGui.QRadioButton("radio button #6")
 
        # přidání přepínacích tlačítek do skupin
        self.buttonGroup1and2.addButton(self.testRadioButton1)
        self.buttonGroup1and2.addButton(self.testRadioButton2)
        self.buttonGroup3and4.addButton(self.testRadioButton3)
        self.buttonGroup3and4.addButton(self.testRadioButton4)
 
        # tlačítko pro zjištění stavů přepínačů
        testButton = QtGui.QPushButton("Print state")
 
        # tlačítko pro ukončení aplikace
        quitButton = QtGui.QPushButton("Quit")
 
        # horizontální oddělovače
        horizontalLine1 = QtGui.QLabel()
        horizontalLine1.setFrameStyle(QtGui.QFrame.HLine)
        horizontalLine2 = QtGui.QLabel()
        horizontalLine2.setFrameStyle(QtGui.QFrame.HLine)
 
        # vytvoření správce geometrie
        layout = QtGui.QVBoxLayout()
 
        # umístění widgetů do okna
        layout.addWidget(self.testRadioButton1)
        layout.addWidget(self.testRadioButton2)
        layout.addWidget(horizontalLine1)
        layout.addWidget(self.testRadioButton3)
        layout.addWidget(self.testRadioButton4)
        layout.addWidget(horizontalLine2)
        layout.addWidget(self.testRadioButton5)
        layout.addWidget(self.testRadioButton6)
        layout.addWidget(testButton)
        layout.addWidget(quitButton)
 
        # nastavení správce geometrie a vložení všech komponent do okna
        self.setLayout(layout)
 
        # po stisku testovacího tlačítka se zavolá metoda
        testButton.clicked.connect(self.printState)
 
        # navázání akce na stisk tlačítka pro ukončení aplikace
        quitButton.clicked.connect(self.quit)
 
    def printState(self):
        print("-" * 50)
        MainWindow.printStateForRadioButton("#1", self.testRadioButton1)
        MainWindow.printStateForRadioButton("#2", self.testRadioButton2)
        MainWindow.printStateForRadioButton("#3", self.testRadioButton3)
        MainWindow.printStateForRadioButton("#4", self.testRadioButton4)
        MainWindow.printStateForRadioButton("#5", self.testRadioButton5)
        MainWindow.printStateForRadioButton("#6", self.testRadioButton6)
 
    @staticmethod
    def printStateForRadioButton(name, radioButton):
        state = "checked" if radioButton.isChecked() else "unchecked"
        print("Radio button {name} is {state}".format(name=name, state=state))
 
    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. Použití klávesových zkratek namísto myši při přístupu ke standardním widgetům

Ve druhé třetině článku se budeme zabývat přiřazením klávesových zkratek k jednotlivým aktivním ovládacím prvkům. Při tvorbě zkratek můžeme použít dva přístupy – buď se spolehneme na použití kombinace klávesy Alt s dalším znakem, nebo budeme muset vytvořit vlastní klávesovou zkratku, která ovšem může být mnohonásobně komplikovanější a může obsahovat i sekvenci po sobě stisknutých kombinací kláves. Nejdříve se však budeme zabývat prvním případem, tj. klávesovými zkratkami ve tvaru Alt+znak. Příslušný znak může být součástí textu na ovládacím prvku; postačí před něj vložit znak &. Příslušný znak bude většinou zobrazen s podtržením a bude i správně reagovat na ovládání z klávesnice. Ukažme si příklad použití tohoto postupu pro různé typy widgetů:

# testovací zaškrtávací tlačítka
testCheckBox1 = QtGui.QCheckBox("check box &x")
testCheckBox2 = QtGui.QCheckBox("check box &y")
testCheckBox3 = QtGui.QCheckBox("check box &z")
 
# testovací přepínací tlačítka
estRadioButton1 = QtGui.QRadioButton("radio button &a")
estRadioButton2 = QtGui.QRadioButton("radio button &b")
estRadioButton3 = QtGui.QRadioButton("radio button &c")
 
# tlačítko pro zjištění stavů přepínačů
testButton = QtGui.QPushButton("&Print state")
 
# tlačítko pro ukončení aplikace
quitButton = QtGui.QPushButton("&Quit")

Obrázek 18: Ovládací prvky, k nimž je přiřazena kombinace Alt+znak.

Pokud budeme chtít pouze zobrazit znak &, musíme ho zdvojit a tím negovat jeho speciální význam:

# testovací zaškrtávací tlačítka
testCheckBox1 = QtGui.QCheckBox("check box &&x")
testCheckBox2 = QtGui.QCheckBox("check box &&y")
testCheckBox3 = QtGui.QCheckBox("check box &&z")
 
# testovací přepínací tlačítka
estRadioButton1 = QtGui.QRadioButton("radio button &&a")
estRadioButton2 = QtGui.QRadioButton("radio button &&b")
estRadioButton3 = QtGui.QRadioButton("radio button &&c")
 
# tlačítko pro zjištění stavů přepínačů
testButton = QtGui.QPushButton("&&Print state")
 
# tlačítko pro ukončení aplikace
quitButton = QtGui.QPushButton("&&Quit")

Obrázek 19: Ovládací prvky se zobrazením znaku & (nemá zde žádný speciální význam).

11. Pátý demonstrační příklad

V pořadí pátém demonstračním příkladu je ukázáno, jak lze k jednotlivým ovládacím prvkům, tj. k běžným tlačítkům, zaškrtávacím tlačítkům i k tlačítkům přepínacím přiřadit klávesové zkratky. Po spuštění příkladu si můžete sami vyzkoušet použít kombinaci Alt+znak pro stisk příslušného tlačítka bez nutnosti použít myš či přepínat fokus s využitím Tab:

#!/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("Shortcuts")
 
        # testovací zaškrtávací tlačítka
        self.testCheckBox1 = QtGui.QCheckBox("check box &x")
        self.testCheckBox2 = QtGui.QCheckBox("check box &y")
        self.testCheckBox3 = QtGui.QCheckBox("check box &z")
        self.testCheckBox1.setCheckState(QtCore.Qt.Unchecked)
        self.testCheckBox2.setCheckState(QtCore.Qt.Checked)
        self.testCheckBox3.setCheckState(QtCore.Qt.Unchecked)
 
        # testovací přepínací tlačítka
        self.testRadioButton1 = QtGui.QRadioButton("radio button &a")
        self.testRadioButton2 = QtGui.QRadioButton("radio button &b")
        self.testRadioButton3 = QtGui.QRadioButton("radio button &c")
        self.testRadioButton2.setChecked(True)
 
        # tlačítko pro zjištění stavů přepínačů
        testButton = QtGui.QPushButton("&Print state")
 
        # tlačítko pro ukončení aplikace
        quitButton = QtGui.QPushButton("&Quit")
 
        # horizontální oddělovače
        horizontalLine1 = QtGui.QLabel()
        horizontalLine1.setFrameStyle(QtGui.QFrame.HLine)
        horizontalLine2 = QtGui.QLabel()
        horizontalLine2.setFrameStyle(QtGui.QFrame.HLine)
 
        # vytvoření správce geometrie
        layout = QtGui.QVBoxLayout()
 
        # umístění widgetů do okna
        layout.addWidget(self.testRadioButton1)
        layout.addWidget(self.testRadioButton2)
        layout.addWidget(self.testRadioButton3)
        layout.addWidget(horizontalLine1)
        layout.addWidget(self.testCheckBox1)
        layout.addWidget(self.testCheckBox2)
        layout.addWidget(self.testCheckBox3)
        layout.addWidget(horizontalLine2)
        layout.addWidget(testButton)
        layout.addWidget(quitButton)
 
        # nastavení správce geometrie a vložení všech komponent do okna
        self.setLayout(layout)
 
        # po stisku testovacího tlačítka se zavolá metoda
        testButton.clicked.connect(self.printState)
 
        # navázání akce na stisk tlačítka pro ukončení aplikace
        quitButton.clicked.connect(self.quit)
 
    def printState(self):
        print("-" * 50)
        MainWindow.printStateForRadioButton("#1", self.testRadioButton1)
        MainWindow.printStateForRadioButton("#2", self.testRadioButton2)
        MainWindow.printStateForRadioButton("#3", self.testRadioButton3)
        MainWindow.printStateForCheckbox("#1", self.testCheckBox1)
        MainWindow.printStateForCheckbox("#2", self.testCheckBox2)
        MainWindow.printStateForCheckbox("#3", self.testCheckBox3)
 
    @staticmethod
    def printStateForCheckbox(name, checkbox):
        state = "checked" if checkbox.isChecked() else "unchecked"
        print("Checkbox {name} is {state}".format(name=name, state=state))
 
    @staticmethod
    def printStateForRadioButton(name, radioButton):
        state = "checked" if radioButton.isChecked() else "unchecked"
        print("Radio button {name} is {state}".format(name=name, state=state))
 
    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()

12. Explicitní klávesové zkratky (Ctrl+?, Shift+?, Alt+?)

Pokud kombinace Alt+znak z nějakého důvodu nedostačuje pro potřeby aplikace, je nutné použít nepatrně složitější způsob deklarace klávesových zkratek. Tento způsob využívá metodu setShortcut deklarovanou ve třídě QAbstractButton. Této metodě se předává objekt typu QKeySequence. Instance QKeySequence lze získat různými způsoby, ovšem pokud nebudeme chtít klávesovou zkratku zobrazit uživateli (což u běžného dialogu asi nemá význam, na rozdíl od položek menu), je nejjednodušší použít konstruktor:

QKeySequence("klávesová zkratka")

Pozor: ve chvíli, kdy k ovládacímu prvku přiřadíte vlastní klávesovou zkratku, nebude možné použít implicitní zkratku Alt+znak, i když bude příslušný znak podtržený!

Příklady použití ukazují, jaké klávesové zkratky (nutno říci, že v této podobě jsou systémově závislé) se mohou v řetězci objevit:

# klávesové zkratky
testRadioButton1.setShortcut(QtGui.QKeySequence("Ctrl+A"))
testRadioButton2.setShortcut(QtGui.QKeySequence("Ctrl+B"))
testRadioButton3.setShortcut(QtGui.QKeySequence("Ctrl+C"))
 
testCheckBox1.setShortcut(QtGui.QKeySequence("F1"))
testCheckBox2.setShortcut(QtGui.QKeySequence("F2"))
testCheckBox3.setShortcut(QtGui.QKeySequence("F3"))
 
testButton.setShortcut(QtGui.QKeySequence("Shift+P"))
quitButton.setShortcut(QtGui.QKeySequence("Esc"))

Obrázek 20: Aplikace, kterou je možné ovládat klávesovými zkratkami.

13. Šestý demonstrační příklad

Způsob deklarace klávesových zkratek přiřazených k jednotlivým komponentám grafického uživatelského rozhraní je ukázán v dnešním šestém demonstračním příkladu, jehož zdrojový kód je vypsán pod tímto odstavcem:

#!/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("Custom shortcuts")
 
        # testovací zaškrtávací tlačítka
        self.testCheckBox1 = QtGui.QCheckBox("check box x (F1)")
        self.testCheckBox2 = QtGui.QCheckBox("check box y (F2)")
        self.testCheckBox3 = QtGui.QCheckBox("check box z (F3)")
        self.testCheckBox1.setCheckState(QtCore.Qt.Unchecked)
        self.testCheckBox2.setCheckState(QtCore.Qt.Checked)
        self.testCheckBox3.setCheckState(QtCore.Qt.Unchecked)
 
        # testovací přepínací tlačítka
        self.testRadioButton1 = QtGui.QRadioButton("radio button a (Ctrl+A)")
        self.testRadioButton2 = QtGui.QRadioButton("radio button b (Ctrl+B)")
        self.testRadioButton3 = QtGui.QRadioButton("radio button c (Ctrl+C)")
        self.testRadioButton2.setChecked(True)
 
        # tlačítko pro zjištění stavů přepínačů
        testButton = QtGui.QPushButton("Print state (Shift+P)")
 
        # tlačítko pro ukončení aplikace
        quitButton = QtGui.QPushButton("Quit (Esc)")
 
        # klávesové zkratky
        self.testRadioButton1.setShortcut(QtGui.QKeySequence("Ctrl+A"))
        self.testRadioButton2.setShortcut(QtGui.QKeySequence("Ctrl+B"))
        self.testRadioButton3.setShortcut(QtGui.QKeySequence("Ctrl+C"))
 
        self.testCheckBox1.setShortcut(QtGui.QKeySequence("F1"))
        self.testCheckBox2.setShortcut(QtGui.QKeySequence("F2"))
        self.testCheckBox3.setShortcut(QtGui.QKeySequence("F3"))
 
        testButton.setShortcut(QtGui.QKeySequence("Shift+P"))
        quitButton.setShortcut(QtGui.QKeySequence("Esc"))
 
        # horizontální oddělovače
        horizontalLine1 = QtGui.QLabel()
        horizontalLine1.setFrameStyle(QtGui.QFrame.HLine)
        horizontalLine2 = QtGui.QLabel()
        horizontalLine2.setFrameStyle(QtGui.QFrame.HLine)
 
        # vytvoření správce geometrie
        layout = QtGui.QVBoxLayout()
 
        # umístění widgetů do okna
        layout.addWidget(self.testRadioButton1)
        layout.addWidget(self.testRadioButton2)
        layout.addWidget(self.testRadioButton3)
        layout.addWidget(horizontalLine1)
        layout.addWidget(self.testCheckBox1)
        layout.addWidget(self.testCheckBox2)
        layout.addWidget(self.testCheckBox3)
        layout.addWidget(horizontalLine2)
        layout.addWidget(testButton)
        layout.addWidget(quitButton)
 
        # nastavení správce geometrie a vložení všech komponent do okna
        self.setLayout(layout)
 
        # po stisku testovacího tlačítka se zavolá metoda
        testButton.clicked.connect(self.printState)
 
        # navázání akce na stisk tlačítka pro ukončení aplikace
        quitButton.clicked.connect(self.quit)
 
    def printState(self):
        print("-" * 50)
        MainWindow.printStateForRadioButton("#1", self.testRadioButton1)
        MainWindow.printStateForRadioButton("#2", self.testRadioButton2)
        MainWindow.printStateForRadioButton("#3", self.testRadioButton3)
        MainWindow.printStateForCheckbox("#1", self.testCheckBox1)
        MainWindow.printStateForCheckbox("#2", self.testCheckBox2)
        MainWindow.printStateForCheckbox("#3", self.testCheckBox3)
 
    @staticmethod
    def printStateForCheckbox(name, checkbox):
        state = "checked" if checkbox.isChecked() else "unchecked"
        print("Checkbox {name} is {state}".format(name=name, state=state))
 
    @staticmethod
    def printStateForRadioButton(name, radioButton):
        state = "checked" if radioButton.isChecked() else "unchecked"
        print("Radio button {name} is {state}".format(name=name, state=state))
 
    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()

14. Třída QMainWindow a její odlišnosti od QWidget

V poslední třetině článku si ukážeme, jakým způsobem je možné použít třídu QMainWindow pro vytvoření hlavního okna aplikace. Všechny předchozí příklady byly založeny na použití obecné komponenty QWidget, od níž jsme odvodili vlastní třídu s odlišnými vlastnostmi (a samozřejmě i vzhledem):

# nový widget bude odvozen od obecného widgetu
class MainWindow(QtGui.QWidget):
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()

Využití QWidgetu je možné doporučit v případě, že vše, co vyžadujeme, je prázdné okno či dialog, na jehož plochu budeme vkládat další komponenty. Ovšem na hlavní okno aplikace jsou kladeny poněkud odlišné požadavky, neboť uživatelé očekávají:

  1. Existenci hlavního menu
  2. Existenci stavového řádku
  3. Většinou taktéž toolbar
  4. U některých aplikací taby s jednotlivými listy

Právě v tomto případě není nutné všechny očekávané vlastnosti implementovat ručně, ale lze namísto QWidgetu použít právě třídu QMainWindow. Plocha hlavního okna je rozdělena na oblasti, do kterých lze vkládat zmíněné hlavní menu (představované komponentou QMenuBar), stavový řádek představovaný komponentou QStatusBar, dále zde existuje oblast určená pro vložení toolbaru a tzv. centrální plocha, do které je možné (většinou nepřímo) vkládat další ovládací prvky.

Obrázek 21: Okno vytvořené odvozením od komponenty QMainWindow.

Kostra aplikace používající třídu QMainWindow může vypadat prakticky stejně, jako aplikace založená na třídě QWidget:

# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()

O dalších odlišnostech, které jsou mnohdy zásadního charakteru, se dozvíme v dalších kapitolách.

15. Zobrazení hlavního okna

Způsob použití třídy QMainWindow pro odvození třídy reprezentující konkrétní hlavní okno aplikace je ukázán v dalším demonstračním příkladu, jehož zdrojový kód je vypsán pod tímto odstavcem. Povšimněte si, že se tato aplikace prakticky nijak neliší od aplikací, v nichž jsme hlavní okno odvodili od obecného ovládacího prvku QWidget. Ve skutečnosti však bude chování odlišné, což uvidíme ve chvíli, kdy do hlavního okna budeme vkládat stavový řádek, hlavní menu, nástrojový pruh či „pouze“ libovolné další ovládací prvky:

#!/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 hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()
 
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareGUI(self):
        # velikost není potřeba specifikovat
        # self.resize(320, 240)
        self.setWindowTitle("QMainWindow")
 
    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. Přidání tlačítka do centrální části hlavního okna

Jak se vlastně do hlavního okna přidávají další widgety? Už ve čtrnácté kapitole jsme si řekli, že hlavní okno se v některých ohledech odlišuje od obecného widgetu QWidget, a to především v tom ohledu, že v hlavním oknu už jsou předem vyhrazené oblasti na speciální ovládací prvky. Proto není vhodné přidávat na hlavní okno další komponenty stylem:

# návěští
label = QtGui.QLabel("Hello world!", self)
# posun v rámci nadřazeného widgetu
label.move(100, 100)

Widget (či widgety) budeme vkládat do centrální oblasti, která se v případě potřeby automaticky zvětší a ostatní nepoužité oblasti se zmenší na nulovou plochu:

# tlačítko pro ukončení aplikace
quitButton = QtGui.QPushButton("Quit")
# vložení komponenty do okna
self.setCentralWidget(quitButton)

Obrázek 22: Hlavní okno aplikace s tlačítkem Quit.

Celý příklad, který po svém spuštění zobrazí hlavní okno s jediným tlačítkem Quit, 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 hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()
 
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareGUI(self):
        # velikost není potřeba specifikovat
        # self.resize(320, 240)
        self.setWindowTitle("QMainWindow")
 
        # tlačítko pro ukončení aplikace
        quitButton = QtGui.QPushButton("Quit")
 
        # vložení komponenty do okna
        self.setCentralWidget(quitButton)
 
        # navázání akce na stisk tlačítka pro ukončení aplikace
        quitButton.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. Lepší přístup – odvození vlastního widgetu pro centrální část hlavního okna

V předchozím příkladu se tlačítko Quit zvětšilo tak, že vyplnilo celou plochu hlavního okna (resp. přesněji řečeno jeho centrální oblasti). Ovšem ve chvíli, kdy budeme chtít do okna přidávat další ovládací prvky (a to asi budeme, protože okno s jediným tlačítkem je poněkud nepraktické), využijeme správce rozvržení neboli layout manager. Podívejme se nyní na jeden z možných přístupů. Vytvoříme novou třídu představující „vnitřek“ hlavního okna. Tato třída bude odvozena od QWidget, tedy stylem, s nímž jsme se již setkali. Můžeme zde použít libovolného správce rozvržení, atd. atd.

# nový widget bude odvozen od obecného widgetu
class MainWindowContent(QtGui.QWidget):
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindowContent, self).__init__()
 
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareGUI(self):
        # 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)

Následně náš nový widget (obsahující libovolné množství dalších widgetů) vložíme do hlavního okna, konkrétně do jeho centrální oblasti:

def prepareGUI(self):
    # velikost není potřeba specifikovat
    # self.resize(320, 240)
    self.setWindowTitle("QMainWindow")
 
    # vložení komponenty do okna
    self.setCentralWidget(MainWindowContent())

Obrázek 23: Hlavní okno aplikace s tlačítkem Quit.

Celý příklad s dvojicí tříd 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 MainWindowContent(QtGui.QWidget):
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindowContent, self).__init__()
 
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareGUI(self):
        # 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)
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()
 
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareGUI(self):
        # velikost není potřeba specifikovat
        # self.resize(320, 240)
        self.setWindowTitle("QMainWindow")
 
        # vložení komponenty do okna
        self.setCentralWidget(MainWindowContent())
 
    def run(self, app):
        # zobrazení okna na obrazovce
        self.show()
        # vstup do smyčky událostí (event loop)
        app.exec_()
 
 
def main():
    app = QtGui.QApplication(sys.argv)
    MainWindow().run(app)
 
 
if __name__ == '__main__':
    main()

18. Složitější layout komponent, stavový řádek a další vylepšení

Rozdělení funkcionality hlavního okna do dvou tříd může vést k větší přehlednosti zdrojového kódu, ovšem někdy se setkáme s nutností posílání zpráv mezi instancemi obou tříd. V následujícím příkladu je řešen problém jednoduchého čítače, jehož hodnota je zvýšena po každém stisku tlačítka:

Obrázek 24: Hlavní okno aplikace ihned po spuštění.

Obrázek 25: Stav aplikace po několikerém stisku tlačítka.

Čítač je ovšem umístěn do stavového řádku hlavního okna, takže je nutné, aby tlačítko (které je součástí třídy MainWindowContent) komunikovalo se stavovým řádkem (ten je ovšem součástí třídy MainWindow). Jedno z řešení – schválně zatím neřeknu, jak moc je dobré – je ukázáno níže. Sami se zkuste zamyslet nad dalšími způsoby řešení tohoto problému. Některé z nich si popíšeme příště:

MIF18 tip v článku témata

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# nový widget bude odvozen od obecného widgetu
class MainWindowContent(QtGui.QWidget):
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindowContent, self).__init__()
 
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareGUI(self):
        # tlačítko 1
        self.counterButton = QtGui.QPushButton("Counter", self)
        self.counterButton.resize(self.counterButton.sizeHint())
 
        # tlačítko 2
        quitButton = QtGui.QPushButton("Quit", self)
        quitButton.resize(quitButton.sizeHint())
 
        # vytvoření správce geometrie
        layout = QtGui.QVBoxLayout()
 
        # umístění widgetů do okna
        layout.addWidget(self.counterButton)
        layout.addWidget(quitButton)
 
        # nastavení správce geometrie a vložení všech komponent do okna
        self.setLayout(layout)
 
        # navázání akce na signál
        quitButton.clicked.connect(QtCore.QCoreApplication.instance().quit)
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()
 
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
        self._counter = 0
 
    def prepareGUI(self):
        # velikost není potřeba specifikovat
        # self.resize(320, 240)
        self.setWindowTitle("QMainWindow")
 
        self.statusBar().showMessage("QMainWindow")
 
        content = MainWindowContent()
        self.setCentralWidget(content)
 
        # jedna z variant naprogramování reakce na stisk tlačítka
        content.counterButton.clicked.connect(self.counterClicked)
 
    def counterClicked(self):
        self._counter += 1
        self.statusBar().showMessage(str(self._counter))
 
    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()

19. Repositář s demonstračními příklady

Zdrojové kódy všech jedenácti dnes popsaných demonstračních příkladů byly opět, podobně jako tomu bylo i v předchozích článcích, uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/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:

Poznámka: první příklad je naprogramován s využitím Tkinteru a ukazuje chování jediného přepínacího tlačítka v této knihovně.

20. Odkazy na Internetu

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