Obsah
1. Pygubu designer ve funkci jednosměrného RAD
2. Vygenerování ukázkového zdrojového kódu
3. Vygenerování kódu založeného na knihovně Tkinter
4. Práce s dialogem obsahujícím zaškrtávací pole
5. Kostra demonstračního příkladu pro zobrazení dialogu se zaškrtávacími poli
6. Automatické nastavení proměnných při změně stavu zaškrtávacího pole
7. Zobrazení aktuálního stavu zaškrtávacích polí
8. Nastavení výchozího stavu zaškrtávacích polí
9. Widget představující vstupní textové pole
10. Realizace formuláře s textovým polem
11. Průběžná validace textu zapisovaného do textového pole
12. Realizace formuláře s validovaným textovým polem
13. Práce s grafikou v knihovně Tkinter
15. Realizace formuláře s kreslicím plátnem (canvasem)
16. První skript, který zobrazí formulář s prázdným kreslicím plátnem
18. Ukázka dalších možností kreslicího plátna
19. Repositář s demonstračními příklady
1. Pygubu designer ve funkci jednosměrného RAD
V předchozích dvou článcích jsme si na několika místech řekli, že Pygubu designer nelze považovat za plnohodnotný nástroj typu RAD (Rapid Application Development), a to především z toho důvodu, že se jedná o „pouhého“ návrháře GUI bez obousměrné vazby na zdrojový kód. Nicméně v poslední verzi Pygubu-designeru se objevila první vlaštovka RAD nástroje – Pygubu designer totiž nyní umožňuje z návrhu grafického uživatelského rozhraní vygenerovat dva typy zdrojových kódů, které budou ukázány v navazující dvojici kapitol.
Vraťme se nyní k poslední aplikaci, která byla popsána minule. Uživatelská část této aplikace se skládá z okna, do kterého je vložena pětice tlačítek:
Obrázek 1: Grafický návrh uživatelského rozhraní aplikace.
Tlačítka budou reagovat na operace provedené uživatelem, protože je u každého specifikována callback funkce (resp. přesněji řečeno metoda), která se zavolá po stisku tohoto tlačítka (libovolným ovladačem – klávesnicí nebo myší):
Obrázek 2: Nastavení callback funkce (resp. metody) i dalších parametrů jednotlivých tlačítek.
Při návrhu tohoto formuláře byl použit grid layout neboli mřížka, do které se jednotlivé ovládací prvky vkládají. U každého prvku lze určit, ke kterým okrajům buňky bude „přilepen“:
Obrázek 3: Použití grid layoutu (mřížky) a určení, ke kterým okrajům mřížky se mají tlačítka přilepit (sticky).
Vygenerovaný soubor s návrhem grafického uživatelského rozhraní by měl vypadat následovně:
<?xml version='1.0' encoding='utf-8'?> <interface version="1.1"> <object class="ttk.Frame" id="MainWindow"> <property name="height">200</property> <property name="width">200</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> <child> <object class="ttk.Button" id="ok_cancel"> <property cbtype="simple" name="command" type="command">on_button_ok_cancel_click</property> <property name="text" translatable="yes">Ok + Cancel</property> <layout manager="grid"> <property name="column">0</property> <property name="propagate">True</property> <property name="row">0</property> <property name="sticky">ew</property> </layout> </object> </child> <child> <object class="ttk.Button" id="retry_cancel"> <property cbtype="simple" name="command" type="command">on_button_retry_cancel_click</property> <property name="text" translatable="yes">Retry + Cancel</property> <layout manager="grid"> <property name="column">1</property> <property name="propagate">True</property> <property name="row">0</property> <property name="sticky">ew</property> </layout> </object> </child> <child> <object class="ttk.Button" id="yes_no"> <property cbtype="simple" name="command" type="command">on_button_yes_no_click</property> <property name="text" translatable="yes">Yes + No</property> <layout manager="grid"> <property name="column">0</property> <property name="propagate">True</property> <property name="row">1</property> <property name="sticky">ew</property> </layout> </object> </child> <child> <object class="ttk.Button" id="exit"> <property cbtype="simple" name="command" type="command">on_button_exit_click</property> <property name="style">Red.TButton</property> <property name="text" translatable="yes">Exit</property> <layout manager="grid"> <property name="column">1</property> <property name="propagate">True</property> <property name="row">4</property> <property name="sticky">ew</property> </layout> </object> </child> <child> <object class="ttk.Button" id="question"> <property cbtype="simple" name="command" type="command">on_button_question_click</property> <property name="text" translatable="yes">Question</property> <layout manager="grid"> <property name="column">1</property> <property name="propagate">True</property> <property name="row">1</property> <property name="sticky">ew</property> </layout> </object> </child> </object> </interface>
2. Vygenerování ukázkového zdrojového kódu
V horní části uživatelského rozhraní nástroje Pygubu je možné se přepnout mezi taby Design a Code (což je právě ona novinka, která v dřívějších verzích nebyla dostupná). V případě, že se přepnete na tab Code, lze si tlačítkem Application nechat vygenerovat kostru aplikace (ovšem bez implementace callback metod atd.):
Obrázek 4: Vygenerování základní struktury kódu aplikace.
Vygenerovaný kód, který je závislý na knihovně pygubu, vypadá následovně:
import os import pygubu PROJECT_PATH = os.path.dirname(__file__) PROJECT_UI = os.path.join(PROJECT_PATH, "example7.ui") class Example7App: def __init__(self): self.builder = builder = pygubu.Builder() builder.add_resource_path(PROJECT_PATH) builder.add_from_file(PROJECT_UI) self.mainwindow = builder.get_object('MainWindow') builder.connect_callbacks(self) def on_button_ok_cancel_click(self): pass def on_button_retry_cancel_click(self): pass def on_button_yes_no_click(self): pass def on_button_exit_click(self): pass def on_button_question_click(self): pass def run(self): self.mainwindow.mainloop() if __name__ == '__main__': import tkinter as tk root = tk.Tk() app = Example7App(root) app.run()
Jen pro zajímavost se podívejme na to, jak vypadala aplikace, kterou jsem vytvořil ručně v rámci předchozího článku:
"""Pygubu and Tkinter: changing style.""" # Example7.py import tkinter as tk from tkinter import ttk from tkinter import messagebox import pygubu class Example7App(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('example7.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: Configure callbacks builder.connect_callbacks(self) root.bind('<Control-q>', lambda event: self.on_button_exit_click()) def on_button_ok_cancel_click(self): messagebox.askokcancel("askokcancel()", "askokcancel()") def on_button_yes_no_click(self): messagebox.askyesno("askyesno()", "askyesno()") def on_button_retry_cancel_click(self): messagebox.askretrycancel("askretrycancel()", "askretrycancel()") def on_button_question_click(self): messagebox.askquestion("askquestion()", "askquestion()") def on_button_exit_click(self): root.destroy() if __name__ == '__main__': # needed to have a menu root = tk.Tk() # style style = ttk.Style() # run the application app = Example7App(root) app.run()
3. Vygenerování kódu založeného na knihovně Tkinter
Druhé tlačítko, které na tabu Code nalezneme, se jmenuje Code Script. Toto tlačítko taktéž vygeneruje zdrojový kód, ovšem tento zdrojový kód již na knihovně pygubu nezávisí – jedinou závislostí je knihovna Tkinter, která je v Pythonu standardní. To vlastně znamená, že následující (vygenerovaný) kód by měl být spustitelný na každém počítači se standardně nainstalovaným Pythonem:
import tkinter as tk import tkinter.ttk as ttk class Example7App: def __init__(self, master=None): # build ui self.MainWindow = ttk.Frame(master) self.ok_cancel = ttk.Button(self.MainWindow) self.ok_cancel.configure(text='Ok + Cancel') self.ok_cancel.grid(column='0', row='0', sticky='ew') self.ok_cancel.configure(command=self.on_button_ok_cancel_click) self.retry_cancel = ttk.Button(self.MainWindow) self.retry_cancel.configure(text='Retry + Cancel') self.retry_cancel.grid(column='1', row='0', sticky='ew') self.retry_cancel.configure(command=self.on_button_retry_cancel_click) self.yes_no = ttk.Button(self.MainWindow) self.yes_no.configure(text='Yes + No') self.yes_no.grid(column='0', row='1', sticky='ew') self.yes_no.configure(command=self.on_button_yes_no_click) self.exit = ttk.Button(self.MainWindow) self.exit.configure(style='Red.TButton', text='Exit') self.exit.grid(column='1', row='4', sticky='ew') self.exit.configure(command=self.on_button_exit_click) self.question = ttk.Button(self.MainWindow) self.question.configure(text='Question') self.question.grid(column='1', row='1', sticky='ew') self.question.configure(command=self.on_button_question_click) self.MainWindow.configure(height='200', width='200') self.MainWindow.pack(side='top') # Main widget self.mainwindow = self.MainWindow def on_button_ok_cancel_click(self): pass def on_button_retry_cancel_click(self): pass def on_button_yes_no_click(self): pass def on_button_exit_click(self): pass def on_button_question_click(self): pass def run(self): self.mainwindow.mainloop() if __name__ == '__main__': import tkinter as tk root = tk.Tk() app = Example7App(root) app.run()
4. Práce s dialogem obsahujícím zaškrtávací pole
Nyní již umíme reagovat na výběr těch ovládacích prvků, které přímo spouští nějakou akci. Typicky se jedná o položky menu a taktéž o příkazová tlačítka. Ovšem mnohdy se setkáme s nutností použít i zaškrtávací pole neboli checkboxy. I tyto prvky (widgety) pochopitelně v knihovně Tkinter existují a jsou přímo podporovány i Pygubu designerem. Vytvoříme si tedy návrh aplikace se třemi zaškrtávacími poli a taktéž s tlačítkem, které bude použito pro zobrazení stavu všech polí:
Obrázek 5: Návrh grafického uživatelského rozhraní aplikace se třemi zaškrtávacími poli.
Obrázek 6: Tlačítko Display Selections spustí callback metodu on_button_display_selections_click.
Obrázek 7: Pro změnu oproti předchozímu demonstračními příkladu nyní použijeme pack layout manager, kde u tlačítek zvolíme, že v horizontálním směru mají vyplnit celou šířku okna (parametr fill).
Výsledný soubor s popisem návrhu grafického uživatelského rozhraní aplikace by měl vypadat následovně:
<?xml version='1.0' encoding='utf-8'?> <interface version="1.1"> <object class="ttk.Frame" id="MainWindow"> <property name="height">200</property> <property name="width">200</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> <child> <object class="ttk.Checkbutton" id="checkbutton_a"> <property name="text" translatable="yes">Selection A</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Checkbutton" id="checkbutton_b"> <property name="text" translatable="yes">Selection B</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Checkbutton" id="checkbutton_c"> <property name="text" translatable="yes">Selection C</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Button" id="button_display_values"> <property cbtype="simple" name="command" type="command">on_button_display_selections_click</property> <property name="compound">left</property> <property name="image">display.png</property> <property name="text" translatable="yes">Display selections</property> <layout manager="pack"> <property name="expand">true</property> <property name="fill">x</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Button" id="button_quit"> <property cbtype="simple" name="command" type="command">on_quit_button_click</property> <property name="compound">left</property> <property name="image">application-exit.png</property> <property name="text" translatable="yes">Quit</property> <layout manager="pack"> <property name="expand">true</property> <property name="fill">x</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> </object> </interface>
5. Kostra demonstračního příkladu pro zobrazení dialogu se zaškrtávacími poli
Nejprve si ukažme kostru demonstračního příkladu, který po svém spuštění zobrazí dialog se zaškrtávacími poli. Prozatím jsou implementovány pouze dvě operace – reakce na stisk tlačítka pro ukončení aplikace a reakce na stisk klávesové zkratky Ctrl+Q se stejným významem – okamžité ukončení aplikace:
"""Pygubu and Tkinter: using check boxes.""" # Example8.py import tkinter as tk from tkinter import ttk from tkinter import messagebox import pygubu class Example8App(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('example8.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: Configure callbacks builder.connect_callbacks(self) root.bind('<Control-q>', lambda event: self.on_quit_button_click()) def on_quit_button_click(self): root.destroy() def on_button_display_selections_click(self): pass if __name__ == '__main__': # needed to have a menu root = tk.Tk() # style style = ttk.Style() # run the application app = Example8App(root) app.run()
Obrázek 8: Po spuštění příkladu je zřejmé, že zaškrtávací pole mají tři stavy.
6. Automatické nastavení proměnných při změně stavu zaškrtávacího pole
Velmi praktická je další vlastnost widgetu checkbutton. Stav přepínacího tlačítka je totiž možné navázat na vybranou proměnnou, která tak bude automaticky sledovat jeho stav. Pokud je tlačítko nastaveno (je v něm znak × nebo ✓), bude do proměnné implicitně vložena hodnota 1, pokud je naopak nenastaveno, nastaví se nulová hodnota. Sledující proměnnou je v případě přímého použití knihovny Tkinter nutné vytvořit konstruktorem tkinter.IntVar() a specifikovat ji pojmenovaným parametrem variable:
delete_internet = tkinter.IntVar() checkbutton = ttk.Checkbutton(root, text="Delete Internet?", variable=delete_internet)
K hodnotě sledující proměnné se můžeme dostat s využitím getteru, tedy metody get():
checkbutton = ttk.Checkbutton(root, text="Delete Internet?", variable=delete_internet, command=lambda: print(delete_internet.get()))
V Pygubu designeru se toto chování nastavuje s využitím vlastnosti variable. Navíc je možné zvolit hodnotu proměnné ve chvíli, kdy je tlačítko vybráno (zašrkrtnuto) a kdy nikoli:
Obrázek 9: Nastavení vlastností zaškrtávacího tlačítka.
Soubor s návrhem grafického uživatelského rozhraní se změní následujícím způsobem:
<?xml version='1.0' encoding='utf-8'?> <interface version="1.1"> <object class="ttk.Frame" id="MainWindow"> <property name="height">200</property> <property name="width">200</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> <child> <object class="ttk.Checkbutton" id="checkbutton_a"> <property name="offvalue">no</property> <property name="onvalue">yes</property> <property name="state">normal</property> <property name="text" translatable="yes">Selection A</property> <property name="variable">string:checkbutton_a</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Checkbutton" id="checkbutton_b"> <property name="offvalue">no</property> <property name="onvalue">yes</property> <property name="state">normal</property> <property name="text" translatable="yes">Selection B</property> <property name="variable">string:checkbutton_b</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Checkbutton" id="checkbutton_c"> <property name="offvalue">no</property> <property name="onvalue">yes</property> <property name="state">normal</property> <property name="text" translatable="yes">Selection C</property> <property name="variable">string:checkbutton_c</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Button" id="button_display_values"> <property cbtype="simple" name="command" type="command">on_button_display_selections_click</property> <property name="compound">left</property> <property name="image">display.png</property> <property name="text" translatable="yes">Display selections</property> <layout manager="pack"> <property name="expand">true</property> <property name="fill">x</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Button" id="button_quit"> <property cbtype="simple" name="command" type="command">on_quit_button_click</property> <property name="compound">left</property> <property name="image">application-exit.png</property> <property name="text" translatable="yes">Quit</property> <layout manager="pack"> <property name="expand">true</property> <property name="fill">x</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> </object> </interface>
7. Zobrazení aktuálního stavu zaškrtávacích polí
V následujícím demonstračním příkladu je ukázáno, jakým způsobem lze zjistit aktuální stav zaškrtávacích polí. Nejprve přistoupíme k atributu tkvariables objektu builder:
vars = self.builder.tkvariables
Následně se k proměnným knihovny Tkinter přistupuje následovně:
vars["checkbutton_a"].get() vars["checkbutton_b"].get() vars["checkbutton_c"].get()
Konkrétně tedy můžeme snadno připravit zprávu pro dialogový box:
message = \ "Checkbutton A: {}\n" \ "Checkbutton B: {}\n" \ "Checkbutton C: {}\n".format(vars["checkbutton_a"].get(), vars["checkbutton_b"].get(), vars["checkbutton_c"].get())
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
"""Pygubu and Tkinter: using check boxes.""" # Example9A.py import tkinter as tk from tkinter import ttk from tkinter import messagebox import pygubu class Example9App(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('example9.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: Configure callbacks builder.connect_callbacks(self) root.bind('<Control-q>', lambda event: self.on_quit_button_click()) def on_quit_button_click(self): root.destroy() def on_button_display_selections_click(self): vars = self.builder.tkvariables message = \ "Checkbutton A: {}\n" \ "Checkbutton B: {}\n" \ "Checkbutton C: {}\n".format(vars["checkbutton_a"].get(), vars["checkbutton_b"].get(), vars["checkbutton_c"].get()) messagebox.askokcancel("askokcancel()", message) if __name__ == '__main__': # needed to have a menu root = tk.Tk() # style style = ttk.Style() # run the application app = Example9App(root) app.run()
8. Nastavení výchozího stavu zaškrtávacích polí
Výchozí stav zaškrtávacích polí je opět možné nastavit přes proměnné, které knihovna Tkinter sleduje, popř. nastavuje. V našem konkrétním příkladu, v němž máme definovány tři sledovací proměnné checkbutton_a, checkbutton_b a checkbutton_c pro trojici zaškrtávacích polí, se jejich nastavení do zvoleného výchozího stavu provede následujícím způsobem:
vars = self.builder.tkvariables vars["checkbutton_a"].set("no") vars["checkbutton_b"].set("no") vars["checkbutton_c"].set("no")
Demonstrační příklad lze upravit takto (viz zvýrazněné části):
"""Pygubu and Tkinter: using check boxes.""" # Example9B.py import tkinter as tk from tkinter import ttk from tkinter import messagebox import pygubu class Example9App(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('example9.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: Configure callbacks builder.connect_callbacks(self) # step #5: Set variables vars = self.builder.tkvariables vars["checkbutton_a"].set("no") vars["checkbutton_b"].set("no") vars["checkbutton_c"].set("no") root.bind('<Control-q>', lambda event: self.on_quit_button_click()) def on_quit_button_click(self): root.destroy() def on_button_display_selections_click(self): vars = self.builder.tkvariables message = \ "Checkbutton A: {}\n" \ "Checkbutton B: {}\n" \ "Checkbutton C: {}\n".format(vars["checkbutton_a"].get(), vars["checkbutton_b"].get(), vars["checkbutton_c"].get()) messagebox.askokcancel("Selections", message) if __name__ == '__main__': # needed to have a menu root = tk.Tk() # style style = ttk.Style() # run the application app = Example9App(root) app.run()
9. Widget představující vstupní textové pole
S využitím widgetu typu entry je možné v okně či dialogu zobrazit jeden řádek textu. Od staticky pojatého widgetu label se tento widget liší především v tom, že zobrazený řádek textu je možné editovat. Při editaci jsou k dispozici základní klávesy pro pohyb kurzoru (šipka doleva, šipka doprava, klávesa [Home] a klávesa [End]) a mimo jiné také další klávesové zkratky, které jsou známé například z shellu či editoru Emacs, Joe a Pico: [Ctrl+A] (posun na začátek textu), [Ctrl+E] (posun na konec textu). K tomu připočítejme dnes již standardní klávesy pro práci se schránkou: [Ctrl+C], [Ctrl+V] a [Ctrl+X] a na některých systémech i kombinace kláves [Ctrl+Insert], [Shift+Insert] a [Shift+Delete]. Bude ovšem fungovat i použití výběru, tedy stisk prostředního tlačítka myši vloží obsah výběru do widgetu entry.
Text se do widgetu přiřazuje metodou insert:
entry = ttk.Entry(root) entry.insert(0, "xyzzy")
I k widgetu entry je možné zaregistrovat „sledovací proměnnou“, jejíž hodnota bude reflektovat stav textu v tomto prvku. Sledovací proměnná bude mít typ StringVar:
value = tkinter.StringVar()
Nastavení sledování:
entry = ttk.Entry(root, textvariable=value)
Podobně lze postupovat i při použití Pygube designeru. Nejprve si ukažme, jak se textové pole vloží do formuláře a jak se definuje okraj okolo tohoto widgetu (textové pole je totiž jedním z widgetů, které typicky okolo sebe nějaký okraj vyžaduje kvůli vzhledu):
Obrázek 10: Návrh dialogu se vstupním textovým polem.
Obrázek 11: Úprava widgetu takovým způsobem, aby okolo něj vznikl nevyužitý okraj, který widget vizuálně odděluje od svého okolí.
Výsledný soubor s návrhem formuláře vypadá takto:
<?xml version='1.0' encoding='utf-8'?> <interface version="1.1"> <object class="ttk.Frame" id="MainWindow"> <property name="height">200</property> <property name="width">200</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> <child> <object class="ttk.Entry" id="text_entry"> <property name="textvariable">string:input_text</property> <layout manager="pack"> <property name="anchor">center</property> <property name="padx">10</property> <property name="pady">10</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Button" id="button_display_text"> <property cbtype="simple" name="command" type="command">on_button_display_text_click</property> <property name="compound">left</property> <property name="image">display.png</property> <property name="text" translatable="yes">Display text</property> <layout manager="pack"> <property name="expand">true</property> <property name="fill">x</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Button" id="button_quit"> <property cbtype="simple" name="command" type="command">on_quit_button_click</property> <property name="compound">left</property> <property name="image">application-exit.png</property> <property name="text" translatable="yes">Quit</property> <layout manager="pack"> <property name="expand">true</property> <property name="fill">x</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> </object> </interface>
10. Realizace formuláře s textovým polem
Podívejme se nyní na způsob realizace programové části formuláře, který obsahuje textové pole. Při inicializaci formuláře nastavíme sledovací proměnnou nazvanou input_text na zvolenou výchozí hodnotu, což se ihned projeví i ve vlastním formuláři zobrazeném uživateli:
vars = self.builder.tkvariables vars["input_text"].set("")
Následně implementujeme tělo callback metody zavolané ve chvíli, kdy uživatel klikne na tlačítko určené pro zobrazení aktuálního obsahu textového pole. Vše je opět vyřešeno přes sledovací proměnnou:
def on_button_display_text_click(self): vars = self.builder.tkvariables text = vars["input_text"].get()
Úplný zdrojový kód v pořadí již desátého demonstračního příkladu vypadá následovně. Zvýrazněny jsou ty části kódu, které se týkají zpracování textu z textového pole:
"""Pygubu and Tkinter: text entry.""" # Example10.py import tkinter as tk from tkinter import ttk from tkinter import messagebox import pygubu class Example10App(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('exampleA.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: Configure callbacks builder.connect_callbacks(self) # step #5: Set variables vars = self.builder.tkvariables vars["input_text"].set("") root.bind('<Control-q>', lambda event: self.on_quit_button_click()) def on_quit_button_click(self): root.destroy() def on_button_display_text_click(self): vars = self.builder.tkvariables text = vars["input_text"].get() messagebox.askokcancel("Text entered by user:", text) if __name__ == '__main__': # needed to have a menu root = tk.Tk() # style style = ttk.Style() # run the application app = Example10App(root) app.run()
11. Průběžná validace textu zapisovaného do textového pole
V mnoha aplikacích se setkáme s požadavkem na textové pole, jehož vstup je nějakým způsobem omezený (a následně validovaný). Příkladem může být textové pole určené pro zápis kladného čísla, pole pro vstup rodného čísla, čísla platební karty atd. atd. I tuto kontrolu je možné v knihovně Tkinter zajistit – a to buď průběžně (již v průběhu zadávání textu) nebo až následně, po ukončení zadávání (a oba způsoby mají své pro i proti). Ukážeme si způsob průběžné validace textu přímo při jeho zápisu, což vlastně znamená, že neplatné znaky nebude vůbec možné do textového pole vložit:
Obrázek 12: Nastavení validace zapisovaného textu. Pokud je zapotřebí zajistit kontrolu prováděnou ihned při zápisu jednotlivých znaků, je nutné ve vlastnosti validate zvolit volbu key.
Obrázek 13: Specifikace argumentu předávaného validátoru. Hodnota zadávaná do textového pole bude průběžně posílána do zvolené callback metody nazvaný validate_input_text. To zajišťuje parametr %P.
Výsledný návrh formuláře, který nyní obsahuje i specifikaci chování (což je důležité – chování GUI je součástí návrhu GUI a nikoli logiky aplikace), bude vypadat takto:
<?xml version='1.0' encoding='utf-8'?> <interface version="1.1"> <object class="ttk.Frame" id="MainWindow"> <property name="height">200</property> <property name="width">200</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> <child> <object class="ttk.Entry" id="text_entry"> <property name="textvariable">string:input_text</property> <property name="validate">key</property> <property args="%P" cbtype="entry_validate" name="validatecommand" type="command">validate_input_text</property> <layout manager="pack"> <property name="anchor">center</property> <property name="padx">10</property> <property name="pady">10</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Button" id="button_display_text"> <property cbtype="simple" name="command" type="command">on_button_display_text_click</property> <property name="compound">left</property> <property name="image">display.png</property> <property name="text" translatable="yes">Display text</property> <layout manager="pack"> <property name="expand">true</property> <property name="fill">x</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Button" id="button_quit"> <property cbtype="simple" name="command" type="command">on_quit_button_click</property> <property name="compound">left</property> <property name="image">application-exit.png</property> <property name="text" translatable="yes">Quit</property> <layout manager="pack"> <property name="expand">true</property> <property name="fill">x</property> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> </object> </interface>
12. Realizace formuláře s validovaným textovým polem
Metoda určená pro průběžnou validaci zadávaného textu bude kontrolovat, zda text obsahuje číslice, popř. jestli je textové pole prázdné (což je výchozí stav). Implementace validátoru je v tomto případě triviální, protože výsledek validace odpovídá návratové hodnotě validátoru:
def validate_input_text(self, value): if value.isdigit(): return True elif value is "": return True else: return False
Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:
"""Pygubu and Tkinter: text entry and validators.""" # Example11.py import tkinter as tk from tkinter import ttk from tkinter import messagebox import pygubu class Example11App(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('exampleB.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: Configure callbacks builder.connect_callbacks(self) # step #5: Set variables vars = self.builder.tkvariables vars["input_text"].set("") root.bind('<Control-q>', lambda event: self.on_quit_button_click()) def on_quit_button_click(self): root.destroy() def on_button_display_text_click(self): vars = self.builder.tkvariables text = vars["input_text"].get() messagebox.askokcancel("Text entered by user:", text) def validate_input_text(self, value): if value.isdigit(): return True elif value is "": return True else: return False if __name__ == '__main__': # needed to have a menu root = tk.Tk() # style style = ttk.Style() # run the application app = Example11App(root) app.run()
13. Práce s grafikou v knihovně Tkinter
Práci s grafikou můžeme chápat ve dvou úrovních. Na vyšší úrovni manipulujeme s jednotlivými geometricky popsanými tvary, jakými jsou například úsečka, obdélník, kruh, text či spline křivka. Každému tvaru může být přiřazeno značné množství atributů, jedná se například o tloušťku čáry, kterou je vykreslena hranice objektu, barva a styl výplně uzavřených objektů či font vykreslovaného textu. Na úrovni nižší se může manipulovat přímo s pixely, které tvoří zobrazovaný rastrový obrázek. V knihovně Tkinter je pro práci na vyšší úrovni určen widget canvas spolu s dalšími objektu, na nižší úrovni pak widgety (resp. objekty) bitmap, image a photo. Objekt typu image přitom zapouzdřuje více typů obrázků, od monochromatického BitmapImage přes vícebarevný PhotoImage až po uživatelsky vytvářené formáty.
14. Widget canvas
Jak jsme si již řekli v předchozí kapitole, je pro práci s grafikou na vyšší úrovni nabízen v knihovně Tkinter widget canvas, česky bychom mohli tento název přeložit jako (malířské) plátno. Nenechte se ovšem zmýlit tímto názvem, který má v jiných programových knihovnách odlišný (většinou jednodušší) význam, zde se skutečně jedná o velmi mocný widget, který současně slouží jako kontejner pro další objekty.
Vkládané objekty si své vlastnosti zachovávají, tj. lze s nimi i po jejich vykreslení interaktivně i programově pohybovat, měnit jejich vlastnosti apod. K tomu musíme připočítat možnost uložit celé plátno do PostScriptového souboru se zachováním vektorových charakteristik plátna (tj. neprovádí se ztrátový převod na bitmapy). To mj. znamená, že se vlastnosti canvasu do určité míry podobají použití SVG na webových stránkách; naproti tomu „webový canvas“ je vlastně pouze bitmapa, do níž se objekty vykreslují a ihned po jejich vykreslení se jejich vlastnosti ztratí (zůstanou jen barevné pixely).
Každý objekt, který je na plátno umístěn, musí mít specifikovány souřadnice počátečního a koncového bodu, v případě lomených čar, spline křivek a polygonů se samozřejmě specifikuje bodů více. Souřadnice bodů mohou být zadány pomocí více jednotek, jež se rozlišují podle jednoho písmene zapsaného za numerickou hodnotou (hodnoty jsou samozřejmě platné pouze při správně nakalibrované obrazovce, což kupodivu mnoho systémů dodnes nedodržuje):
Přípona | Význam |
---|---|
m | milimetry |
c | centimetry |
i | palce |
p | body (implicitní hodnota) |
V následující tabulce je ukázáno, jaké objekty je možné na plátno pokládat (a tím je vlastně ihned vykreslit):
Jméno objektu | Význam |
---|---|
arc | kruhový nebo eliptický oblouk |
bitmap | bitmapový obrázek |
image | obecně vícebarevný rastrový obrázek |
line | úsečka, lomená úsečka nebo dokonce hladká spline křivka (!) |
oval | uzavřená kružnice nebo elipsa |
polygon | uzavřený polygon či tvar vytvořený ze spline křivek |
rectangle | čtverec nebo obdélník |
text | textový řetězec |
window | vnořené okno se samostatným řízením |
15. Realizace formuláře s kreslicím plátnem (canvasem)
Pro otestování některých možností, které nám kreslicí plátno nabízí, vytvoříme jednoduchý formulář, který bude prozatím obsahovat pouze vlastní plátno a taktéž tlačítko sloužící pro ukončení činnosti aplikace:
Obrázek 14: Návrh dialogu s jediným tlačítkem a s kreslicí plochou.
Vlastní návrh formuláře je v tomto případě skutečně triviální:
<?xml version='1.0' encoding='utf-8'?> <interface version="1.1"> <object class="ttk.Frame" id="MainWindow"> <property name="height">200</property> <property name="width">200</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> <child> <object class="tk.Canvas" id="canvas"> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> <child> <object class="ttk.Button" id="quit"> <property cbtype="simple" name="command" type="command">on_quit_button_click</property> <property name="text" translatable="yes">Quit</property> <layout manager="pack"> <property name="propagate">True</property> <property name="side">top</property> </layout> </object> </child> </object> </interface>
16. První skript, který zobrazí formulář s prázdným kreslicím plátnem
Skript, který bez dalších operací pouze zobrazí formulář obsahující prázdné kreslicí plátno a bude očekávat uzavření formuláře (a tím pádem i ukončení celé aplikace), bude vypadat následovně:
"""Pygubu and Tkinter: changing style.""" # Example12.py import tkinter as tk from tkinter import ttk from tkinter import messagebox import pygubu class Example12App(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('exampleC.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: Configure callbacks builder.connect_callbacks(self) root.bind('<Control-q>', lambda event: self.on_quit_button_click()) def on_quit_button_click(self): root.destroy() if __name__ == '__main__': # needed to have a menu root = tk.Tk() # style style = ttk.Style() # run the application app = Example12App(root) app.run()
17. Kreslení na plátno
V následujícím demonstračním příkladu je ukázáno jednoduché použití plátna, na které jsou s využitím metody create_TYP_OBJEKTU vloženy jednoduché objekty – úsečka, kruh, text, polygon atd.. Vzhledem k tomu, že se při specifikaci souřadnic neuvádí u číselných hodnot žádná přípona (používáme celá čísla), předpokládá se, že jsou všechny hodnoty zapsané v bodech (pixelech). To také znamená, že na různých obrazovkách a při různých rozlišeních budou mít nakreslené objekty jinou velikost, což nám v tomto případě nemusí vadit.
Obrázek 15: Nastavení minimální výšky kreslicího plátna.
Grafické objekty vkládané na plátno mohou mít nastaveno velké množství vlastností, jak je ukázáno na modifikaci předchozího příkladu (nejsou zde ukázány zdaleka všechny vlastnosti, ty jsou uvedeny a podrobně popsány v helpu):
Vlastnost | Popis |
---|---|
outline | barva obrysu |
fill | barva výplně |
width | šířka obrysu |
dash | vzorek při vykreslování úseček a polyčar |
font | popis použitého fontu |
Některé z těchto vlastností v příkladu určujeme, například:
canvas.create_arc(100, 1, 200, 100, outline='blue', start=45, extent=180, style=tk.ARC, width=2) canvas.create_oval(325, 25, 375, 75, fill="#a0a0ff") canvas.create_text(300, 150, text="Hello world!", font="Helvetica 20")
Obrázek 16: Vykreslení základních obrazců na plátno.
Úplný kód dnešního posledního demonstračního příkladu:
"""Pygubu and Tkinter: changing style.""" # Example13.py import tkinter as tk from tkinter import ttk from tkinter import messagebox import pygubu class Example13App(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('exampleD.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: Configure callbacks builder.connect_callbacks(self) root.bind('<Control-q>', lambda event: self.on_quit_button_click()) root.bind('<Control-d>', lambda event: self.on_draw_button_click()) def on_quit_button_click(self): root.destroy() def on_draw_button_click(self): canvas = self.builder.get_object("canvas") width = canvas.winfo_width() height = canvas.winfo_height() grid_size = 30 # draw something onto canvas for x in range(0, width, grid_size): canvas.create_line(x, 0, x, height, dash=7, fill="gray") for y in range(0, height, grid_size): canvas.create_line(0, y, width, y, dash=7, fill="gray") canvas.create_line(0, 0, 100, 100, fill='red', width=2, dash=8) canvas.create_arc(100, 1, 200, 100, outline='blue', start=45, extent=180, style=tk.ARC, width=2) canvas.create_oval(200, 1, 300, 100) canvas.create_oval(325, 25, 375, 75, fill="#a0a0ff") canvas.create_rectangle(50, 125, 150, 175, fill="#a0a0ff") canvas.create_text(300, 150, text="Hello world!", font="Helvetica 20") canvas.create_polygon(50, 205, 200, 280, 50, 355, fill="#80ff80") canvas.create_polygon(230, 205, 370, 280, 230, 355, fill="black", outline="red", width="5") if __name__ == '__main__': # needed to have a menu root = tk.Tk() # style style = ttk.Style() # run the application app = Example13App(root) app.run()
18. Ukázka dalších možností kreslicího plátna
S využitím plátna je relativně snadné a přímočaré implementovat i různé graficky orientované nástroje, například následující systém pro nalezení a vykreslení obrysů místností o kterých jsou informace uložené v SAPu (přesněji řečeno tento CAD systém komunikuje s webovou službou, která obsahuje kontektor do SAPu).
Obrázek 17: Specializovaný CAD systém vytvořený v kombinaci Python+Tkinter.
19. Repositář s demonstračními příklady
Zdrojové kódy všech předminule, minule i 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:
# | Jméno souboru | Stručný popis souboru | Cesta |
---|---|---|---|
Ovládací prvky vložené do hlavního okna aplikace | |||
1 | example1.ui | soubor s návrhem GUI prvního demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example1.ui |
2 | example1.py | implementace prvního demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example1.py |
Menu, jehož jednotlivé položky obsahují i ikony | |||
3 | example2.ui | soubor s návrhem GUI druhého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example2.ui |
4 | example2A.py | implementace třetího demonstračního příkladu (bez menu) | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example2A.py |
5 | example2B.py | implementace třetího demonstračního příkladu (nespecifikován adresář s ikonami) | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example2B.py |
6 | example2C.py | implementace třetího demonstračního příkladu (korektní varianta) | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example2C.py |
Menu, jehož položky volají zvolené metody | |||
7 | example3.ui | soubor s návrhem GUI třetího demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example3.ui |
8 | example3.py | implementace třetího demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example3.py |
„Odtrhávací“ menu | |||
9 | example4.ui | soubor s návrhem GUI čtvrtého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example4.ui |
10 | example4.py | implementace čtvrtého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example4.py |
Klávesové zkratky přiřazené položkám menu | |||
11 | example5.ui | soubor s návrhem GUI pátého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example5.ui |
12 | example5.py | implementace pátého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example5.py |
13 | example5B.py | implementace pátého demonstračního příkladu s reakcemi na stisk kláves | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example5B.py |
Změna tématu zobrazení za běhu aplikace | |||
14 | example6.ui | soubor s návrhem GUI šestého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example6.ui |
15 | example6.py | implementace šestého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example6.py |
Standardní dialogová okna | |||
16 | example7.ui | soubor s návrhem GUI sedmého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example7.ui |
17 | example7.py | implementace sedmého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example7.py |
Práce s dialogem obsahujícím zaškrtávací pole | |||
18 | example8.ui | soubor s návrhem GUI osmého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example8.ui |
19 | example8.py | implementace osmého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example8.py |
Automatické nastavení proměnných při změně stavu zaškrtávacího pole | |||
20 | example9.ui | soubor s návrhem GUI devátého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example9.ui |
21 | example9.py | implementace devátého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/example9.py |
Realizace formuláře s textovým polem | |||
22 | exampleA.ui | soubor s návrhem GUI desátého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/exampleA.ui |
23 | exampleA.py | implementace desátého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/exampleA.py |
Realizace formuláře s validovaným textovým polem | |||
24 | exampleB.ui | soubor s návrhem GUI jedenáctého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/exampleB.ui |
25 | exampleB.py | implementace jedenáctého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/exampleB.py |
Realizace formuláře s kreslicím plátnem (canvasem) | |||
26 | exampleC.ui | soubor s návrhem GUI dvanáctého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/exampleC.ui |
27 | exampleC.py | implementace dvanáctého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/exampleC.py |
Kreslení do canvasu | |||
28 | exampleD.ui | soubor s návrhem GUI třináctého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/exampleD.ui |
29 | exampleD.py | implementace třináctého demonstračního příkladu | https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/exampleD.py |
Ve druhém a třetím demonstračním příkladu, jsou navíc použity i ikony v menu, stejně jako v příkladech používajících tlačítko pro uzavření aplikace. 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 - Raspberry Pi Tutorial: Create your own GUI (Graphical User Interface) with TkInter and Python
https://www.youtube.com/watch?v=Bvq0LdBn0dY - Raspberry Pi Workshop – Chapter 4 – Your First GUI with TkInter and Python
https://www.youtube.com/watch?v=ap-ABFNCBoE