Portál AbcLinuxu, 30. dubna 2025 11:36
Programoval jsem pár aplikací v Pythonu s použitím PyQt. Dlouho jsem zkoumal, jaký je nejlepší způsob inicializace grafického rozhraní ze souboru *.ui
vygenerovaného pomocí QDesigneru. V jinak dobré dokumentaci je tohle téma poněkud strohé. Takže tady uvedu moje poznatky.
Základním zdrojem informací je oficiální manuál [1]. Dále jsem našel pěkný příklad v diskusním fóru [2]. V zásadě jsou dva druhy přístupu. První spočívá v tom, že soubor *.ui
se přechroustá pomocí utilitky pyuic4
(nainstaluje se při instalaci PyQt
) a vznikne tak pythonovský soubor, který se dá naimportovat a použít (je tedy nutný mezikrok, který se v lepším případě provádí pomocí makefile
, v horším ručně). Tento způsob je v oficiální dokumentaci [1] dobře posán i s příklady. Druhý způsob je použití modulu uic
(přesněji PyQt4.uic
), který umožňuje dynamicky načítat přímo soubor *.ui
a není potřeba žádný mezikrok. Tento způsob je v dokumentaci [1] popsán poněkud stručněji a bez příkladů. Upřednostňuji druhý způsob, který teď rozeberu.
Zatímco v příkladech na internetu jsou krátké skripty, které nejsou psány objektově, v praxi je potřeba zapouzdřit kód do objektu, se kterým se dá pracovat odjinud. V následujících příkladech budu tedy vytvářet třídy, které stačí v ideálním případě naimportovat a jedním příkazem spustit, aniž by se člověk musel zabývat konkrétním použitím knihovny PyQt
. Můj prográmek umí jenom to, že při kliknutí na tlačítko vypíše zprávu na standardní výstup.
Dokumentace popisuje 3 způsoby použití. Bez dědění, s jednoduchou dědičností a dvojitou dědičností. Takže začneme od začátku.
Nejdřív si vytvoříme soubor priklad.ui
, který popisuje jednoduché okno aplikace, které obsahuje tlačítko (QPushButton
) s názvem pushButton
. Tlačítko je zatrhávací (checkable
), aby se ve výpisech aspoň střídaly 0 a 1.
Soubor priklad.ui
vypadá takto.
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>98</width> <height>46</height> </rect> </property> <property name="windowTitle"> <string>MainWindow</string> </property> <widget class="QWidget" name="centralwidget"> <widget class="QPushButton" name="pushButton"> <property name="geometry"> <rect> <x>9</x> <y>9</y> <width>75</width> <height>23</height> </rect> </property> <property name="text"> <string>PushButton</string> </property> <property name="checkable"> <bool>true</bool> </property> </widget> </widget> </widget> <resources/> <connections/> </ui>
#!/usr/bin/env python import sys from PyQt4 import QtGui, uic class MyApplication: def __init__(self): pass def run(self): self.app = QtGui.QApplication(sys.argv) self.ui = uic.loadUi("priklad.ui") self.ui.pushButton.clicked.connect(self.method_with_some_weird_name) self.ui.closeEvent = self._closeEvent self.ui.show() self.app.exec_() def method_with_some_weird_name(self, value): print "Clicked %d" % value def _closeEvent(self, event): print "Close event" event.accept() if __name__ == '__main__': MyApp = MyApplication() MyApp.run()
Výhody
Qt
, nehrozí konflikt jmen. Při množství metod a vlastností tříd v Qt
je každé pěkné jméno už použito.QtGui.QApplication
si můžu vytvořit ve vlastní třídě ještě před inicializací grafických objektů*.ui
se dá podstrčit až za běhu a měl by jít i za běhu vyměnit.self.ui
budu psát self.__ui
(takový pseudo soukromý atribut), donutí mě to napsat si vlastní funkce na všechno co potřebuji (udělat wrapper). Zbytku programu je potom jedno, jaké grafické rozhraní se používá (a jestli vůbec grafické). Kdybych chtěl aplikovat vzor MVC, asi bych to dělal takhle.Nevýhody
self.ui.pushButton.clicked.connect...
)Qt
je stavěno tak, že se dědí a předefinovává jako na běžícím pásu. Viz předefinování close event (řádek: self.ui.closeEvent = self._closeEvent)Přístup bez dědičnosti ukazuje, že není třeba bezhlavě dědit, i když to znamená práci navíc (viz výše). Pro aplikace s jednoduchým grafickým rozhraním je tento přístup vhodný.
#!/usr/bin/env python import sys from PyQt4 import QtGui, QtCore, uic class MyApplication(QtGui.QMainWindow): def __init__(self): QtGui.QMainWindow.__init__(self) uic.loadUi("priklad.ui", self) def run(self): self.show() self.app.exec_() @QtCore.pyqtSlot(bool, name="on_pushButton_clicked") def method_with_some_weird_name(self, value): print "Clicked %d" % value @QtCore.pyqtSlot(bool) # without this line the slot catches two signals: clicked() and clicked(bool) def on_pushButton_clicked(self, value): print "Clicked2 %d" % value def closeEvent(self, event): print "Close event" event.accept() def main(): app = QtGui.QApplication(sys.argv) MyApp = MyApplication() MyApp.app = app MyApp.run() if __name__ == '__main__': main()
Všimněte si, že při volání metody loadUi
přibyl nepovinný parametr self
.
výhody
clicked()
připojena hned dvojice metod (slotů) pomocí dekorátorů (to jen tak pro ukázku, většinou člověk chce připojit jeden slot). V případě metody method_with_some_weird_name
jsme museli napovědět, k jaké je to vlastně události, kdežto metoda on_pushButton_clicked
už má správný název a nic jsme dělat nemuseli. Samozřejmě nic nám nebrání připojovat sloty ručně jako v případě bez dědičnosti.close event
je jednodušší, prostě se předefinuje metoda closeEvent
. To platí i pro jiné jevy, které však v tomto jednoduchém příkladu nenastávají.nevýhody
Qt
, musím si hlídat jména svých metodQtGui.QApplication
ve vlastní třídě (musela by to být první instrukce konstruktoru), proto ta komplikace s funkcí main
.
Při použití jednoduché dědičnosti získáme pohodlí. Připojení slotů se dá udělat pomocí dekorátorů (ty se můžou řetězit, takže můžeme například všechna kliknutí směřovat do jednoho slotu), předefinování vlastností hlavního okna je rovněž o chlup pohodlnější. Platíme za to silnou závislostí na Qt
, takže například výběr alternativního rozhraní by se musel udělat o patro výš.
#!/usr/bin/env python import sys from PyQt4 import QtGui, QtCore, uic form, formbase = uic.loadUiType( "priklad.ui" ) class MyApplication(formbase, form): def __init__(self): formbase.__init__(self) self.setupUi(self) # inherited from form def run(self): self.show() self.app.exec_() @QtCore.pyqtSlot(bool, name="on_pushButton_clicked") def method_with_some_weird_name(self, value): print "Clicked %d" % value @QtCore.pyqtSlot(bool) # without this line the slot catches two signals: clicked() and clicked(bool) def on_pushButton_clicked(self, value): print "Clicked2 %d" % value def closeEvent(self, event): print "Close event" event.accept() def main(): app = QtGui.QApplication(sys.argv) MyApp = MyApplication() MyApp.app = app MyApp.run() if __name__ == '__main__': main()
Oproti jednoduché dědičnosti je tu ještě komplikace s tím, že se musí předem vytvořit typy form
a formbase
(volání funkce loadUiType
), které se pak použijí jako předek mé třídy.
Třída formbase
je třída z Qt, která je hlavním widgetem v souboru *.ui
. V tomto případě to je PyQt4.QtGui.QMainWindow
.
Třída form
je obyčejná třída, která dědí jenom object
(je to tedy tzv. new style class), není tedy potomkem žádné třídy z Qt
(to je důležité, neboť násobné dědění tříd z Qt
dokumentace zakazuje [3]). Dá se říct, že při použití funkce loadUi
dostanu instanci typu, který vrátí funkce loadUiType
.
výhody
nevýhody
*.ui
a nedá se za běhu změnit
V tomto případě to je použití funkce loadUiType
poněkud velký kalibr, který navíc nepřinesl žádné ulehčení pro programátora. Funkce loadUiType
asi najde využití ve větších aplikacích, kde je třeba vytvářet velké množství stejných oken.
Použití dvojité dědičnosti z mého pohledu nepřináší žádný přínos oproti jednoduché dědičnosti. Aplikací, kde by byla opravdu nutná, je asi hodně málo.
Qt
- http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/gotchas.html#multiple-inheritance
Tiskni
Sdílej:
udělej z toho článek:)
nedá se použít automatické připojení slotů a signálů (viz řádek self.ui.pushButton.clicked.connect...)
Dá, ak sa objekt bude deidiť z QObject-u (QMetaObject.connectSlotsByName).
K dvojitej dedičnosti by som dodal ešte veľkú nevýhodu - prvky ui sú public. U C++ sa to rieši privátnou dedičnosťou, takže tam sa dá ešte dvojitá dedičnosť tolerovať, ale v pythone radšej zvyšné spôsoby.
Inak pekný článok, po dlhom čase niečo linuxové ;)
Dá, ak sa objekt bude deidiť z QObject-u (QMetaObject.connectSlotsByName).U postupu bez dědičnosti jsem chtěl opravdu nic nedědit, takže jsem oželel vymoženosti frameworku Qt. S tím problémem u dvojité dědičnosti--public prvky -- to záleží na použití. Třeba se najde někdo, kdo chce míchat logiku aplikace s grafickým rozhraním, a u jednoúčelových miniaplikací na tom až tak nesejde.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.