Obsah
1. Interaktivní tvorba grafického uživatelského rozhraní s využitím nástroje Pygubu
2. Základní informace o knihovně Tkinter
3. Programová tvorba aplikací s grafickým uživatelským rozhraním v Tkinteru
4. Příklad jednoduché aplikace s imperativně popsaným GUI
5. Nástroje typu RAD – Rapid Application Development
7. Návrh UI prvního demonstračního příkladu s oknem a dvojicí ovládacích prvků
8. Zdrojový kód prvního demonstračního příkladu
9. Přidání hlavního menu do okna aplikace
10. První varianta zdrojového kódu druhého příkladu
11. Odstranění základních chyb: inicializace menu a specifikace adresáře s ikonami
12. Specifikace callback funkcí volaných při práci s uživatelským rozhraním
13. Zdrojový kód třetího demonstračního příkladu
15. Gambas – Gambas Almost Means BASIC
19. Repositář s demonstračními příklady
1. Interaktivní tvorba grafického uživatelského rozhraní s využitím nástroje Pygubu
Na stránkách serveru Root.cz jsme se již mnohokrát setkali s programovacím jazykem Python. Zabývali jsme se různými oblastmi nasazení tohoto v současnosti velmi populárního jazyka, a to jak na backendu, tak i na frontendu (i když je na tomto místě nutné poznamenat, že použití Pythonu na frontendu je dosti alternativním řešením, které trpí několika nedostatky – viz též článek Brython aneb použití jazyka Python ve skriptech přímo v prohlížeči). Python je ovšem velmi dobře použitelný i pro tvorbu desktopových aplikací s grafickým uživatelským rozhraním, což je téma, kterému jsme se podrobněji věnovali v samostatném seriálu; ostatně dnešní článek bude do tohoto seriálu taktéž zařazen. Dnes se seznámíme s poměrně jednoduchým nástrojem, který se jmenuje Pygubu. Jedná se o nástroj, který lze použít pro návrh grafického uživatelského rozhraní (typicky různých dialogů) desktopových aplikací vyvíjených v Pythonu s tím, že pro vlastní GUI bude použita knihovna Tkinter.
Obrázek 1: Obrazovka nástroje Pygubu designer určeného pro návrh grafického uživatelského rozhraní aplikací vytvářených v Pythonu.
2. Základní informace o knihovně Tkinter
Nástroj Pygubu designer a Pythonovská knihovna Pygubu jsou určeny pro návrh grafického uživatelského rozhraní založeného na knihovně Tkinter. S touto knihovnou jsme se již několikrát setkali, takže si v této kapitole pouze ve stručnosti připomeneme některé její základní vlastnosti.
Pythonovská knihovna nazvaná Tkinter tvoří rozhraní ke knihovně Tk, přičemž Tk je takzvaný toolkit (z pohledu programovacího jazyka se přitom jedná o knihovnu) určený pro jednoduchý a rychlý vývoj programů obsahujících grafické uživatelské rozhraní. Autorem tohoto toolkitu je, podobně jako v případě programovacího jazyka Tcl, John Ousterhout, mezi jehož zájmy v minulosti patřila automatizace (skriptovatelnost) aplikací a právě tvorba grafických uživatelských rozhraní. V minulosti byl tento toolkit velmi oblíbený, proto se dodával (a stále dodává) spolu s instalací programovacího jazyka Tcl a – což nás v kontextu dnešního článku zajímá mnohem více – i s jazykem Python společně s rozhraním Tkinter. I z tohoto důvodu se také v různých materiálech a dokumentech často setkáme se společným názvem Tcl/Tk. Samotný název Tk jednoduše znamená zkratku výše použitého slova ToolKit.
Důležitou vlastností knihovny Tk, která zůstala zachována i v Tkinteru, je úsporný, flexibilní a přitom čitelný zápis programu se specifikací ovládacích prvků, jejich umístění v oknech, vlastností i callback funkcí volaných v důsledku uživatelské činnosti. Udává se, že poměr psaného kódu v Tcl/Tk je vůči dnes již obstarožnímu a prakticky zapomenutému Motifu na hodnotách 1:20 a vůči základnímu Xlibu dokonce 1:100! (vše se samozřejmě týká tvorby grafického uživatelského rozhraní). Na tomto místě je však nutné podotknout, že Motif je z dnešního pohledu již zastaralá knihovna a navíc dnes mnoho moderních knihoven pro GUI podporuje deklaraci grafického uživatelského rozhraní v konfiguračních souborech, nikoli tedy programem (což má své výhody, ale samozřejmě i zápory). Nicméně i dnes představuje kombinace Tcl+Tk či ještě lépe Python+Tkinter poměrně dobrou volbu pro mnoho typů aplikací s GUI.
V průběhu postupného vývoje novějších (avšak ne nutně dokonalejších) programovacích nástrojů byla knihovna Tk kromě Tcl použita i v mnoha dalších programovacích jazycích. Pravděpodobně nejznámější je, samozřejmě kromě samotné dvojice Tcl/Tk, modul určený pro programovací jazyk Perl (Perl/Tk) a Python (Tkinter – Tk Interface, což je téma navazující kapitoly). Knihovnu Tk je však samozřejmě možné použít i v dalších programovacích jazycích, i když v mnoha případech ne tak snadně a efektivně, jako ze samotného Tcl. V praxi se často s výhodou používá či používalo spojení programů napsaných v programovacím jazyku C či s C++ s grafickým uživatelským rozhraním, které je vytvořeno pomocí Tcl a Tk.
3. Programová tvorba aplikací s grafickým uživatelským rozhraním v Tkinteru
Pro ilustraci si ukažme, jak jednoduchá může být (značně primitivní) aplikace s jedním oknem a textovým návěštím umístěným do tohoto okna. Celý zdrojový kód aplikace vypadá následovně:
#!/usr/bin/env python from tkinter import * root = Tk() label = Label(root, text="Hello world!") label.pack() root.mainloop()
Ve skutečnosti se však v současnosti používá takzvané TTk neboli Themed Tk) a program bude muset být nepatrně upraven. Konkrétně se přidá jeden import navíc a namísto Label() se zavolá konstruktor ttk.Label()), takže nová varianta programu bude vypadat takto:
#!/usr/bin/env python from tkinter import * from tkinter import ttk root = Tk() label = ttk.Label(root, text="Hello world!") label.pack() root.mainloop()
Na obou příkladech je patrné, že grafické uživatelské rozhraní se tvoří programově a není nijak odděleno od zbytku programového kódu. To má své výhody (kontextová nápověda a automatické doplňování), ale i mnoho nevýhod, které jsou odstraněny právě v projektu Pygubu.
4. Příklad jednoduché aplikace s imperativně popsaným GUI
Aby bylo ještě více patrné, jak úzce propojený je programový kód a vlastnosti grafického uživatelského rozhraní, pokud se používá knihovna Tkinter, ukážeme si ještě jeden demonstrační příklad. Tentokrát se do hlavního okna aplikace (neboli formuláře) vloží čtveřice tlačítek a umístí se do mřížky (grid). Navíc se explicitně určí, ke kterým okrajům buněk budou tlačítka „přilepena“. Po stisku všech tlačítek dojde k ukončení aplikace:
#!/usr/bin/env python from tkinter import * from tkinter import ttk import sys root = Tk() button1 = ttk.Button(root, text="1st btn", command=lambda: sys.exit(0)) button2 = ttk.Button(root, text="Second button", command=lambda: sys.exit(0)) button3 = ttk.Button(root, text="Third button", command=lambda: sys.exit(0)) button4 = ttk.Button(root, text="This is fourth button, the last one", command=lambda: sys.exit(0)) button1.grid(column=1, row=1, sticky="we") button2.grid(column=2, row=2, sticky="we") button3.grid(column=1, row=3, sticky="we") button4.grid(column=3, row=1, rowspan=4, sticky="nswe") root.mainloop()
Obrázek 2: Čtvrté tlačítko používá „přilepení“ ke všem stranám spojené buňky.
5. Nástroje typu RAD – Rapid Application Development
Programová a tedy de facto i ruční tvorba grafického uživatelského rozhraní trpí hned několika dosti zásadními neduhy. Zejména se jedná o mnohdy zbytečně nízkoúrovňovou práci, ovšem horší problém spočívá v tom, že požadavek na změnu GUI (například i pouhé posunutí nějakého ovládacího prvku) vyžaduje zásah do programového kódu, který navíc není či nemusí vždy být triviální a intuitivní. Tohoto úskalí si byly některé softwarové firmy vědomy již minimálně od začátku devadesátých let minulého století a právě z tohoto důvodu vznikly nástroje typu RAD neboli Rapid Application Development. Tyto nástroje umožňují nejenom velmi rychlou tvorbu grafického uživatelského rozhraní s využitím návrhářů GUI, ale – což je možná ještě důležitější – obousměrnou vazbu mezi grafickým uživatelským rozhraním a programovým kódem. Dnes se zkratkou RAD označují (alespoň v některých případech) i nástroje typu Glade, které ovšem ve skutečnosti „pouze“ slouží k návrhu GUI. Striktně řečeno do této kategorie Pygubu nespadá (není provedena obousměrná vazba s kódem), o čemž se ostatně přesvědčíme v následujícím textu.
Obrázek 3: Historická první verze vývojového prostředí Delphi 1.0 běžícího ve Windows 3.11.
6. Instalace nástroje Pygubu
Ve druhé části článku si ukážeme použití nástroje Pygubu prakticky. Samotná instalace Pygubu je přitom velmi jednoduchá, neboť Pygubu je dostupný jako běžný balíček pro Python nabízený na PyPi, a to včetně designeru. Pro instalaci tedy použijeme buď nástroj pip nebo pip3, v závislosti na tom, jakým způsobem je Python nakonfigurován:
$ pip3 install --user pygubu Collecting pygubu Downloading https://files.pythonhosted.org/packages/ac/e5/ce8d5241a3119045e77ae0e47a182415069e9a7419125d604d2cc4ffcc8f/pygubu-0.9.8.6-py3-none-any.whl (190kB) 100% |████████████████████████████████| 194kB 1.2MB/s Requirement already satisfied: appdirs>=1.3 in ./.local/lib/python3.6/site-packages (from pygubu) Installing collected packages: pygubu Successfully installed pygubu-0.9.8.6
$ python3 -m venv pygubu
Dále virtuální prostředí aktivujeme:
$ source pygubu/bin/activate
To by se mělo projevit úpravou výzvy (prompt):
(pygubu) bash-4.4$
Nakonec provedeme instalaci:
(pygubu) bash-4.4$ pip3 install --user pygubu
7. Návrh UI prvního demonstračního příkladu s oknem a dvojicí ovládacích prvků
Nyní již máme k dispozici všechny balíčky potřebné pro spuštění návrháře grafického uživatelského rozhraní. Samotný návrhář se jmenuje pygubu-designer a spustit ho můžeme stejným způsobem, jako jakoukoli jinou aplikaci:
$ pygubu-designer
Po spuštění by se mělo objevit hlavní okno návrháře grafického uživatelského rozhraní, které vypadá následovně:
Obrázek 4: Hlavní okno návrháře grafického uživatelského rozhraní s informacemi o verzi (od verze 0.10 došlo k oddělení Pygubu od Pygubu designeru).
V případě, že se pygubu-designer nespustí, je to většinou způsobeno tím, že není korektně nainstalována knihovna Tkinter – tato informace se zobrazí v terminálu.
Ukažme si nyní způsob vytvoření grafického uživatelského rozhraní velmi jednoduché aplikace obsahující pouze hlavní okno, do něhož je vloženo několik ovládacích prvků (widgetů). Okno, resp. přesněji řečeno prvek GUI, který okno představuje, se v knihovně Tkinter nazývá ttk.Frame a nalezneme ho v sekci Containers (protože se skutečně jedná o kontejner, do něhož se vkládají další prvky).
Obrázek 5: Rámec (okno aplikace) s dvojicí dalších prvků.
Do hlavního okna vložíme další dva ovládací prvky, například tlačítko a zatrhávací box. U prvků je nutné v panelu Layout zvolit, jakým způsobem mají být do hlavního okna (resp. jeho mřížky) vloženy a zda se mají „přilepit“ k jednotlivým okrajům buněk:
Obrázek 6: Specifikace umístění ovládacích prvků do mřížky hlavního okna.
Ve chvíli, kdy je návrh grafického uživatelského rozhraní aplikace ukončen, je nutné návrh uložit do souboru s koncovkou .ui. Ve skutečnosti se jedná o soubory založené na jazyku XML, o čemž se ostatně můžeme velmi snadno přesvědčit pohledem do těchto souborů:
<?xml version='1.0' encoding='utf-8'?> <interface> <object class="ttk.Frame" id="Frame_1"> <property name="height">200</property> <property name="width">200</property> <layout> <property name="column">3</property> <property name="propagate">True</property> <property name="row">3</property> </layout> <child> <object class="ttk.Button" id="Button_2"> <property name="text" translatable="yes">Button_2</property> <layout> <property name="column">0</property> <property name="propagate">True</property> <property name="row">0</property> <property name="sticky">ne</property> </layout> </object> </child> <child> <object class="ttk.Checkbutton" id="Checkbutton_1"> <property name="text" translatable="yes">Checkbutton_1</property> <layout> <property name="column">1</property> <property name="propagate">True</property> <property name="row">1</property> </layout> </object> </child> </object> </interface>
8. Zdrojový kód prvního demonstračního příkladu
Podívejme se nyní, jak by mohla vypadat první verze programu, který načte návrh grafického uživatelského rozhraní a použije ho pro zobrazení hlavního okna aplikace i jednotlivých ovládacích prvků. V programu je uvedeno (a okomentováno) několik kroků nutných pro inicializaci GUI i pro spuštění hlavní smyčky, která interně reaguje na jednotlivé události vznikající činností uživatele (tato smyčka se nazývá event loop a v určité podobě ji nalezneme u všech aplikací s grafickým uživatelským rozhraním, a ostatně i her založených na SDL2 či podobné knihovně):
"""Pygubu and Tkinter: user interface initialization.""" # example1.py import tkinter as tk import pygubu class Example1App: """Class representing a Tkinter based application.""" def __init__(self): """Construct and initializes all UI-related data structures.""" # step #1: Create a builder self.builder = builder = pygubu.Builder() # step #2: Load an ui file builder.add_from_file('example1.ui') # step #3: Create the mainwindow self.mainwindow = builder.get_object('Frame_1') def run(self): """Start the UI.""" self.mainwindow.mainloop() if __name__ == '__main__': # run the application app = Example1App() app.run()
Ze zdrojového kódu je patrné, že se v něm provádí několik činností:
- Zkonstruuje se instance třídy představující celou aplikaci.
- Zkonstruuje se instance třídy Builder.
- Načte se soubor s návrhem GUI – zde se inicializují všechny potřebné objekty.
- Získá se instance třídy představující hlavní okno aplikace.
- Spustí se smyčka pro zpracování událostí (představovaná metodou z knihovny Tkinter).
Obrázek 5: GUI demonstračního příkladu po jeho spuštění.
9. Přidání hlavního menu do okna aplikace
V dnešním druhém demonstračním příkladu založeném opět na nástroji pygubu si ukážeme, jakým způsobem se do hlavního okna aplikace vloží menu, přesněji řečeno pruh menu s několika položkami, přičemž se pro výběru nějaké položky zobrazí plnohodnotné rozbalovací menu (pull-down menu). V rozbalovacím menu se budou nacházet jednotlivé příkazy, které navíc budou obsahovat ikony reprezentované samostatnými soubory typu PNG. Průběh přidání menu je zobrazen na následujících screenshotech:
Obrázek 6: Menu se připravuje nezávisle na hlavním oknu aplikace (propojení je provedeno až v programu, nikoli v UI při návrhu). U jednotlivých položek lze zvolit index znaku, který bude podtržen a současně bude sloužit pro rychlý výběr položky menu. Navíc u jednotlivých položek můžeme specifikovat soubor s ikonou i umístění této ikony s textem (vlastnost compound).
Obrázek 7: Přidat je možné i vizuální separátor mezi jednotlivými položkami menu.
Obrázek 8: Položky lze v rámci menu přeskupit klávesovými zkratkami Ctrl+I a Ctrl+K (což pravděpodobně odkazuje na alternativní klávesové zkratky pro posun kurzoru – IJKL).
Výsledný XML soubor s popisem návrhu UI aplikace by měl vypadat následovně:
<?xml version='1.0' encoding='utf-8'?> <interface> <object class="tk.Menu" id="MainMenu"> <child> <object class="tk.Menuitem.Submenu" id="FileMenu"> <property name="font">TkDefaultFont</property> <property name="label" translatable="yes">File</property> <property name="relief">raised</property> <property name="state">normal</property> <property name="tearoff">false</property> <property name="underline">0</property> <child> <object class="tk.Menuitem.Command" id="Command_New"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">document-new.png</property> <property name="label" translatable="yes">New</property> <property name="state">normal</property> <property name="underline">0</property> </object> </child> <child> <object class="tk.Menuitem.Command" id="Command_Open"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">document-open.png</property> <property name="label" translatable="yes">Open</property> <property name="underline">0</property> </object> </child> <child> <object class="tk.Menuitem.Separator" id="Separator_1" /> </child> <child> <object class="tk.Menuitem.Command" id="Command_Quit"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">application-exit.png</property> <property name="label" translatable="yes">Quit</property> <property name="underline">0</property> </object> </child> </object> </child> <child> <object class="tk.Menuitem.Submenu" id="EditMenu"> <property name="label" translatable="yes">Edit</property> <property name="underline">0</property> <child> <object class="tk.Menuitem.Command" id="Command_Cut"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">edit-cut.png</property> <property name="label" translatable="yes">Cut</property> </object> </child> <child> <object class="tk.Menuitem.Command" id="Command_Copy"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">edit-copy.png</property> <property name="label" translatable="yes">Copy</property> </object> </child> <child> <object class="tk.Menuitem.Command" id="Command_Paste"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">edit-paste.png</property> <property name="label" translatable="yes">Paste</property> </object> </child> <child> <object class="tk.Menuitem.Separator" id="Separator_2" /> </child> <child> <object class="tk.Menuitem.Command" id="Command_Delete"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">edit-delete.png</property> <property name="label" translatable="yes">Delete</property> <property name="state">disabled</property> </object> </child> </object> </child> </object> <object class="ttk.Frame" id="MainWindow"> <property name="height">200</property> <property name="width">200</property> <layout> <property name="column">0</property> <property name="propagate">True</property> <property name="row">0</property> </layout> </object> </interface>
10. První varianta zdrojového kódu druhého příkladu
První varianta zdrojového kódu dnešního druhého demonstračního příkladu může vypadat následovně. Používáme zde poněkud jinou šablonu, než v příkladu prvním, ovšem této šablony se budeme držet i v dalších příkladech:
"""Pygubu and Tkinter: main menu in main window (not working properly).""" # example2A.py import tkinter as tk import pygubu class Example2App(pygubu.TkApplication): """Class representing a Tkinter based application.""" def _create_ui(self): """Construct and initializes all UI-related data structures.""" # step #1: Create a builder self.builder = builder = pygubu.Builder() # step #2: Load an ui file builder.add_from_file('example2.ui') # step #3: Create the mainwindow self.mainwindow = builder.get_object('MainWindow', self.master) # step #4: Set main menu self.mainmenu = menu = builder.get_object('MainMenu', self.master) self.set_menu(menu) # step $5: Configure callbacks builder.connect_callbacks(self) if __name__ == '__main__': # run the application app = Example2App() app.run()
11. Odstranění základních chyb: inicializace menu a specifikace adresáře s ikonami
Předchozí verze demonstračního příkladu ve skutečnosti obsahuje jednu zásadní chybu, která se projeví při snaze o jeho spuštění:
$ python3 example2A.py Traceback (most recent call last): File "example2A.py", line 33, in app = Example2App() File "/home/ptisnovs/.local/lib/python3.6/site-packages/pygubu/__init__.py", line 24, in __init__ self.toplevel = master.winfo_toplevel() AttributeError: 'NoneType' object has no attribute 'winfo_toplevel'
Tuto chybu lze napravit přidáním příkazu zavolaného ještě před inicializací třídy představující aplikaci:
root = tk.Tk()
Zdrojový kód příkladu tedy bude nepatrně odlišný:
"""Pygubu and Tkinter: main menu in main window (resources not setup).""" # example2B.py import tkinter as tk import pygubu class Example2App(pygubu.TkApplication): """Class representing a Tkinter based application.""" def _create_ui(self): """Construct and initializes all UI-related data structures.""" # step #1: Create a builder self.builder = builder = pygubu.Builder() # step #2: Load an ui file builder.add_from_file('example2.ui') # step #3: Create the mainwindow self.mainwindow = builder.get_object('MainWindow', self.master) # step #4: Set main menu self.mainmenu = menu = builder.get_object('MainMenu', self.master) self.set_menu(menu) # step $5: Configure callbacks builder.connect_callbacks(self) if __name__ == '__main__': # needed to have a menu root = tk.Tk() # run the application app = Example2App(root) app.run()
I při spuštění druhé varianty však nalezneme jednu chybu:
$ python3 example2B.py WARNING:pygubu.builder:Image 'document-new.png' not found in resource paths. WARNING:pygubu.builder:Image 'document-open.png' not found in resource paths. WARNING:pygubu.builder:Image 'application-exit.png' not found in resource paths. WARNING:pygubu.builder:Image 'edit-cut.png' not found in resource paths. WARNING:pygubu.builder:Image 'edit-copy.png' not found in resource paths. WARNING:pygubu.builder:Image 'edit-paste.png' not found in resource paths. WARNING:pygubu.builder:Image 'edit-delete.png' not found in resource paths.
Tato chyba spočívá v tom, že program nenalezne soubory s ikonami. Ještě před vytvořením menu a hlavního okna je nutné specifikovat cestu k těmto souborům:
# step #2B: Specify path to images and other resources builder.add_resource_path(".")
Upravený, nyní již plně funkční příklad, vypadá takto:
"""Pygubu and Tkinter: main menu in main window (working example).""" # example2C.py import tkinter as tk import pygubu class Example2App(pygubu.TkApplication): """Class representing a Tkinter based application.""" def _create_ui(self): """Construct and initializes all UI-related data structures.""" # step #1: Create a builder self.builder = builder = pygubu.Builder() # step #2: Load an ui file builder.add_from_file('example2.ui') # step #2B: Specify path to images and other resources builder.add_resource_path(".") # step #3: Create the mainwindow self.mainwindow = builder.get_object('MainWindow', self.master) # step #4: Set main menu self.mainmenu = menu = builder.get_object('MainMenu', self.master) self.set_menu(menu) # step $5: Configure callbacks builder.connect_callbacks(self) if __name__ == '__main__': # needed to have a menu root = tk.Tk() # run the application app = Example2App(root) app.run()
Obrázek 9: Aplikace s menu, položky menu mají přiřazeny ikony.
12. Specifikace callback funkcí volaných při práci s uživatelským rozhraním
Dalším neméně důležitým krokem je specifikace callback funkcí, které jsou volány ve chvíli, kdy uživatel v grafickém uživatelském rozhraní provede nějakou operaci. V tomto článku si ukážeme nejpřímější řešení, které spočívá v tom, že se u každého ovládacího prvku (tedy položky menu, popř. u tlačítka) zvolí jméno funkce vyvolané po výběru nebo stisku daného ovládacího elementu:
Obrázek 10: Vyplnění políčka Specific/Command u nového tlačítka přidaného do návrhu GUI vyvíjené aplikace.
Z výpisu obsahu souboru s popisem návrhu GUI je patrné, že jména funkcí byla přidána jak pro položku menu File→Quit, tak i pro nové tlačítko vložené do formuláře:
<?xml version='1.0' encoding='utf-8'?> <interface> <object class="tk.Menu" id="MainMenu"> <child> <object class="tk.Menuitem.Submenu" id="FileMenu"> <property name="font">TkDefaultFont</property> <property name="label" translatable="yes">File</property> <property name="relief">raised</property> <property name="state">normal</property> <property name="tearoff">false</property> <property name="underline">0</property> <child> <object class="tk.Menuitem.Command" id="Command_New"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">document-new.png</property> <property name="label" translatable="yes">New</property> <property name="state">normal</property> <property name="underline">0</property> </object> </child> <child> <object class="tk.Menuitem.Command" id="Command_Open"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">document-open.png</property> <property name="label" translatable="yes">Open</property> <property name="underline">0</property> </object> </child> <child> <object class="tk.Menuitem.Separator" id="Separator_1" /> </child> <child> <object class="tk.Menuitem.Command" id="Command_Quit"> <property name="command">on_command_quit_selected</property> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">application-exit.png</property> <property name="label" translatable="yes">Quit</property> <property name="underline">0</property> </object> </child> </object> </child> <child> <object class="tk.Menuitem.Submenu" id="EditMenu"> <property name="label" translatable="yes">Edit</property> <property name="underline">0</property> <child> <object class="tk.Menuitem.Command" id="Command_Cut"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">edit-cut.png</property> <property name="label" translatable="yes">Cut</property> </object> </child> <child> <object class="tk.Menuitem.Command" id="Command_Copy"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">edit-copy.png</property> <property name="label" translatable="yes">Copy</property> </object> </child> <child> <object class="tk.Menuitem.Command" id="Command_Paste"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">edit-paste.png</property> <property name="label" translatable="yes">Paste</property> </object> </child> <child> <object class="tk.Menuitem.Separator" id="Separator_2" /> </child> <child> <object class="tk.Menuitem.Command" id="Command_Delete"> <property name="command_id_arg">false</property> <property name="compound">left</property> <property name="image">edit-delete.png</property> <property name="label" translatable="yes">Delete</property> <property name="state">disabled</property> </object> </child> </object> </child> </object> <object class="ttk.Frame" id="MainWindow"> <property name="height">200</property> <property name="width">200</property> <layout> <property name="column">0</property> <property name="propagate">True</property> <property name="row">0</property> </layout> <child> <object class="ttk.Button" id="Button_Hello"> <property name="command">on_button_clicked</property> <property name="compound">top</property> <property name="state">normal</property> <property name="text" translatable="yes">Quit</property> <property name="underline">0</property> <layout> <property name="column">0</property> <property name="propagate">True</property> <property name="row">0</property> </layout> </object> </child> </object> </interface>
13. Zdrojový kód třetího demonstračního příkladu
Ve zdrojovém kódu příkladu je nutné provést dvě úpravy. První úprava spočívá v nakonfigurování callback funkcí:
# step $5: Configure callbacks builder.connect_callbacks(self)
Nesmíme samozřejmě zapomenout na vlastní callback funkce (ve skutečnosti se jedná o metody, ovšem princip je totožný). Jména funkcí odpovídají jménům specifikovaným v Pygubu-designeru:
def on_button_clicked(self): """Define handler for Quit button.""" tk.messagebox.showinfo('Message', 'You clicked on Quit button') root.destroy() def on_command_quit_selected(self): """Define handler for Quit command.""" tk.messagebox.showinfo('Message', 'You selected Quit command') root.destroy()
Úplný zdrojový kód tohoto příkladu vypadá následovně:
"""Pygubu and Tkinter: main menu in main window, callback functions (working example).""" # example3.py import tkinter as tk from tkinter import messagebox import pygubu class Example3App(pygubu.TkApplication): """Class representing a Tkinter based application.""" def _create_ui(self): """Construct and initializes all UI-related data structures.""" # step #1: Create a builder self.builder = builder = pygubu.Builder() # step #2: Load an ui file builder.add_from_file('example3.ui') # step #2B: Specify path to images and other resources builder.add_resource_path(".") # step #3: Create the mainwindow self.mainwindow = builder.get_object('MainWindow', self.master) # step #4: Set main menu self.mainmenu = menu = builder.get_object('MainMenu', self.master) self.set_menu(menu) # step $5: Configure callbacks builder.connect_callbacks(self) def on_button_clicked(self): """Define handler for Quit button.""" tk.messagebox.showinfo('Message', 'You clicked on Quit button') root.destroy() def on_command_quit_selected(self): """Define handler for Quit command.""" tk.messagebox.showinfo('Message', 'You selected Quit command') root.destroy() if __name__ == '__main__': # needed to have a menu root = tk.Tk() # run the application app = Example3App(root) app.run()
Obrázek 11: Takto vypadá třetí demonstrační příklad po spuštění.
Obrázek 12: Akce provedená po stisku tlačítka Quit.
14. Možná alternativní řešení
V závěru dnešního článku se ve stručnosti seznámíme s některými alternativními projekty, které vývojářům většinou nabízí plnohodnotné RAD (na rozdíl od poměrně úzce specializovaného projektu pygubu). Do této skupiny aplikací patří především projekt Lazarus založený na programovacím jazyku Object Pascal, resp. přesněji řečeno na jeho volně šiřitelné variantě, dále pak projekt Gambas, jenž vývojářům přináší obdobu nechvalně proslulého programovacího jazyka Visual Basic (ovšem spíše se jedná o dialekt) a konečně nesmíme zapomenout ani na projekt Qt Creator (dříve Qt Designer). S příchodem aplikací založených na webových technologiích sice význam těchto nástrojů klesl, ovšem například pro vnitropodnikové aplikace se stále (podle mého skromného názoru) jedná o velmi snadno a především rychle použitelnou technologii vyžadující méně vstupních znalostí (a méně systémových prostředků na straně uživatele).
15. Gambas – Gambas Almost Means BASIC
První „alternativní“ integrované vývojové prostředí s možnostmi RAD, s nímž se v dnešním článku alespoň ve stručnosti seznámíme, se jmenuje Gambas (Gambas Almost Means BASic). Jak již druhá část jména tohoto IDE napovídá, jedná se o integrované vývojové prostředí, v němž se používá programovací jazyk odvozený od Visual Basicu. Autor Gambasu, jímž je programátor Benoit Minisini, sám v přiložené dokumentaci píše, že na vývoji Gambasu začal původně pracovat především z toho důvodu, že mu množství chyb a různých nekonzistencí, které můžeme najít v původním Visual Basicu (nikoli VB .NET) prostě připadalo rozkošné, takže se nechal Visual Basicem inspirovat (a tím pádem demonstroval tvrzení „worse is better“).
Obrázek 13: Dnes již historická verze Gambasu 1.0.
Obrázek 14: SDI prostředí Gambasu.
Ovšem tím hlavním důvodem, proč se v tomto článku vůbec integrovaným vývojovým prostředím Gambas zabýváme, však není zvolený programovací jazyk, ale další velmi důležitá součást tohoto IDE – jedná se totiž o interaktivní grafický návrhář formulářů, díky jehož existenci je možné velmi jednoduše a především rychle a navíc s relativně velkým komfortem vytvořit i poměrně složitou aplikaci s plnohodnotným grafickým uživatelským rozhraním.
Obrázek 15: Dialog pro vytvoření nového projektu v Gambasu.
Interaktivní návrhář formulářů v prostředí Gambas je samozřejmě obousměrně propojen s ostatními částmi integrovaného vývojového prostředí, zejména s programátorským editorem zdrojových kódů. To například znamená, že pokud se na formuláři vytvoří nové tlačítko, je možné ihned začít psát obslužný kód zavolaný ve chvíli, kdy bude toto tlačítko na reálném GUI použito. Způsob propojení návrháře a programátorského editoru je obdobný způsobu, který byl využit v již výše zmíněném Visual Basicu (Microsoft) a později taktéž ve slavném Delphi vytvořeném a prodávaném společností Borland. Později došlo k rozšíření tohoto úspěšného konceptu i do dalších IDE.
16. Lazarus
Dalším integrovaným vývojovým prostředím, s nímž se v dnešním článku alespoň ve stručnosti seznámíme, je multiplatformní IDE nazvané Lazarus, které lze v současnosti provozovat na Linuxu, FreeBSD, Mac OS X i na systémech Microsoft Windows. Jedná se o programátorské prostředí, které se snaží o napodobení stylu vývoje aplikací použitého ve známém a především v minulosti velmi populárním komerčním IDE nazvaném Delphi, jenž bylo vyvíjeno původně společností Borland a později firmou Embarcadero Technologies (Delphi bylo původně určeno pro šestnáctibitový systém Windows 3.x a později bylo upraveno pro 32bitové systémy Windows 95, Windows NT i navazující řadu operačních systémů společnosti Microsoft).
Obrázek 16: Konfigurace prostředí Lazarus.
Vzhledem k tomu, že je Delphi založeno na programovacím jazyku Object Pascal, je v integrovaném vývojovém prostředí Lazarus vytvořeno propojení s multiplatformním překladačem fpc programovacího jazyka Free Pascal a samotné prostředí obsahuje jak poměrně pokročilý programátorský editor (se zvýrazněním syntaxe, foldingem, šablonami atd.) tak i interaktivní grafický editor určený pro návrh formulářů aplikace. Nesmíme samozřejmě zapomenout ani na ladicí program (debugger), který je do Lazaru plně integrován (stejně jako debugger do Delphi – ostatně vývojová prostředí firmy Borland byla oblíbena mj. i díky kvalitním interním debuggerům).
Obrázek 17: Integrované vývojové prostředí Lazara.
Obrázek 18: Informace o použité verzi.
Obrázek 19: Návrh formuláře se ihned projeví i v programovém kódu.
17. PySide a Qt Creator
Ještě lepší alternativou k Pygubu může být PySide založené na frameworku Qt. Zde se používají tzv. UI soubory, popř. QML (Qt Modeling Language).
Do souborů UI se ukládají popisy jednotlivých uživatelsky definovaných ovládacích prvků (widgetů), formulářů i celých oken. Jedná se o formát založený na XML, což znamená, že tyto soubory je možné relativně snadno zpracovat i dalšími nástroji (XML editory atd.). Tyto soubory lze vytvářet například Qt Creatorem. Ve chvíli, kdy je soubor UI s popisem nějakého prvku GUI (widget, formulář, okno) vytvořen, dá se použít několika různými způsoby:
- Soubor UI lze načíst do běžící aplikace naprogramované v C++ s využitím třídy QUiLoader.
- Soubor UI lze načíst do běžící aplikace naprogramované v Pythonu, opět s využitím třídy QUiLoader.
- Soubor UI je možné konvertovat na zdrojový kód v C++ nástrojemUIC (User Interface Compiler).
- Soubor UI je možné konvertovat na zdrojový kód v Pythonu nástrojemPython-uic. Výsledek by měl být dobře pochopitelný, protože se používají postupy, které jsme si popsali v předchozích článcích.
Jedním z nástrojů, který dokáže vytvářet soubory .ui, je aplikace nazvaná Qt Creator. Jak již název tohoto nástroje napovídá, jedná se o IDE určené primárně pro desktopové prostředí KDE založené na knihovně Qt, ovšem ve skutečnosti nám samozřejmě nic nebrání použít Qt Creator i na desktopu se spuštěným Gnome Shellem, popř. nějakým „alternativním“ desktopovým prostředím (jediným problémem bude nepatrně delší čas spouštění zapříčiněný načítáním knihovny Qt do paměti). To, že je Qt Creator původně orientován na vývoj aplikací postavených na knihovně Qt a tím pádem i na programovacím jazyku C++, je patrné již při pohledu na screenshoty, kde jsou ukázány dialogy zobrazené při vytváření nového projektu.
Obrázek 20: Vítací obrazovka dnes již poněkud starší verze Qt Creatoru.
Formát souborů vytvářených Qt Creatorem se však odlišuje od stejně pojmenovaných souborů vytvářených systémem Pygubu:
<ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow" > <property name="geometry" > <rect> <x>0</x> <y>0</y> <width>400</width> <height>300</height> </rect> </property> <property name="windowTitle" > <string>MainWindow</string> </property> <widget class="QMenuBar" name="menuBar" /> <widget class="QToolBar" name="mainToolBar" /> <widget class="QWidget" name="centralWidget" /> <widget class="QStatusBar" name="statusBar" /> </widget> <layoutDefault spacing="6" margin="11" /> <pixmapfunction></pixmapfunction> <resources/> <connections/> </ui>
18. Závěrečné zhodnocení
Nástroj Pygubu, s jehož základními možnostmi jsme se seznámili především v polovině dnešního článku, nemůžeme považovat za plnohodnotný nástroj typu RAD a ve skutečnosti to ani není jeho cílem. Jedná se „pouze“ o relativně jednoduchou utilitu a současně i knihovnu pro programovací jazyk Python umožňující interaktivní návrh formulářů, popř. uceleného grafického uživatelského rozhraní složeného z jednotlivých formulářů a typicky i hlavního okna aplikace. Zda se jedná o výhodu či nevýhodu již záleží na konkrétním způsobu použití, protože některým vývojářům může vadit, že Pygubu není integrován do nějakého IDE pro Python (Visual Studio Code, Pycharm atd.); navíc zde existuje nutnost vytvářet programový kód zvlášť. Díky tomu, že je GUI založeno na knihovně Tkinter, je výsledná aplikace snadno přenositelná na různé operační systémy, přičemž nový vzhled ovládacích prvků přidaný do TCL/Tk do značné míry odstranil jednu z velkých nevýhod této knihovny – „retro“ styl aplikací založených na Tkinteru, resp. přesněji řečeno na kombinaci TCL/Tk, kterou Tkinter interně volá.
19. Repositář s demonstračními příklady
Zdrojové kódy všech tří dnes popsaných demonstračních příkladů určených pro Python 3 a nejnovější stabilní verzi knihovny Pygubu (a pochopitelně i pro Pygubu designer) byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
Ve druhém a třetím demonstračním příkladu jsou navíc použity i ikony v menu. Tyto ikony jsou uloženy samostatně ve formátu PNG a měly by být umístěny do stejného adresáře, ze kterého se spouští aplikace:
20. Odkazy na Internetu
- Seriál Grafické uživatelské rozhraní v Pythonu
https://www.root.cz/serialy/graficke-uzivatelske-rozhrani-v-pythonu/ - Pygubu na PyPi
https://pypi.org/project/pygubu/ - Repositář projektu Pygubu
https://github.com/alejandroautalan/pygubu - pygubu-designer na PyPi
https://pypi.org/project/pygubu-designer/ - Repositář projektu pygubu-designer
https://github.com/alejandroautalan/pygubu-designer - Pygubu Wiki
https://github.com/alejandroautalan/pygubu/wiki - How to install Tkinter in Python?
https://www.tutorialspoint.com/how-to-install-tkinter-in-python - Stránky projektu Glade
https://glade.gnome.org/ - Hra Breakout napísaná v Tkinteri
https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/ - Brython aneb použití jazyka Python ve skriptech přímo v prohlížeči
https://www.root.cz/clanky/brython-aneb-pouziti-jazyka-python-ve-skriptech-primo-v-prohlizeci/ - 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 - Rapid application development
https://en.wikipedia.org/wiki/Rapid_application_development - Non-functional requirement
https://en.wikipedia.org/wiki/Non-functional_requirement - Graphical user interface builder
https://en.wikipedia.org/wiki/Graphical_user_interface_builder - User interface markup language
https://en.wikipedia.org/wiki/User_interface_markup_language - Top 10 programming languages that developers hate the most
https://www.techworm.net/2017/11/perl-hated-programming-language-developers-says-report.html