Lekce 13 - Tvorba kalkučky v tkinteru
V minulé lekci, Úvod do tkinteru v Pythonu, jsme si udělali úvod do tkinteru. V dnešním Python tutoriálu si zkusíme naprogramovat jednoduchou kalkulačku za použití tlačítek (button) a popisků (label). Ovšem budeme rozlišovat dva druhy widgetů (komponent do GUI aplikace) a to obyčejné z tkinteru a vzhledově lépe vypadající z podknihovny ttk.
Podknihovna reimplementuje všechny hlavní widgety, ale není úplně kompatibilní ve stylování widgetů.
Label
Label umožňuje v aplikaci zobrazit nějaký text. Česky se překládá jako textový popisek. V konstruktoru labelu můžeme přidat další volitelné parametry a tím změnit jeho vlastnosti.
Základní labely
Napřed se podíváme použití obyčejného labelu z tkinteru.
import tkinter class MainWindow(tkinter.Frame): def __init__(self, parent): super().__init__(parent) self.parent = parent self.parent.title("První GUI aplikace") self.create_widgets() def create_widgets(self): self.label = tkinter.Label(text="Hello world!") self.label.grid(row=0, column=0) root = tkinter.Tk() app = MainWindow(root) app.mainloop()
Možné parametry konstruktoru jsou:
- text - zobrazí daný řetězec
- background (bg) - barva pozadí
- foreground (fg) - barva textu
- wraplenght - počet řádek, na které se text zalomí (defaultně je nastaveno na nulu)
Existují i další, tyto jsou ty nezákladnější. Label v ukázce bude mít bílý text a černé pozadí.
ttk labely
Tyto pokročilejší labely se formátují za pomoci stylů. Vytvoříme si několik druhů stylů s předdefinovanými vlastnostmi a poté určíme každému popisku jeho styl.
Ukázka kódu pro vytvoření stylu a jeho aplikace:
style = Style() style.configure("BW.Label", background="white", foreground="black") label = ttk.Label(text="Some text", style="BW.Label")
Vytvořený label bude mít všechny vlastnosti nadefinované stylem. Metoda
na konfigurování stylů přijímá jako první parametr název stylu a dále
jako klíčové argumenty hodnoty jednotlivých vlastností, které se widgetům
nastaví. Normálně je název stylu widgetu stejný jako název třídy widgetu
(dá se zjistit metodou winfo_class()
) a vlastní styly musí být
ve tvaru <prefix>.<nazev tridy>
Tlačítka
Tlačítka jistě znáte. Umožňují po kliknutí provést nějakou akci v programu.
Nejzákladnějšími parametry jsou:
- text - text zobrazený na tlačítku
- background (bg) - barva pozadí
- foreground (fg) - barva textu
- command - funkce, která se má provést po stisknutí
- state - stav tlačítka, možnosti jsou "normal" (tlačítko je funkční, ale zatím neaktivní), "active" (tlačítko je funkční a aktivní, např. pokud je nad ním myš) nebo "disabled" (tlačítko je nefunkční)
- font - bere jako parametr buď řetězec s názvem fontu, nebo n-tici (název_fontu, velikost_fontu, dekorace)
button = tkinter.Button(text="Some text", background="white", command=self.do_something, font=("Arial", 18, "bold"))
Ovšem tato základní tlačítka jsou dosti nevzhledná, proto použijeme ekvivalenty z knihovny ttk.
ttk tlačítka
Základní parametry:
- text - text zobrazený na tlačítku
- command - funkce, která se má provést po stisknutí
- style - název stylu, který se má použít pro tlačítko
Pro více názornosti, jak funguje stylování a bindování příkazů, se vrhneme na kalkulačku.
Kalkulačka
Napřed importujeme obě knihovny:
import tkinter from tkinter import ttk
a nyní se vrhneme na okno. Okno dědí z ttk.Frame a nastavíme mu titulek "Kalkulacka". Kód pro výpočet vyčleníme do jiné třídy. Dále si připravíme speciální proměnnou, která bude představovat text zobrazený na displeji kalkulačky. Díky tomu se automaticky změní i text labelu, kdykoliv tuto proměnnou změníme. A nakonec zavoláme metodu, ve které vytvoříme obsah okna.
class CalculatorWindow(ttk.Frame): def __init__(self, parent): super().__init__(parent) self.parent = parent self.parent.title("Kalkulacka") self.calculator = Calculator() self.display_var = tkinter.StringVar(parent, self.calculator.text) self.create_widgets()
Nejprve definujeme seznam se znaky, které budou pod displejem. Displej zabere celou první řadu v okně. Dále upravíme styly widgetů a vytvoříme label představující display a tlačítka kalkulačky.
def create_widgets(self): buttons = [["7", "8", "9", "/", "("], ["4", "5", "6", "*", ")"], ["1", "2", "3", "-", "CE"], ["0", ".", "=", "+", "C"] ] self.define_styles() self.create_label(columnspan=max(map(len, buttons))) self.create_buttons(buttons)
V metodě definujeme dva styly. Defaultní styl pro nějaký prvek má název složený s písmena T a názvu widgetu. Pokud nechcete měnit výchozí styl widgetu, ale mít vlastní, tak existuje konvence, podle které se přidá před název defaultního stylu vlastní prefix. Například "display.TLabel" pro Label.
def define_styles(self): style = ttk.Style() style.configure("display.TLabel", foreground = "blue", sticky="NSWE", font=15) style.configure("TButton", sticky="NSWE")
U labelu nastavujeme textovou proměnnou, kterou bude zobrazovat přes
parametr textvariable. Ten přijímá jako parametr proměnnou typu
StringVar
, kterou jsme definovali již dříve. Pokud změníme
její hodnotu přes metodu set, tak se automaticky updatuje i hodnota zobrazená
widgetem. Kromě typu StringVar
existují ještě typy
IntVar
, DoubleVar
a BooleanVar
, všechny
dědící z třídy Variable
.
def create_label(self, columnspan): self.display_label = ttk.Label(style="display.TLabel", textvariable=self.display_var) self.display_label.grid(row=0, column=0, columnspan=columnspan)
Dále musíme každému tlačítku přidat nějakou událost. To lze jednoduše přiřazením funkce do parametru command. Pokud potřebujeme zavolat funkci a předat jí parametry, tak si vypomůžeme lambda funkcí. Problém ale nastává, pokud potřebujeme vykonat více funkcí naráz, lambda funkce dokáže obalit pouze jedinou funkci. To se dá řešit buď vytvořením nové funkce, která uvnitř zavolá požadované funkce, anebo vytvořením speciální třídy.
class MultiCall: def __init__(self, *calls): self.calls = calls def __call__(self, *ignore): for func in self.calls: func()
Instance třídy dostane v konstruktoru funkce a pokud bude sama zavolaná jako funkce, tak volání propaguje do předaných funkcí. Místo nastavování každého tlačítka zvlášť projdeme dvojrozměrný seznam s tlačítky dvěma for cykly. Pokud potřebujeme předat hodnotu z aktuálního cyklu do lambda funkce, musíme předat proměnnou jako parametr do funkce, čímž se hodnota proměnné zkopíruje, jinak tyto lambda funkce budou sdílet stejnou proměnnou včetně její hodnoty.
def create_buttons(self, buttons): for i, row in enumerate(buttons): for j, button_text in enumerate(row): command = lambda button_text=button_text, *ignore: self.update_text(button_text) if button_text == "=": command = MultiCall(self.calculator.compute, self.update_text) elif button_text == "C": command = MultiCall(self.calculator.delete_last, self.update_text) elif button_text == "CE": command = MultiCall(self.calculator.delete_all, self.update_text) button = ttk.Button(text=button_text, command=command) button.grid(row = i + 1, column = j, padx = 2, pady = 2) for row_id in range(len(buttons)+1): self.parent.rowconfigure(row_id, weight=100) for column_id in range(len(buttons[0])): self.parent.columnconfigure(column_id, weight=100)
A již nám v CalculatorWindow
chybí pouze metoda
update_text()
, která má volitelný parametr text, zastupující
nově přidaný znak.
def update_text(self, text=""): self.calculator.add_symbols(text) self.display_var.set(self.calculator.text)
Nyní se vrhneme na třídu Calculator
. V ní si budeme
pamatovat text, který je zobrazený na displeji a to, zda-li je kalkulačka ve
validním stavu, ve kterém neindikuje chybu.
class Calculator: def __init__(self): self.text = "" self.is_valid = False def add_symbols(self, symbols): if not self.is_valid and symbols != "": self.text = symbols self.is_valid = True elif len(self.text + symbols) < 20: self.text += symbols def delete_last(self): if self.is_valid: self.text = self.text[0:-1] if self.text == "": self.is_valid = False def delete_all(self): self.text = "" self.is_valid = False def compute(self): try: if self.is_valid: self.text = str(eval(self.text)) except (ZeroDivisionError, SyntaxError, OverflowError, TypeError): self.text = "Error" self.is_valid = False
Pro jednoduchost používáme metodu eval()
. Nyní stačí
doplnit řádky na spuštění okna a kalkulačka je kompletní.
root = tkinter.Tk() app = CalculatorWindow(root) app.mainloop()
Takto vypadá naše kalkulačka ve Windows 10:

A to je pro tuto lekci vše, na procvičení si můžete napsat vlastní
vyhodnocení výrazu, aby se zamezilo použití potenciálně nebezpečné
funkce eval()
.
Měla jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 235x (3.42 kB)
Aplikace je včetně zdrojových kódů v jazyce Python
Komentáře
Zatím nikdo nevložil komentář - buď první!