Hlavní navigace

Interaktivní tvorba grafického uživatelského rozhraní nástrojem Pygubu

30. 3. 2021
Doba čtení: 36 minut

Sdílet

 Autor: Alejandro Autalan, Pygubu
Dnes se seznámíme s nástrojem s podivným jménem Pygubu. Jedná se o jednoduchý nástroj určený pro interaktivní tvorbu grafického uživatelského rozhraní pro aplikace vyvíjené v Pythonu s využitím knihovny Tkinter.

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

6. Instalace nástroje Pygubu

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

14. Možná alternativní řešení

15. Gambas – Gambas Almost Means BASIC

16. Lazarus

17. PySide a Qt Creator

18. Závěrečné zhodnocení

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

20. Odkazy na Internetu

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.

Poznámka: v posledních několika letech se můžeme setkat s tím, že se desktopové aplikace začínají vytvářet s využitím webových technologií. Typickým příkladem jsou aplikace postavené na technologii Electron. Těmito typy aplikací – které pochopitelně opět trpí několika neduhy – se ovšem v dnešním článku zabývat nebudeme.

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.

Poznámka: jak uvidíme dále, umožňuje Pygubu oddělení definice grafického uživatelského rozhraní od programového kódu. Inspirace je nasnadě – jedná se o projekt Glade.

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.

Poznámka: v následujících kapitolách předpokládáme, že je knihovna Tkinter nainstalována (což je v některých systémech splněno instalací samotného prostředí Pythonu). Způsob instalace knihovny Tkinter je podrobněji popsán na této stránce.

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.

Poznámka: na Rootu již vyšel zajímavý článek o Tkinteru, v jehož rámci byla vytvořena jednoduchá hra typu Arkanoid/Breakout.

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
Poznámka: pochopitelně je možné instalaci nástroje Pygubu provést v rámci virtuálního prostředí Pythonu, resp. přesněji řečeno s využitím virtualenv či venv. Postačuje vytvořit adresářovou strukturu s virtuálním prostředím, inicializovat ho a následně spustit instalaci v rámci právě vytvořeného a inicializovaného virtuálního prostředí:
$ 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.

Poznámka: ve skutečnosti může být styl zobrazení odlišný, protože knihovna Tkinter podporuje (přes Tk) několik stylů zobrazení oken i jednotlivých ovládacích prvků (widgetů). Přepínání mezi jednotlivými styly se provádí přes položku hlavního menu Preview → ttk theme.

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.

Poznámka: pokud znáte knihovnu Tkinter, je vám již zřejmé, že se ve výchozím nastavení používá Grid layout.

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>
Poznámka: využití jazyka XML je v tomto ohledu poměrně dobrým nápadem, protože soubory s popisem grafického uživatelského rozhraní jsou snadno editovatelné i mimo vlastní pygubu-designer (což v praxi oceníme zejména při přidávání nových položek do menu). Další předností je, že změny provedené v grafickém uživatelském rozhraní jsou snadno verzovatelné například v Gitu či v jiném systému pro správu verzí. Podobným způsobem pracuje s návrhem GUI například i dále zmíněný projekt Lazarus a ostatně i jeho idový předchůdce – systém Delphi (ve kterém bylo možné zvolit buď binární nebo textový formát).

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>
Poznámka: kdykoli, i během návrhu UI, je možné si nechat navrhované okno s ovládacími prvky zobrazit takovým způsobem, jak bude vypadat výsledná aplikace. K tomuto účelu slouží klávesová zkratka F5, ovšem příslušný příkaz je dostupný i z hlavního menu návrháře.

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()
Poznámka: povšimněte si, že se snažíme získat instanci objektu představujícího hlavní menu a explicitně nastavujeme, že hlavní okno má obsahovat i toto menu.

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(".")
Poznámka: ještě lepší je s využitím modulu os získat a specifikovat absolutní cestu.

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).

Poznámka: ve skutečnosti existuje celá řada projektů určených pro interaktivní návrh GUI. Většina těch známějších je zmíněna na stránce Graphical user interface builder, jazyky pro popis návrhu GUI nalezneme na stránce User interface markup language.

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.

Poznámka: na tomto místě je však nutné vysvětlit, že Gambas ve své současné verzi (v praxi se ještě stále používá jak verze 1.x, tak i v mnoha ohledech vylepšená verze 2.x a 3.x, poslední stabilní verzí je 3.15.2) je stabilní a poměrně schopné vývojové prostředí a i programovací jazyk založený na strukturovaném Visual Basicu rozšířeném o konstrukce určené pro práci s objekty, nemá s prapůvodním BASICem s čísly řádků a nestrukturovanými konstrukcemi založenými na příkazu GOTO prakticky nic společného.

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.

Poznámka: dříve (před Qt 5) existovala samostatná aplikace nazvaná Qt Designer, která byla určená prakticky výhradně na návrh GUI. Dnes jsou funkce obou nástrojů sjednoceny právě v Qt Creatoru, i když se například ve starší dokumentaci stále setkáme s původním názvem.

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á.

Cloud 22 temata

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:

# Jméno souboru Stručný popis souboru Cesta
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
       
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
       
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

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:

# Jméno souboru Stručný popis souboru Cesta
1 application-exit.png ikona pro položku menu sloužící k ukončení aplikace https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/application-exit.png
2 document-new.png ikona pro položku menu File→New https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/document-new.png
3 document-open.png ikona pro položku menu File→Open https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/document-open.png
4 edit-copy.png ikona pro položku menu Edit→Copy https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/edit-copy.png
5 edit-cut.png ikona pro položku menu Edit→Cut https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/edit-cut.png
6 edit-paste.png ikona pro položku menu Edit→Paste https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/edit-paste.png
7 edit-delete.png ikona pro položku menu Edit→Delete https://github.com/tisnik/most-popular-python-libs/blob/master/pygubu/edit-delete.png

20. Odkazy na Internetu

  1. Seriál Grafické uživatelské rozhraní v Pythonu
    https://www.root.cz/serialy/graficke-uzivatelske-rozhrani-v-pythonu/
  2. Pygubu na PyPi
    https://pypi.org/project/pygubu/
  3. Repositář projektu Pygubu
    https://github.com/alejan­droautalan/pygubu
  4. pygubu-designer na PyPi
    https://pypi.org/project/pygubu-designer/
  5. Repositář projektu pygubu-designer
    https://github.com/alejan­droautalan/pygubu-designer
  6. Pygubu Wiki
    https://github.com/alejan­droautalan/pygubu/wiki
  7. How to install Tkinter in Python?
    https://www.tutorialspoint.com/how-to-install-tkinter-in-python
  8. Stránky projektu Glade
    https://glade.gnome.org/
  9. Hra Breakout napísaná v Tkinteri
    https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/
  10. 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/
  11. The Hitchhiker's Guide to Pyhton: GUI Applications
    http://docs.python-guide.org/en/latest/scenarios/gui/
  12. 7 Top Python GUI Frameworks for 2017
    http://insights.dice.com/2014/11/26/5-top-python-guis-for-2015/
  13. GUI Programming in Python
    https://wiki.python.org/mo­in/GuiProgramming
  14. Cameron Laird's personal notes on Python GUIs
    http://phaseit.net/claird/com­p.lang.python/python_GUI.html
  15. Python GUI development
    http://pythoncentral.io/introduction-python-gui-development/
  16. Graphic User Interface FAQ
    https://docs.python.org/2/faq/gu­i.html#graphic-user-interface-faq
  17. TkInter
    https://wiki.python.org/moin/TkInter
  18. Tkinter 8.5 reference: a GUI for Python
    http://infohost.nmt.edu/tcc/hel­p/pubs/tkinter/web/index.html
  19. TkInter (Wikipedia)
    https://en.wikipedia.org/wiki/Tkinter
  20. Rapid application development
    https://en.wikipedia.org/wi­ki/Rapid_application_deve­lopment
  21. Non-functional requirement
    https://en.wikipedia.org/wiki/Non-functional_requirement
  22. Graphical user interface builder
    https://en.wikipedia.org/wi­ki/Graphical_user_interfa­ce_builder
  23. User interface markup language
    https://en.wikipedia.org/wi­ki/User_interface_markup_lan­guage
  24. Top 10 programming languages that developers hate the most
    https://www.techworm.net/2017/11/perl-hated-programming-language-developers-says-report.html