IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

12. diel - Tvorba kalkučky v tkinteru

V minulej lekcii, Úvod do tkinteru v Pythone , sme si urobili úvod do tkinteru. V dnešnom Python tutoriálu si skúsime naprogramovať jednoduchú kalkulačku za použitia tlačidiel (button) a popiskov (label). Ovšem budeme rozlišovať dva druhy widgetov (komponentov do GUI aplikácie) a to obyčajné z tkinteru a vzhľadovo lepšie vyzerajúce z podknihovny TTK.

Podknihovna reimplementuje všetky hlavné widgety, ale nie je úplne kompatibilný vo štýlovanie widgetov.

Label

Label umožňuje v aplikácii zobraziť nejaký text. Slovensky sa prekladá ako textový popis. V konstruktoru labelu môžeme pridať ďalšie voliteľné parametre a tým zmeniť jeho vlastnosti.

Základné labely

Napred sa pozrieme použitie obyčajné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é parametre konstruktoru sú:

  • text - zobrazí daný reťazec
  • background (bg) - farba pozadia
  • foreground (fg) - farba textu
  • wraplenght - počet riadkov, na ktoré sa text zalomia (defaultne je nastavené na nulu)

Existujú aj ďalšie, tieto sú tie nezákladnější. Label v ukážke bude mať biely text a čierne pozadie.

TTK labely

Tieto pokročilejšie labely sa formátujú za pomoci štýlov. Vytvoríme si niekoľko druhov štýlov s preddefinovanými vlastnosťami a potom určíme každému popisku jeho štýl.

Ukážka kódu pre vytvorenie štýlu a jeho aplikácia:

style = Style()
style.configure("BW.Label", background="white", foreground="black")
label = ttk.Label(text="Some text", style="BW.Label")

Vytvorený label bude mať všetky vlastnosti nadefinované štýlom. Metóda na konfigurovanie štýlov prijíma ako prvý parameter názov štýlu a ďalej ako kľúčové argumenty hodnoty jednotlivých vlastností, ktoré sa widgetom nastaví. Normálne je názov štýlu miniaplikácie rovnaký ako názov triedy widgetu (dá sa zistiť metódou winfo_class()) a vlastné štýly musí byť v tvare <prefix>.<nazev tridy>

Tlačidla

Tlačidlá iste poznáte. Umožňujú po kliknutí vykonať nejakú akciu v programe.

Najzákladnejšími parametre sú:

  • text - text zobrazený na tlačidle
  • background (bg) - farba pozadia
  • foreground (fg) - farba textu
  • command - funkcia, ktorá sa má vykonať po stlačení
  • state - stav tlačidla, možnosti sú "normal" (tlačidlo je funkčná, ale zatiaľ neaktívne), "active" (tlačidlo je funkčný a aktívny, napr. keď je nad ním myš) alebo "disabled" (tlačidlo je nefunkčné)
  • font - berie ako parameter buď reťazec s názvom fontu, alebo n-ticu (název_fontu, velikost_fontu, dekorácie)
button = tkinter.Button(text="Some text", background="white", command=self.do_something, font=("Arial", 18, "bold"))

Avšak tieto základné tlačidlá sú dosť nevzhľadná, preto použijeme ekvivalenty z knižnice TTK.

TTK tlačidlá

Základné parametre:

  • text - text zobrazený na tlačidle
  • command - funkcia, ktorá sa má vykonať po stlačení
  • style - názov štýlu, ktorý sa má použiť pre tlačidlo

Pre viac názornosti, ako funguje štýlovanie a bindovanie príkazov, sa vrhneme na kalkulačku.

Kalkulačka

Napred importujeme obe knižnice:

import tkinter
from tkinter import ttk

a teraz sa vrhneme na okno. Okno dedí z ttk.Frame a nastavíme mu titulok "Kalkulacka". Kód pre výpočet vyčleníme do inej triedy. Ďalej si pripravíme špeciálnu premennú, ktorá bude predstavovať text zobrazený na displeji kalkulačky. Vďaka tomu sa automaticky zmení aj text labelu, kedykoľvek túto premennú zmeníme. A nakoniec zavoláme metódu, v ktorej vytvorí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()

Najprv definujeme zoznam so znakmi, ktoré budú pod displejom. Displej zaberie celú prvú sériu v okne. Ďalej upravíme štýly widgetov a vytvoríme label predstavujúce display a tlačidlá 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 metóde definujeme dva štýly. Defaultný štýl pre nejaký prvok má názov zložený s písmená T a názvu widgetu. Ak nechcete meniť predvolený štýl widgetu, ale mať vlastné, tak existuje konvencie, podľa ktorej sa pridá pred názov defaultného štýlu vlastné prefix. Napríklad "display.TLabel" pre 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 textovú premennú, ktorú bude zobrazovať cez parameter textvariable. Ten prijíma ako parameter premennú typu StringVar, ktorú sme definovali už skôr. Ak zmeníme jej hodnotu cez metódu set, tak sa automaticky updatuje aj hodnota zobrazená widgetem. Okrem typu StringVar existujú ešte typy IntVar, DoubleVar a BooleanVar, všetky dedičov z triedy 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)

Ďalej musíme každému tlačidlu pridať nejakú udalosť. To možno jednoducho priradením funkcie do parametra command. Ak potrebujeme zavolať funkciu a odovzdať jej parametre, tak si vypomôžeme lambda funkcií. Problém ale nastáva, ak potrebujeme vykonať viac funkcií naraz, lambda funkcie dokáže obaliť iba jedinú funkciu. To sa dá riešiť buď vytvorením novej funkcie, ktorá vo vnútri zavolá požadované funkcie, alebo vytvorením špeciálne triedy.

class MultiCall:

    def __init__(self, *calls):
        self.calls = calls

    def __call__(self, *ignore):
        for func in self.calls:
            func()

Inštancie triedy dostane v konstruktoru funkcie a ak bude sama zavolaná ako funkcia, tak volania propaguje do odovzdaných funkcií. Namiesto nastavovanie každého tlačidla zvlášť prejdeme dvojrozmerný zoznam s tlačidlami dvoma for cykly. Ak potrebujeme odovzdať hodnotu z aktuálneho cyklu do lambda funkcie, musíme odovzdať premennú ako parameter do funkcie, čím sa hodnota premennej skopíruje, inak tieto lambda funkcie budú zdieľať rovnaký ukazovateľ vrátane 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 už nám v CalculatorWindow chýba iba metóda update_text(), ktorá má voliteľný parameter text, zastupujúci novo pridaný znak.

def update_text(self, text=""):
    self.calculator.add_symbols(text)
    self.display_var.set(self.calculator.text)

Teraz sa vrhneme na triedu Calculator. V nej si budeme pamätať text, ktorý je zobrazený na displeji a to, či je kalkulačka vo validným stave, v ktorom 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

Pre jednoduchosť používame metódu eval(). Teraz stačí doplniť riadky na spustenie okná a kalkulačka je kompletný.

root = tkinter.Tk()
app = CalculatorWindow(root)
app.mainloop()

Takto vyzerá naša kalkulačka vo Windows 10:

ukážka formulára - Okenné aplikácie v Pythone

A to je pre túto lekciu všetko, na precvičenie si môžete napísať vlastné vyhodnotenie výrazu, aby sa zamedzilo použitie potenciálne nebezpečné funkcie eval().


 

Predchádzajúci článok
Úvod do tkinteru v Pythone
Všetky články v sekcii
Okenné aplikácie v Pythone
Článok pre vás napísal gcx11
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
(^_^)
Aktivity