Python GUI – Tkinter

In this section I am going to show how Tkinter works. Tkinter is a default library for GUI implementation in Python. The codes I wrote represents basic elements such as windows, input fields, labels, spin boxes, combo boxes, radio buttons, submit buttons. These are the so-called widgets of Tkinter GUI. In the first example I am going to introduce a Tkinter window using the aforementioned widgets. The second example consists of similar elements and represents how is possible open further window(s) pushing a button on the original/main window. I have not implemented database to pass throught the input datas and I have not written codes for validation to check input datas. I have focused only for the GUI to showing itself. You can find the codes of the basic Python GUI example and the images of the windows below.

Info! The explanations you can read below are written in Hungarian language, but I pushed the codes to GitHub as well and attached textfiles with brief documentations in English. Here is the link: github.com/python_gui

A következő példaprogram egy Python űrlapra hoz példát. Az űrlap olyan inputok bekérésére alkalmas grafikus interfész, melyet a program felhasználója is könnyedén kezelhet. Az első kódrészletben az űrlap-osztály (Form) inicializáló függvénye látható.

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox


class Form(tk.Tk):

    def __init__(self):
       super().__init__()
       self.title("Tkinter example without formatting")
       self.index = 0
       self.left_column, self.right_column = 1, 2
       self.value_radio = tk.IntVar()
       self.value_radio.set(2)

A fenti kódban megfigyelhető, hogy a Form osztály argumentuma egy tk.Tk osztály. Pontosabban itt arról van szó, hogy a tk.Tk tkinter grafikus elemeket tartalmazó osztály a szülője a Form osztálynak. Ez a gyakorlatban annyit tesz, hogy a tkinter modul Tk osztálya a Form osztályban is felhasználható, így például a Tk title függvénye a Form osztályban a super().__init__() után self.title(“…”)-ként, azaz saját osztályfüggvényként is hívható. Az alábbi függvények az űrlap elemeinek egy-egy fajtáját határozzák meg, minimális paraméterezéssel, formázással.

" LABEL "

   def main_label(self, text):
       label = ttk.Label (self, text=text )
       label.grid(
           pady=(0,10),
           padx=(5,0),
           row = self.index
       )
       self.index += 1

   def param_label(self, text):
       label = ttk.Label (self, text=text )
       label.grid(
           row = self.index,
           column = self.left_column
       )
       self.index += 1


   " ENTRY "

   def input_field(self):
       value_entry = tk.StringVar()
       entry = ttk.Entry(self, textvariable = value_entry)
       entry.grid(
           padx = (20,20),
           row = self.index - 1,
           column = self.right_column
       )
       self.index += 1
       return value_entry


   " SPINBOX "

   def spin_box(self, set_spinbox):
       value_spinbox = tk.IntVar()
       value_spinbox.set(set_spinbox)
       spin = ttk.Spinbox (self, values=(1,2,3,4,5), textvariable=value_spinbox, state='readonly')
       spin.grid(
           row=self.index - 1,
           column=self.right_column
       )
       self.index += 1
       return value_spinbox


   " COMBOBOX "

   def combo_box(self, inputValues):
       value_combo = tk.StringVar()
       values = ttk.Combobox(self, textvariable=value_combo)
       values["values"] = (inputValues)
       values["state"] = "readonly"
       values.grid(
           row=self.index-1,
           column=self.right_column
       )
       self.index += 1
       return value_combo


   " RADIO BUTTON "

   def radio_button(self, text,value):
       radioButton = ttk.Radiobutton (self, text=text, variable=self.value_radio, value=value)
       radioButton.grid(
           row=self.index-1,
           column=self.right_column,
       )
       self.index += 1


   " NOTEBOOK -> TAB "

   def notebook(self, text):
       t = ttk.Notebook(self)
       tab1 = ttk.Frame(t)
       t.add(tab1, text=text)
       t.grid(
           pady = (0,10),
           row = 0,
           column = self.index
       )
       self.index += 1
       

   " BUTTON "

   def submit_button(self, submit):
       button = tk.Button(self, text="submit", command=submit)
       button.grid(pady=(20,20),row=self.index, column = 2)
       self.index += 1

A fenti űrlapelemekből állíthatjuk össze a kívánt űrlapot (lásd következő kódrészlet a  __main__ alatt). Mivel itt a szemléltetés a cél, ezért a példában szereplő űrlap tartalmaz minden, itt rendelkezésreálló elemet. Ahhoz, hogy rendezhessük az egyes elemeket, vagyis hogy ne összevissza, egymásba torlódva szerepeljenek a fenti kódrészletben látható űrlapelemek, a rácsokon való elrendezést (grid) használtuk. A grid argumentumai az adott űrlapelem pozícióját jelölik ki az űrlapon. A rácsos elrendezésnél megadhatjuk, hogy az elemek táblázatos formában, sorokban és oszlopokban helyezkedjenek el. Ahhoz, hogy az elemek egymás utáni sorrendben legyenek egy index változót vezetünk be, amely minden egyes elem meghívásával -1 sor pozíciót vesz fel [lásd a grid row paraméterét: (row=self.index-1)]. Ezzel oldható meg tehát, hogy az űrlapelemek rendben, egymás alatt helyezkedjenek el.

def submit_0():
    messagebox.showinfo("Names",f"Name_0: {name0.get()}\nName_1: {name1.get()}\nName_2: {name2.get()}\n")

def submit_1():
    messagebox.showinfo("Values",f"Value_0: {value0.get()}\nValue_1: {value1.get()}\nValue_2: {value2.get()}\n")


if __name__ == "__main__":
    
    combo_values = ["June","July","August"]

    tc = Form()
    
    tc.notebook("Tab_1")
    tc.notebook("Tab_2")
    tc.main_label("FORM_0")
    tc.param_label("Name_0:")
    name0 = tc.input_field()
    tc.param_label("Name_1:")
    name1 = tc.input_field()
    tc.param_label("Name_2:")
    name2 = tc.input_field()
    tc.submit_button(submit_0)
    tc.main_label("FORM_1")
    tc.param_label("Value_0:")
    value0 = tc.spin_box(5)
    tc.param_label("Value_1:")
    value1 = tc.combo_box(combo_values)
    tc.param_label("Value_2:")
    tc.radio_button("A <- 1",1)
    tc.radio_button("B <- 2",2)
    value2 = tc.value_radio
    tc.submit_button(submit_1)
    
    tc.mainloop()

A fenti kódrészletben az is látható, hogy az űrlapnak megadott adatok miképpen kerülnek felhasználásra. (lásd submit_0 és submit_1 függvényeket) Ebben az esetben egy kis ablakban jelennek meg,  felsorolásszerűen. Az első képen a fentiek alapján elkészített űrlap látható, abban a sorrendben ahogy azt a program főrészében a __main__ alatt megadtuk. A két kisebb kép az ablakokat mutatja az űrlapnak megadott adatokkal.

tkinter2
tkinter_msg1
tkinter_msg2

Ablakok nyitása főablakról + adatbekérés

A következő program szintén a tkinter modult hívja meg, a fenti példában bemutatott widgetek felhasználásával. A program egy mezőgazdasági pixelelemző program egy részlete, de funkciójának bemutatásától most eltekintek és a továbbiakban is csak az alcímben foglaltakra koncentrálok. Ezzel a példaprogrammal az új ablak/ablakok főablakról történő megnyitását illetve az azokon elhelyezett widgetek működését mutatom be.

class FormClass():

    def __init__(self, master):
        
        self.master = master
        self.index, self.column = 0, 0
        self.label_width = 30
        self.main_color = "#ffc266"
        
        # Fő ablak - 1.rész

        self.noveny = tk.StringVar()
        self.kepjel = tk.StringVar()
        self.parcella = tk.IntVar()

        # Fő ablak - pixelelemzés

        self.red = tk.IntVar()
        self.green = tk.IntVar()
        self.blue = tk.IntVar()
        self.tavolsag = tk.IntVar()

        # Darabolás 2 ablak

        self.hatarolo = tk.IntVar()
        self.jump = tk.IntVar()        

A FormClass osztály inicializáló metódusában a widgetek paramétereinek induló értékei után azokat a változókat adtam meg, amelyek a felhasználói adatbekéréshez szükségesek.

A FŐABLAK kódrészben adtam meg a főablak tulajdonságait meghatározó paramétereket (cím, háttérszín, méret(fix)) illetve az adatbekérő widgeteket. Az aktív ablakot a master argumentum határozza meg, a főablak esetében a tk.Tk() Python Tkinter objektum.

    " FŐABLAK "

    def window_main(self):
        
        self.master.title("Pixel_RGB_1.0")
        self.master.configure(background= self.main_color) #háttér
        self.master.geometry("700x700")
        self.master.resizable(height=False, width=False)

        # A kép darabolása

        self.main_label(self.master,"A kép darabolása")
        self.param_label(self.master,"Növény: ")

        # Fő ablak 1. része

        self.combo_box(self.master,("repce","búza","cukorrépa","cerkospóra","","egyéb"),self.noveny,pos=1)
        self.param_label(self.master,"Képjel: ")
        self.combo_box(self.master,("A","B","C","D"),self.kepjel,pos=1)
        self.param_label(self.master,"Parcellák: ")
        self.spin_box(self.master,0,200,172,self.parcella,pos=1)
        self.button(self.master,"Darabolás jelölőkkel", self.window_1) # Darabolás jelölőkkel

        # Pixelemezés

        self.main_label(self.master,"Képek pixelelemzése")
        self.param_label(self.master,"Minta RGB:")
        self.spin_box(self.master,0,255,50,self.red,pos=1)
        self.spin_box(self.master,0,255,49,self.green)
        self.spin_box(self.master,0,255,22,self.blue)
        self.param_label(self.master,"Távolság:")
        self.spin_box(self.master,0,35,50,self.tavolsag,pos=1)

        # Végrehajtás

        self.button(self.master,"Elemzés", lambda:[submit(), self.master.destroy()]) # <- multiple command

Az új ablakok úgy vehetik fel a maguk paramétereit, hogy a Toplevel() függvény meghívásával átveszik a főablak aktív szerepét. Ezután nyílik lehetőség az új ablakok formai jellemzőinek meghatározására és a widgetek hozzáadására. Lásd: window_1 és window_2 függvényeket az ABLAKOK kódrészben.

    " ABLAKOK "
    
    def window_1(self):
        
        win = tk.Toplevel(self.master)
        win.title("Darabolás 2")
        win.configure(background= self.main_color) #háttér
        win.geometry("700x400")

        # Widgets:

        self.main_label(win,"Darabolás jelölő vonalakkal")
        self.param_label(win,"Egy-egy parcella elválasztása...")
        self.radio_button(win," 1 jelölővel",self.hatarolo,1)
        self.radio_button(win," 2 jelölővel",self.hatarolo,2)
        self.param_label(win,"Ugrásköz:")
        self.spin_box(win,0,15,10,self.jump,1)
        self.button(win,"Darabol",win.destroy)
        
    def window_2(self):
       
        win = tk.Toplevel(self.master)
        win.title("Ablak 2")
        win.configure(background= self.main_color) #háttér
        win.geometry("400x400")
        self.main_label(win,"Teszt 2")
        self.button(win,"Teszt 2",submit)

A fenti kódrészletben a window_2 a példaprogramban végül nem került meghívásra. A szerepe annak szemléltetésében merül ki, hogy a window_1-hez hasonlóan több, a főoldalról nyitható ablak is kialakítható egyedi paraméterezéssel illetve adatbekerő widgetek meghívásával. Az előző példaprogramhoz képest a widgetek argumentumai is módosultak, ugyanis a master megadása is szükséges lesz, annak tisztázására, hogy az adott widget melyik ablakon szerepel. Vegyük például a spinbox widgetet.

" SPINBOX "

    def spin_box(self,master, from_,to_,set_,value,pos=0):
        
        value.set(set_)
        spin = tk.Spinbox (
        master,
        from_=from_,
        to=to_,
        textvariable=value,
        state='readonly'
        )
        spin.grid(
            pady=(20,10),
            row=self.index-pos,
            column=self.column
        )
        
        self.index += 1

A spinbox widget egyik argumentuma alapján adja vissza a felhasználó által kiválasztott értéket, melyet az osztály egy változója pl., self.noveny= tk.IntVar() tárol (lásd: előző kódrészlet). Megj.: Spinboxra példa a főablakon a Pixelelemzés widgetei.

Az utolsó kódrészletben a a submit függvény illetve a form objektum főablak metódusa látható [form.window_main()]. A submit függvényben meghívjuk a form objektum megfelelő változóit, amelyek értékeit az ablakokon adta meg a felhasználó. Végül a submit függvény meghívja az eredményközlő információs ablakot, ahol megjelenik a megadott adatok listája. A submit függvényben hozhatók majd létre azok az objektumok, függvények is, amelyek elemzéseket, számításokat végezhetnek a bekért adatokat felhasználva.

def submit():
    
    color = (180,50,50)

    # Fő ablak 1. része

    noveny = form.noveny
    kepjel = form.kepjel
    parcella = form.parcella

    # Pixelemezés

    red = form.red
    green = form.green
    blue = form.blue
    tavolsag = form.tavolsag

    # Darabolás 2 ablak

    hatarolo = form.hatarolo
    jump = form.jump
    
    messagebox.showinfo("Eredmények",f"Növény: {noveny.get()}\nKépjel: {kepjel.get()}\nParcellák: {parcella.get()}\nJelölő: {hatarolo.get()}\nJelölő szín: {color}\nUgrásköz: {jump.get()}\nR: {red.get()} G: {green.get()} B: {blue.get()}\nTávolság: {tavolsag.get()}\nAblak 3: {w3.get()}\n")
    

if __name__ == "__main__":
    
    tc = tk.Tk()
    form = FormClass(tc)
    form.window_main()
    tc.mainloop()

A főablak (Pixel_RGB_1.0) képe:

főablak

Darabolás jelölőkkel gombra kattintással a Darabolás 2 ablak jelenik meg.

Darabolás gomb ->

A főablak Elemzés gombjára kattintva megjelennek a felhasználó által, mindkét ablakon megadott adatok.

ablakInfo