TK

Kategória: Python.

Áttekintés

A grafikus felhasználói felületnek az évtizedek során kialakultak szabványosnak, vagy legalább szokásosnak tekinthető komponensei. A különböző rendszerek között persze vannak eltérések, és önmagukban van fejlődés is, ám az alapok többnyire változatlanok.

Nagyon sok grafikus felhasználói felület rendszer létezik. A felhasználó szempontjából néhány példa:

  • Talán a legismertebb a böngésző felülete. EGy tipikus weboldal tartalmaz szöveget, képeket, menürendszert, formanyomtatványt, nyomógombokat stb.
  • A hagyományos ún. desktop alkalmazásoknak is van egy tipikus felépítése. Általában valamilyen ablakban fut. Többnyire felül van a menürendszere, és a menü elrendezésében is sok a hasonlóság a programok között. A programon belül általában szöveggel, különféle beviteli mezőkkel, nyomógombokkal stb. találkozunk.
  • Az okostelefonok elterjedésével azok speciális lehetőségei is egyre nagyobb jelentőséggel bírnak. A képernyő mérete jóval kisebb mint a hagyományos számítógépé, valamint nincsenek olyan, a számítógépeknél megszokott beviteli eszközök, mint pl. a billentyűzet, az egér vagy az érintőpárna. Ugyanakkor bizonyos szempontból egy okostelefon tudása több mint a számítógépé: döntőrészt érintőképernyősök, valamint általában lényegesen több érzékelőt tartalmaznak, amelyek növelik a lehetőségeket: el lehet forgatni, meg lehet rázni stb. AZokat a megoldásokat, amelyek másképp működnek laptopon és okoseszközön, mindkét esetben kihasználva a lehetőséget, reszponzívnak hívjuk.

Technikailag is igen széles a spektrum. Gondoljunk csak bele a következőkbe:

  • Számos operációs rendszer létezik. Számítógépen a legelterjedtebb a Windows, a MacOS és a Linux. Egy bizonyos ponton túl Windows alatt megkerülhetetlen a Microsoft Foundation Classes ismerete, ahogy Linux alatt is az X-felületé. Hasonlóan külön világ a különféle okoseszközök felületei: más az Android, más az Macintosh eszközei, és még számos egyéb lehetőség van, melynek kisebb a piaci részesedése (pl. Nokia).
  • A legtöbb programozási nyelvnek van saját GUI megoldása, és többnyire nem is egy. Sok esetben próbálkoznak operációs rendszer független megoldásokkal, viszont ennek az a hátránya, hogy gyakorlatilag csak a közös metszetet tudják kihasználni. A másik lehetőség az, hogy a programozási nyelv operációs rendszer függő megoldásokat nyújt.

Egy-egy terület önmagában egy külön világ, melynek mély megtanulása igen sok időt és energiát igényel. Az így megszerzett tudás sajnos alig kompatibilis a másikkal: hiába ismeri valaki mondjuk a Windows által nyújtott Microsoft Foundation Classes C++ könyvtárát, ha iPhone-ra kell fejlesztenie, akkor szinte nulláról kell megtanulnia egyrészt a használandó programozási nyelvet, másrészt az ottani GUI könyvtárat.

A Python beépített könyvtár formájában biztosít lehetőséget grafikus felhasználói felület programozására: ez a tkinter. Fontos adalék a történetéhez, hogy a TK felület eredetileg a TCL/TK nyelvben jelent meg, ami olyan jól sikerült, hogy később a Pythonban is létrehozták az interfészt a használatához. Innen a tkinter elnevezés is, ami nem más, mint a TK interface összevonása és rövidítése.

A TK könyvtár megtanulása a többi grafikus felhasználói felülethez képest kifejezetten egyszerű. Majd látni fogjuk, hogy semmit sem kell beállítani és a legegyszerűbb program megírása mindössze pár sor. Van egy nagyon jól követhető egységes logikája, ami megkönnyíti az újabb komponensek megismerését. További előnye, hogy a platformfüggetlen kód eredményez platformfüggő eredményt. Tehát nem kell külön megtanulni a Windows és a Linux grafikus felületének a rejtelmeit, ugyanakkor a rendszer maga kihasználja az ebben rejlő lehetőségeket.

Az egyszerűségnek persze ára is van. A TK programok kinézete inkább emlékeztet az 1990-es évek programjainak a kinézetére mint a maiakra, így igazán modern hatást keltő programokat ezzel sajnos nem tudunk készíteni. Ugyanakkor a Python tkinter könyvtára tartalmaz egy ttk kiegészítést. Az elnevezésben az első t a témára (theme) utal. Ez a következőket tartalmazza:

  • Hagyományos komponensek megújult külsővel és funkcionalitással.
  • Új komponensek.
  • Stílusok használatával a kinézet megváltoztatása.

A hátrányai ellenére van néhány indok, ami miatt mégis érdemes megtanulni ezt a rendszert:

  • Vagány dolog képesnek lenni grafikus felhasználói felületet programozni, és ez (a többivel összehasonlítva) kifejezetten könnyen tanulható.
  • Akkor is érdemes ezzel kezdeni, ha célunk a grafikus felhasználói felületek fejlesztése. Elképzelhető, hogy nem ebben fogunk dolgozni, ám az alapokat itt is meg tudjuk ismerni, és az alapkoncepciók (komponensek, elrendezések, vezérlés) nagyon hasonlók minden rendszerben

Az alábbi weboldalak segítettek ennek az oldalnak az elkészítésében:

Az oldal felépítése a következő:

  • Először eljutunk a grafikus felhasználói felületek "Helló, világ!"-áig, bár - ahogy látni fogjuk - nem teljesen egyértelmű, hogy mi számít annak.
  • Utána átnézzük a legfontosabb komponenseket.
  • Ezt követően elmélyedünk az elrendezésekben.
  • Majd megvizsgáljuk a legfontosabb esemény típusokat.
  • Utána lexikonszerűen átvesszük a többi komponenst, ill. megnézzük, hogy hogyan tudunk mi magunk ilyet készíteni.
  • Megnézzük az előre gyártott dialógus ablakokat.
  • Szó lesz a kinézetről.
  • Végül elkészítünk pár egyszerűbb programot grafikus felhasználói felülettel.

Alapok

Üres ablak

Az abszolút minimum, ami megjelenít valamit, az alábbi 3 sor

import tkinter as tk
ablak = tk.Tk()
ablak.mainloop()

Magyarázat:

  • import tkinter as tk: a tkinter könyvtár betöltése. Konvenció, hogy tk névvel töltjük be.
  • ablak = tk.Tk(): ezzel hozzuk létre a főablakot.
  • ablak.mainloop(): ennek a sorban a megértéshez tudnunk kell, hogy a grafikus felületek alapvetően úgy működnek, hogy van egy végtelen ciklus, ami az eseményeket kezeli. A TK-ban valójában ez az. Az persze jó kérdés, hogy szükség van-e az abszolút minimális programhoz, ugyanis tapasztalatom szerint bizonyos esetekben enélkül is megjelenik az üres ablak, bizonyos esetekben viszont nem.

Az eredmény ígéretes, ám még van hová fejlődni:

üres.png

Egyszerű szöveg

Írjunk valamit az ablakba!

import tkinter as tk
 
ablak = tk.Tk()
címke = tk.Label(ablak, text='Helló, TK!')
címke.pack()
ablak.mainloop()

Magyarázat

  • címke = tk.Label(ablak, text='Helló, TK!'): ezzel hozzuk létre a feliratot. Egyrészt megadjuk, hogy melyik ablakba kerüljön (ami egyébként opcionális; próbáljuk ki!), másrészt azt, hogy mit írjon ki.
  • címke.pack(): ez jeleníti meg a szöveget. Meglepő, hogy az ablakot nem feltétlenül kell megadni, a megjelenítést viszont igen; mintha létezne olyan forgatókönyv, hogy egy szöveget létrehozunk, de nem szeretnénk megjeleníteni. A pack() hívás az elrendezésre vonatkozik. Többféle elrendezés van; az egyik a pack. Ezekről később még lesz szó részletesen. Most csak annyit jegyezzünk meg, hogy nincs alapértelmezett elrendezés, és a pack() a legegyszerűbb módja a komponensek megjelenésének.
hellótk.png

Helló, felhasználó!

Lássunk egy kicsit összetettebb dolgot:

import tkinter as tk
 
def nyomógomb_kattintás():
    név = str(név_bevitel.get())
    if név != "":
        üdvözlés_felirat['text'] = f'Szia, {név}!'
 
ablak = tk.Tk()
ablak.title('TK példa')
név_keret = tk.Frame(ablak)
név_felirat = tk.Label(név_keret, text='Név:')
név_felirat.grid(column=0, row=0)
név_bevitel = tk.Entry(név_keret)
név_bevitel.grid(column=1, row=0)
név_keret.pack()
nyomógomb = tk.Button(ablak, text='Kattints', command=nyomógomb_kattintás)
nyomógomb.pack()
üdvözlés_felirat = tk.Label(ablak, text='Szia!')
üdvözlés_felirat.pack()
ablak.mainloop()

Bizonyos értelemben ez is egy minimalista, ami már tartalmazza az alapokat:

  • feliratot,
  • beviteli mezőt,
  • nyomógombot,
  • a kattintás esemény kezelését,
  • adatok olvasását,
  • adatok módosítását,
  • elrendezést,
  • ráadásul még az ablaknak is van egy normális címe.

Emiatt talán ez tekinthető a grafikus felhasználói felülettel rendelkező programok helló világának.

A részletes magyarázatot az adott komponens tárgyalásánál adom meg; itt csak dióhéjban:

  • tk.Frame: a keretekkel logikai egységeket tudunk létrehozni.
  • grid(…): táblázatos elrendezési forma, ahol az adott komponens helyét egy cella megadásával adjuk meg.
  • tk.Entry: beviteli mező.
  • tk.Button: nyomógomb. A command paramétere adja meg azt a függvényt, ami a kattintáskor fut le.
  • név_bevitel.get(): a beviteli mező tartalmának lekérdezése.
  • üdvözlés_felirat['text'] = f'Szia, {név}!': egy címke tartalmának megváltoztatása.

Ha beírjuk a nevünket és a nyomógombra kattintunk, akkor üdvözöl minket a program.

hellóuser.png

Objektumorientált megközelítés

A fenti példában a komponensek globálisak, ami nagyobb méret esetén teljesen áttekinthetetlenné válik. Emiatt érdemes ehelyett egy objektumorientált megoldást választani. A fenti program objektumorientált megfelelője az alábbi:

import tkinter as tk
 
class Alkalmazás(tk.Frame):
    def __init__(self, ablak):
        super().__init__(ablak)
        self.ablak = ablak
        self.pack()
        self.komponenseket_létrehoz()
 
    def nyomógom_kattintás(self):
        név = str(self.név_bevitel.get())
        if név != "":
            selfdvözlés_felirat['text'] = f'Szia, {név}!'
 
    def komponenseket_létrehoz(self):
        self.név_keret = tk.Frame(self.ablak)
        self.név_felirat = tk.Label(master=self.név_keret, text='Név:')
        self.név_felirat.grid(column=0, row=0)
        self.név_bevitel = tk.Entry(master=self.név_keret)
        self.név_bevitel.grid(column=1, row=0)
        self.név_keret.pack()
        self.nyomógomb = tk.Button(self.ablak, text='Kattints', command=self.nyomógom_kattintás)
        self.nyomógomb.pack()
        selfdvözlés_felirat = tk.Label(self.ablak, text='Szia!')
        selfdvözlés_felirat.pack()
 
Alkalmazás(tk.Tk()).mainloop()

Egyszerű komponensek

A komponensek gyakori megnevezése widget. Bár ez a megnevezés gyakran nem csupán egy-egy konkrét grafikus elemet jelent, hanem azokból megvalósított funkciót is, ami több grafikus elemet tartalmazhat.

Az alábbiak nagy része minden grafikus felhasználói felület rendszeren megtalálható.

Felirat (Label)

Feliratot (vagy más néven címkét) már láttunk korábban. Arra is láttunk példát, hogy hogyan tudjuk megváltoztatni a felirat tartalmát. AZ alábbi kód létrehoz egy üres címkét, és a feliratát a következő sorban állítja be:

import tkinter as tk
 
ablak = tk.Tk()
címke = tk.Label(ablak)
címke['text'] = 'Helló, TK!'
címke.pack()
ablak.mainloop()
label_címke.png

Nyomógomb (Button)

A nyomógombra is láttunk már fent példát. A feliratát a text paraméterrel adhatjuk meg, míg azt, hogy milyen kód fusson le, ha a felhasználó rákattint, azt a command paraméterrel.

import tkinter as tk
 
def kattint():
    print('Kattintás')
 
ablak = tk.Tk()
nyomógomb = tk.Button(ablak, text='Kattints', command=kattint)
nyomógomb.pack()
ablak.mainloop()
button_nyomógomb.png

Jelölőnégyzet (Checkbutton)

A jelölőnégyzetnek két állapota lehet: bekapcsolt vagy kikapcsolt. A jelölőnégyzetek függetlenek egymástól.

Az alábbi kód két jelölőnégyzetet hoz létre:

import tkinter as tk
 
def kiválaszt():
    print('Kiválaszt')
    print('Egyik:', egyik_érték.get())
    print('Másik:', másik_érték.get())
 
ablak = tk.Tk()
 
egyik_érték = tk.BooleanVar()
jelölőnégyzet_egyik = tk.Checkbutton(ablak, text='Egyik', variable=egyik_érték, command=kiválaszt)
jelölőnégyzet_egyik.pack()
 
másik_érték = tk.BooleanVar()
jelölőnégyzet_másik = tk.Checkbutton(ablak, text='Másik', variable=másik_érték, command=kiválaszt)
jelölőnégyzet_másik.pack()
 
ablak.mainloop()

Figyeljük meg a egyik_érték = tk.BooleanVar() és variable=egyik_érték részt! Ezzel összekapcsoljuk az egyik_érték változó értékét a jelölőnégyzet_egyik állapotával.

checkbutton_jelölőnégyzet.png

Rádiógomb (Radiobutton)

A rádiógombok nem önmagukban állnak, hanem egy csoportot képeznek, amelyből pontosan egy elemet lehet kiválasztani.

import tkinter as tk
 
def kiválaszt():
    print('Kiválasztott:', kiválasztott.get())
 
ablak = tk.Tk()
kiválasztott = tk.IntVar()
 
jelölőnégyzet_egyik = tk.Radiobutton(ablak, text='Egyik', variable=kiválasztott, value=1, command=kiválaszt)
jelölőnégyzet_egyik.pack()
 
jelölőnégyzet_másik = tk.Radiobutton(ablak, text='Másik', variable=kiválasztott, value=2, command=kiválaszt)
jelölőnégyzet_másik.pack()
 
ablak.mainloop()

A közös több rádiógomb csoportot is létrehozhatunk, melyhez külön változókat kell létrehoznunk. Itt a változó típusa tk.IntVar(), ugyanis nemcsak kétféle értéket vehet fel, hanem annyit, ahány rádiógomb tartozik ahhoz a csoporthoz. (Ami persze ebben a speciális esetben pont kettő, viszont akárhány lehetne.)

radiobutton_rádiógomb.png

Beviteli mező (Entry)

Ez is a leggyakrabban használt komponensek közé tartozik: egysoros, rövid szöveget lehet beírni, pl. nevet. Az alábbi példa a beviteli mezőn kívül tartalmaz még két, már megismert komponens típust: egy nyomógombot és egy felirat mezőt. A nyomógombon történő kattintás hatására kiolvassuk a beviteli mezőbe írt értéket, és a felirat mezőbe írjuk.

import tkinter as tk
 
def kattint():
    felirat_eredmény['text'] = 'Beírt szöveg: ' + beviteli_mező.get()
 
ablak = tk.Tk()
beviteli_mező = tk.Entry(ablak)
beviteli_mező.pack()
nyomógomb = tk.Button(ablak, text='Kattints', command=kattint)
nyomógomb.pack()
felirat_eredmény = tk.Label(ablak, text='')
felirat_eredmény.pack()
ablak.mainloop()
entry_beviteli.png

Keret (Frame)

A keret lényege az, hogy a logikailag szorosan egybe tartozó komponenseket egy fizikai egységbe helyezze. A bevezető példában láthattunk ilyet: a "Név:" felirat és a tulajdonképpeni név beviteli mező egy logikai egységet alkot. Egyrészt nem lenne jó, hogy ha fizikailag különböző helyre kerülnének, és az is fontos, hogy egymás mellé kerüljenek (és pl. ne egymás alá). Ott tehát létrehoztunk egy keretet, ami az említett két komponenst tartalmazza.

A keretek segítségével egyfajta faszerkezetet hozhatunk létre: keretre helyezhetünk másik keretet, ezt akármilyen mélységben alkalmazva. Célszerű használnunk ahelyett, hogy a komponenseket közvetlenül a főablakra helyeznénk.

Tulajdonságok

A fentiekben a kinézetet alapértelmezett értékeken hagytuk. Ha ez nem megfelelő, akkor módosítani tudunk rajtuk. Lássunk egy példát!

import tkinter as tk
 
ablak = tk.Tk()
ablak.title('Tulajdonságok')
ablak.geometry('250x150')
ablak.resizable(True, False)
tk.Label(
    ablak,
    text='Ez egy\ntöbbsoros\nszöveg.',
    justify=tk.LEFT,
    relief=tk.SUNKEN,
    font='Verdana 14',
    background='yellow',
    foreground='#3000FF',
    width=12,
    height=4,
    padx=10,
    pady=10,
).pack(
    padx=10,
    pady=10,
)
ablak.mainloop()

Az eredmény:

tulajdonságok.png

Az eredményből is látszik, hogy alapvetően nem érdemes változtatni rajta, mert kicsi eséllyel néz majd ki jobban, ill. - ahogy később majd látjuk - inkább a ttk által nyújtott stílusokat érdemes használni. Az, hogy pontosan mi állítható, komponens függő, ám vannak olyan tulajdonságok is, amelyek a komponensek többségénél megtalálhatóak. Lássuk a fentieket, amely egy egyszerű felirat tulajdonságai:

  • text: ez maga a felirat. Felirata egyébként többféle komponensnek is lehet.
  • justify: a szöveg igazítása; a tk.LEFT balra zártat jelent. (Alapértelmezésben középre zárt.)
  • relief: a tk.SUNKEN paraméter megadásával süllyesztett hatást érhetünk el. Kiemelt hatást a tk.RAISED megadásával érhetünk el.
  • font: betűtípus; a betű méretét is itt kell megadnunk.
  • background: a háttérszín megadása. A gyakoribb színeknek külön megnevezésük van.
  • foreground: a szöveg színének megadása. Itt a hexadecimális RGB színkód megadási lehetőséget láthatjuk. Egyébként még számos egyéb szín lehetséges, függően attól, hogy a komponens épp aktív-e, ki van-e szöveg jelölve stb.
  • width, height:] szélesség és magasság képpontban.
  • padx, pady: ezzel azt adjuk meg, hogy mennyi hely legyen a komponens széle és a lényegi része között; jelen esetben a szöveget körbefogó téglalap és a tényleges szöveg között. Értelemszerűen a padx a vízszintes, míg a pady a függőleges hézag méretét definiálja. A pack() hívásnál pedig ugyanez azt jelenti, hogy mekkora legyen a hézag a komponensek között.

A főablak tulajdonságai:

  • title('Tulajdonságok'): az ablak címét adhatjuk meg ezzel.
  • geometry('250x150'): az ablak alapértelmezett méretei. Figyeljük meg, hogy a eltér a főablak és a komponens méret megadása.
  • resizable(True, False): ezzel azt adjuk meg, hogy melyik irányba méretezhető át az ablak. A példában vízszintesen átméretezhető, de függőlegesen nem.

Elrendezések

Az eddigi példák alig néhány grafikus elemet tartalmaztak, ám még a kisebb, de valós programok is ennél jóval többet tartalmaznak. Már a legegyszerűbb komplexitású esetekben is szükség van a grafikus elemek elrendezésének finomhangolására. Ebben a fejezetben ezeket a lehetőségeket vizsgáljuk meg.

Az rendszerfüggő, hogy hol milyen elrendezések léteznek. A TK három lehetőséget biztosít; ezek egyébként a legtöbb grafikus rendszerben megtalálhatóak és a leggyakoribbak.

Pack

A kezdeti lépéseknél ez a legegyszerűbb elrendezési forma: sorba rakja az elemeket, egymás után. Egészen pontosan: a TK alapértelmezésben egymás alá:

import tkinter as tk
 
ablak = tk.Tk()
tk.Label(ablak, text='alma').pack()
tk.Label(ablak, text='körte').pack()
tk.Label(ablak, text='szilva').pack()
tk.Label(ablak, text='barack').pack()
ablak.mainloop()
pack_alap.png

Bonyolítani lehet a dolgot azzal, hogy megadjuk, melyik részén kezdje el pakolni. Négyféle irányt adhatunk meg: bal, jobb, fent és lent. Az alábbi példában balról jobbra helyezzük az elemeket:

import tkinter as tk
 
ablak = tk.Tk()
tk.Label(ablak, text='alma').pack(side=tk.LEFT)
tk.Label(ablak, text='körte').pack(side=tk.LEFT)
tk.Label(ablak, text='szilva').pack(side=tk.LEFT)
tk.Label(ablak, text='barack').pack(side=tk.LEFT)
ablak.mainloop()
pack_baljobb.png

Mind a négy irány megadása:

import tkinter as tk
 
ablak = tk.Tk()
tk.Label(ablak, text='alma').pack(side=tk.TOP)
tk.Label(ablak, text='körte').pack(side=tk.BOTTOM)
tk.Label(ablak, text='szilva').pack(side=tk.LEFT)
tk.Label(ablak, text='barack').pack(side=tk.RIGHT)
ablak.mainloop()
pack_négyirány.png

Itt viszont már nem mindegy, hogy milyen sorrendben adjuk meg a dolgokat. Nagyon kevés az a sorrend, amely nagyjából olyan eredményt ad, amit elvárunk; a legtöbb esetben "szétcsúszik".

Place

Ezzel a módszerrel a komponensek abszolút pozícióját adhatjuk meg. Ez ad legnagyobb szabadságot a programozó kezébe, ugyanakkor a legrugalmatlanabb megoldást eredményezi, és a legnehezebb a karbantartása, így a nagyon indokolt eseteket kivéve érdemes elkerülni.

Lássunk egy példát!

import tkinter as tk
 
ablak = tk.Tk()
ablak.geometry('200x200')
tk.Label(text='alma').place(x=20, y=20)
tk.Label(text='körte').place(x=50, y=40)
tk.Label(text='szilva').place(x=80, y=60)
tk.Label(text='barack').place(x=110, y=80)
ablak.mainloop()
place_abszolút.png

Más elrendezési módszerrel igen nehézkes lenne egy ehhez hasonlót összehozni. Viszont képzeljük el, hogy be szeretnénk szúrni egy elemet a második és a harmadik közé! Vagy tegyük fel, hogy van 20 elem, és valahova középre kellene beszúrni egy újabb elemet: a pack elrendezéssel egyszer lenne, a place elrendezéssel viszont rémálom.

A fenti esetben abszolút pozíciókat adtunk meg, így a feliratok helye átméretezéskor sem változott. Megadhatunk relatív pozíciót is:

import tkinter as tk
 
ablak = tk.Tk()
ablak.geometry('200x200')
tk.Label(text='alma').place(relx=0.2, rely=0.2)
tk.Label(text='körte').place(relx=0.3, rely=0.4)
tk.Label(text='szilva').place(relx=0.4, rely=0.6)
tk.Label(text='barack').place(relx=0.5, rely=0.8)
ablak.mainloop()
place_relatív.png

Itt azt mondtuk meg, hogy pl. a szilva felirat bal felső pontja vízszintesen 40%-nál, függőlegesen pedig 60%-nál legyen. Ennek átméretezéskor van szerepe; a megadott értékek az aktuális ablakmérethez képest relatívak.

Arra is van mód, hogy ne a bal felső sarok relatív pozícióját adjuk meg. Ehhez az anchor paramétert kell megadnunk egy égtáj rövidítést:

import tkinter as tk
 
ablak = tk.Tk()
ablak.geometry('200x200')
tk.Label(text='alma').place(relx=0.2, rely=0.2, anchor='ne')
tk.Label(text='körte').place(relx=0.3, rely=0.4, anchor='se')
tk.Label(text='szilva').place(relx=0.4, rely=0.6, anchor='nw')
tk.Label(text='barack').place(relx=0.5, rely=0.8, anchor='sw')
ablak.mainloop()
place_anchor.png

Grid

Ezzel a módszerrel egy képzeletbeli táblázat celláiba helyezzük a komponenseket. Az elrendezésnél tehát azt kell megadnunk, hogy melyik sor melyik oszlopába kerüljön az adott elem. A sorok és az oszlopok sorszámozása is 0-tól indul. A méretezéssel nem kell foglalkoznunk; az automatikusan az adott sor legmagasabb ill. adott oszlop legszélesebb komponenséhe igazodik.

Lássunk egy 2x2-es elrendezésű példát!

import tkinter as tk
 
ablak = tk.Tk()
tk.Label(text='alma').grid(row=0, column=0)
tk.Label(text='körte').grid(row=0, column=1)
tk.Label(text='szilva').grid(row=1, column=0)
tk.Label(text='barack').grid(row=1, column=1)
ablak.mainloop()
grid_alap.png

Finomhangolásra itt is leginkább átméretezéskor van szükség. A fenti példában az összes elem be van tömörítve a bal felső sarokba, és ez nem változik átméretezéskor sem. Az alábbi példában súlyt adunk az oszlopoknak: az elsőnek (0 sorszámú) 1-et, a másodiknak (1 sorszámú) pedig 3-at. Ez azt jelenti, hogy a vízszintes helyet 1:3 arányban osztja fel, azaz a második oszlop háromszor szélesebb lesz mint az első. Ez akkor különösen szembetűnő, ha kellően szélesre méretezzük át az ablakot.

import tkinter as tk
 
ablak = tk.Tk()
ablak.columnconfigure(0, weight=1)
ablak.columnconfigure(1, weight=3)
tk.Label(text='alma').grid(row=0, column=0)
tk.Label(text='körte').grid(row=0, column=1)
tk.Label(text='szilva').grid(row=1, column=0)
tk.Label(text='barack').grid(row=1, column=1)
ablak.mainloop()
grid_súly.png

Az előző példában az elemek a cella közepén vannak. Arra is van mód, hogy megadjuk, a cella melyik részéhez "tapadjanak", mégpedig a sticky attribútummal:

import tkinter as tk
 
ablak = tk.Tk()
ablak.columnconfigure(0, weight=1)
ablak.columnconfigure(1, weight=3)
tk.Label(text='alma').grid(row=0, column=0, sticky=tk.N)
tk.Label(text='körte').grid(row=0, column=1, sticky=tk.W)
tk.Label(text='szilva').grid(row=1, column=0, sticky=tk.E)
tk.Label(text='barack').grid(row=1, column=1, sticky=tk.S)
ablak.mainloop()
grid_sticky.png

Események

A grafikus felhasználói felülettel rendelkező programok nélkülözhetetlen eleme az eseményvezérlés. Ezek a műveletek természetszerűleg aszinkronok: tipikusan felhasználói interakció váltja ki, és a program fő belépési pontjából tipikusan elérhetetlenek. Emiatt ezek a rendszerek döntőrészt többszálúak. Ez a logika az eddig megszokottakhoz képest új gondolkodásmódot kíván.

A command paraméter

Számos komponensnek van alapértelmezett parancs művelete, melyre már láttunk példákat:

  • Ahol volt nyomógomb ("Helló, felhasználó!" példa, maga a nyomógomb alfejezet, beviteli mező) a nyomógomb lenyomása eseményt kezeltük le.
  • A jelölőnégyzet és a rádiógomb példákban is láttunk eseményvezérlést, ahhoz az adott jelölők kiválasztása volt a lekezelt esemény.

Hogy itt is legyen egy példa, de ne pont ugyanaz, függvény megadása helyett egysoros lambdával valósítjuk meg:

import tkinter as tk
 
ablak = tk.Tk()
tk.Button(ablak, text='Kattints', command = lambda: print('Kattintás')).pack()
ablak.mainloop()

Ha paramétert is át szeretnénk adni, akkor kombinálni kell a lambdát a függvénnyel:

import tkinter as tk
 
def kattintás(gyümölcs):
    print(gyümölcs)
 
ablak = tk.Tk()
tk.Button(ablak, text='Alma', command = lambda: kattintás('alma')).pack()
tk.Button(ablak, text='Körte', command = lambda: kattintás('körte')).pack()
ablak.mainloop()

Egér események

A nyomógomb természetes eseménye a kattintás. Magát a kattintást mint műveletet viszont más komponensekhez is hozzárendelhetjük.

import tkinter as tk
 
def egérkattintás(esemény):
    print('Egérkattintás:', esemény.num)
 
ablak = tk.Tk()
felirat = tk.Label(ablak, text='Kattints!', padx=20, pady=20, background='white')
felirat.pack(padx=20, pady=20)
felirat.bind('<Button-1>', egérkattintás)
felirat.bind('<Button-3>', egérkattintás)
ablak.mainloop()

Ezzel gyakorlatilag címke komponensből létrehoztunk egy nyomógombot. A bal és a jobb egér kattintására is reagál.

A gyakorlatban az ilyesmit érdemes elkerülni, mert félreviszi a felhasználót, de jó, ha tudunk erről a lehetőségről.

Billentyű események

A billentyű események nem feltétlenül kapcsolódnak egy-egy konkrét komponenshez. Az alábbi példában a billentyű lenyomás és elengedés eseményeket kezeljük le globálisan:

import tkinter as tk
 
def billentyű_esemény(esemény):
    match esemény.keysym:
        case 'Left': billenytű = 'balra nyíl'
        case 'Right': billenytű = 'jobbra nyíl'
        case 'Up': billenytű = 'felfele nyíl'
        case 'Down': billenytű = 'lefele nyíl'
        case 'space': billenytű = 'szóköz'
        case _: billenytű = esemény.char
    match esemény.type:
        case tk.EventType.KeyPress: esemény_típus = 'lenyomva'
        case tk.EventType.KeyRelease: esemény_típus = 'elengedve'
    print(f'{billenytű} {esemény_típus}')
 
ablak = tk.Tk()
ablak.bind('<KeyPress>', billentyű_esemény)
ablak.bind('<KeyRelease>', billentyű_esemény)
ablak.mainloop()

Ugyanazt a függvényt rendeljük mindkét eseményhez. (Természetesen külön függvényeket is használhatunk.) A függvényen belül példákat láthatunk az esemény részleteinek a lekérdezéséhez:

  • esemény.type: az esemény típusát adja meg, pl. tk.EventType.KeyPress, tk.EventType.KeyRelease
  • esemény.keysym: a kérdéses billentyű kódját adja meg, pl. 'Left' a balra nyilat jelenti.
  • esemény.char: a kérdéses billenytű karakter megfelelőjét jelenti, pl. az a az a karakterét.

Az eredmény valami ehhez hasonló:

jobbra nyíl lenyomva
jobbra nyíl elengedve
szóköz lenyomva
szóköz elengedve
a lenyomva
a elengedve

További komponensek

Legördülő lista ({{ttk.Combobox})

A legördülő lista lényege az, hogy előre beírt értékekből lehet választani. A mező lehet szabadon szerkeszthető is; ez esetben ez a komponens felülről kompatibilis az alap beviteli mezővel.

A TK alapból nem tartalmaz ilyen komponenst, a ttk része viszont igen. Példa kód:

import tkinter as tk
import tkinter.ttk as ttk
 
def kiválaszt(esemény):
    felirat_eredmény['text'] = 'Beírt szöveg: ' + legördülő_mező.get()
 
ablak = tk.Tk()
legördülő_mező = ttk.Combobox(ablak, values=['alma', 'körte', 'szilva'])
legördülő_mező.bind('<<ComboboxSelected>>', kiválaszt)
legördülő_mező.pack()
nyomógomb = tk.Button(ablak, text='Kattints', command=lambda: kiválaszt(None))
nyomógomb.pack()
felirat_eredmény = tk.Label(ablak, text='')
felirat_eredmény.pack()
ablak.mainloop()

Figyeljük meg a kódban azt, hogy a tkinter.ttk-t ttk néven importáljuk; ez is konvenció. Ha futtatjuk, akkor kiválaszthatunk egy elemet a listából, de mi magunk is beírhatunk értékeket.

combobox_legördülő.png

Lista doboz (Listbox)

Funkciójában hasonlít a legördülő listára. A különbség a kettő megnevezéséből adódik: a legördülő listában alapból egy elem látszódik, és a többi legörüléskor jelenik meg, míg a lista dobozba egyszerre több is látszódik.

import tkinter as tk
 
ablak = tk.Tk()
 
listadoboz = tk.Listbox(ablak, selectmode=tk.MULTIPLE)
listadoboz.insert(1, 'alma')
listadoboz.insert(2, 'körte')
listadoboz.insert(3, 'szilva')
listadoboz.pack()  
 
tk.Button(ablak, text='Kattints', command=lambda: print(listadoboz.curselection())).pack()
 
ablak.mainloop()

A példában a többszörös választási lehetőség is be van kapcsolva (selectmode=tk.MULTIPLE). Az aktuálisan kiválasztott értékeket a curselection() hívással tudjuk lekérdezni, ami egy módosíthatatlan lista (tuple) formában adja vissza az eredményt.

listbox_listadoboz.png

Léptető doboz (Spinbox)

Ennek segítségével számokat lehet növelni ill. csökkenteni. Különösen akkor érdemes a használatát megfontolni, ha nincs túl sok lehetőség.

import tkinter as tk
 
def kiválaszt():
    print(léptetődoboz.get())
 
ablak = tk.Tk()
léptetődoboz = tk.Spinbox(ablak, from_=1, to=6, increment=1, command=kiválaszt)
léptetődoboz.pack()
ablak.mainloop()
spinbox_léptetődoboz.png

Skála (Scale)

Ha a megadandó érték egy (nem feltétlenül egész) szám egy jól meghatározott nem túl sok értéket tartalmazó intervallumon belül, akkor érdemes megfontolni a csúszkás megoldást.

import tkinter as tk
 
def beállít(érték):
    print(érték)
 
ablak = tk.Tk()
skála = tk.Scale(ablak, from_=0, to=10, resolution=0.5, orient=tk.HORIZONTAL, command=beállít)
skála.pack()
ablak.mainloop()
scale_skála.png

Szövegbeviteli mező (Text)

Idáig csak egysoros beviteli mezővel (ill. mezőkkel) találkozunk. A többsorosra példa:

import tkinter as tk
 
def kattint():
    felirat_eredmény['text'] = 'Beírt szöveg:\n' + többsoros_beviteli_mező.get('1.0', tk.END)
 
ablak = tk.Tk()
többsoros_beviteli_mező = tk.Text(ablak, height=5, width=20)
többsoros_beviteli_mező.pack()
nyomógomb = tk.Button(ablak, text='Kattints', command=kattint)
nyomógomb.pack()
felirat_eredmény = tk.Label(ablak, text='')
felirat_eredmény.pack()
ablak.mainloop()

A szöveg lekérdezése nem nyilvánvaló. Kiolvasáskor a get() függvénynek meg kell adni az elejét és a végét, mindkettőt 'SOR.OSZLOP' formában. Hogy még bonyolultabb legyen: a sorok sorszámozása 1-től, míg az oszlopoké 0-tól történik. (Python mércével mérve ez kimondottan nehézkes.) A tk.END konstans a szöveg végét jelenti. Így a teljes szöveg kijelölése: ('1.0', tk.END). Használhatunk még olyan kifejezéseket, hogy 'linestart' (sor eleje), 'lineend' (sor vége), 'end' (szöveg vége), 'end - 1 chars' (utolsó előtti karakter), 'end - 1 lines' (utolsó előtti sor) stb.; szükség esetén a specifikációt kell megnézni.

text_többsoros.png

Fa nézet (ttk.Treeview)

A fa nézet megnevezés kissé félrevezető: valójában táblázatos megjelenítést lehet ennek segítségével létrehozni:

import tkinter as tk
import tkinter.ttk as ttk
 
def kiválaszt(esemény):
    print('Kiválasztottak:')
    for kiválasztott in fanézet.selection():
        print(fanézet.item(kiválasztott)['values'])
 
ablak = tk.Tk()
fanézet = ttk.Treeview(ablak, columns=['gyümölcs', 'darab'], show='headings')
fanézet.heading('gyümölcs', text='Gyümölcs')
fanézet.heading('darab', text='Darabszám')
fanézet.insert('', tk.END, values=('alma', 3))
fanézet.insert('', tk.END, values=('banán', 2))
fanézet.insert('', tk.END, values=('körte', 5))
fanézet.bind('<<TreeviewSelect>>', kiválaszt)
fanézet.pack()
ablak.mainloop()
treeview_fanézet.png

Folyamatjelző (Progressbar)

Hosszabb folyamat esetén érdemes a felhasználót értesíteni arról, hogy nagyjából hol tart a dolog. Valójában kétféle folyamatjelző létezik: az egyik amikor tudjuk, hogy mennyi van még hátra, kb. hány százaléknál, tartunk, a másiknál viszont nem. Ez utóbbi egyik első, széles körben megismert megnyilvánulási formája a homokóra. A módszerek elég gyorsan gyűlöltté válnak, így sokféle módszer jött létre az évek során. Amit viszont mindenképpen illik elkerülni az az, hogy ha nem tudjuk, mennyi van még hátra, ne alkalmazzunk olyan jelölőt, amit a másikra találtak ki.

A TK önmagában nem, a TTK kiterjesztése viszont tartalmaz folyamatjelzőt. Ezt többféleképpen lehet konfigurálni; lehet fekvő és álló; lehet determinált és nemdeterminált stb., melynek részleteit szükség estén a specifikációban találjuk. EDgy példa fekvő determinált folyamatjelzőre az alábbi:

import tkinter as tk
import tkinter.ttk as ttk
 
def léptet():
    if folyamatjelző['value'] < 90.0:
        folyamatjelző.step(10)
    else:
        folyamatjelző['value'] = 100.0
 
ablak = tk.Tk()
folyamatjelző = ttk.Progressbar(ablak, mode='determinate')
folyamatjelző.pack()
tk.Button(ablak, text='Léptet', command=léptet).pack()
ablak.mainloop()
progressbar_folyamatjelző.png

Vászon Canvas

Ez a komponens kínálja a legtöbb lehetőséget: ez egy téglalap alakú komponens, amit szabadon "telerajzolhatunk".

import tkinter as tk
 
ablak = tk.Tk()
vászon = tk.Canvas(ablak, background='white')
vászon.create_oval(40, 40, 80, 80, width=3, outline='black', fill='yellow')
vászon.create_rectangle(120, 40, 160, 80, width=3, outline='black', fill='yellow')
vászon.create_arc((200, 40, 240, 80), start=45, extent=270, width=3, outline='black', fill='yellow')
vászon.create_polygon((280, 80, 300, 40, 320, 80), width=3, outline='black', fill='yellow')
logó = tk.PhotoImage(file='logo.png')
vászon.create_image(60, 140, image=logó)
vászon.create_text(180, 140, text='Heló, világ!', font=('Helvetica 15 bold'))
vászon.create_line((280, 120, 320, 160), width=3)
vászon.create_line((280, 160, 320, 120), width=3)
vászon.pack()
ablak.mainloop()

A példában többféle objektumot rajzoltunk:

  • create_oval: ezzel oválist lehet rajzolni, a képzeletbeli befoglaló téglalap bal felső és jobb alsó pontjának megadásával.
  • create_rectangle(): téglalapot lehet rajzolni, a bal felső és a jobb alsó pont megadásával.
  • create_arc(): körívet lehet rajzolni, a befoglaló téglalap adatainak, a kiinduló és a megrajzolandó szögnek megadásával.
  • create_polygon(): sokszöget lehet rajzolni, a csúcsok koordinátáinak megadásával. A poligont bezárja.
  • create_image(): kép készítése.
  • create_text(): szöveg írása.
  • create_line(): egyenes rajzolása.

A fill paraméterrel a kitöltést adjuk meg.

canvas_vászon.png

Egyszerűbb játékokat is megvalósíthatunk ennek segítségével, ugyanis a vászonra ráhelyezett komponensek mozgathatók.

import tkinter as tk
 
def gomb_lenyomva(esemény):
    kör_koordináták = canvas.coords(kör)
    if esemény.keysym == 'Left' and kör_koordináták[0] > 0:
        canvas.move(kör, -2, 0)
    if esemény.keysym == 'Right' and kör_koordináták[0] <= 180:
        canvas.move(kör, 2, 0)
    if esemény.keysym == 'Up' and kör_koordináták[1] > 0:
        canvas.move(kör, 0, -2)
    if esemény.keysym == 'Down' and kör_koordináták[1] <= 180:
        canvas.move(kör, 0, 2)
 
ablak = tk.Tk()
canvas = tk.Canvas(ablak, width=200, height=200, bg='black')
canvas.pack()
kör = canvas.create_oval(90, 90, 110, 110, width=3, outline='black', fill='yellow')
ablak.bind('<KeyPress>', gomb_lenyomva)
ablak.mainloop()

Egy komplett játékot találhatunk a Python gyerekeknek oldalon.

Menü (Menu)

A legtöbb ablakos programnak van menüje. Ez döntőrészt a felső részen található, és van neki egy jól meghatározott struktúrája. Akármilyen rendszerben is vagyunk, a menüt egyesével kell felépíteni, így a programnak ez a része tipikusan hosszabb a többi komponenssel kapcsolatos részhez képest. Az alábbi egy viszonylag egyszerű menüt valósít meg:

import tkinter as tk
 
def parancs(művelet):
    print(művelet + ' végrehajtva')
 
ablak = tk.Tk()
 
főmenü = tk.Menu(ablak)
 
fájlmenü = tk.Menu(főmenü, tearoff=0)
fájlmenü.add_command(label='Új', command=lambda: parancs('új'))
fájlmenü.add_command(label='Nyit', command=lambda: parancs('nyit'))
fájlmenü.add_command(label='Ment', command=lambda: parancs('ment'))
fájlmenü.add_separator()
fájlmenü.add_command(label = 'Kilép', command=ablak.destroy)
főmenü.add_cascade(label='Fájl', menu=fájlmenü)
 
szerkesztmenü = tk.Menu(főmenü, tearoff=0)
szerkesztmenü.add_command(label='Keres', command=lambda: parancs('keres'))
szerkesztmenü.add_command(label='Cserél', command=lambda: parancs('cserél'))
szerkesztmenü.add_command(label='Visszavon', command=lambda: parancs('visszavon'))
főmenü.add_cascade(label='Eszközök', menu=szerkesztmenü)
 
súgómenü = tk.Menu(főmenü, tearoff=0)
súgómenü.add_command(label='Dokumentáció', command=lambda: parancs('dokumentáció'))
főmenü.add_cascade(label='Súgó', menu=súgómenü)
 
ablak.config(menu=főmenü)
ablak.mainloop()
menü.png

Görgetősáv (Scrollbar)

A rendszer sajnos nem hoz létre automatikusan görgetősávot, ezt magunknak kell létrehoznunk. Példa:

import tkinter as tk
 
ablak = tk.Tk()
 
vászon = tk.Canvas(ablak, width=300, height=300, scrollregion=(0, 0, 500, 500))
vászon.create_oval(100, 100, 400, 400)
 
vízszintes_gördülősáv = tk.Scrollbar(ablak, orient=tk.HORIZONTAL)
vízszintes_gördülősáv.pack(side=tk.BOTTOM, fill=tk.X)
vízszintes_gördülősáv.config(command=vászon.xview)
 
függőleges_gördülősáv = tk.Scrollbar(ablak, orient=tk.VERTICAL)
függőleges_gördülősáv.pack(side=tk.RIGHT, fill=tk.Y)
függőleges_gördülősáv.config(command=vászon.yview)
 
vászon.config(xscrollcommand=vízszintes_gördülősáv.set, yscrollcommand=függőleges_gördülősáv.set)
vászon.pack(expand=True, fill=tk.BOTH)
 
ablak.mainloop()
scrollbar_görgetősáv.png

Fül (ttk.Notebook)

A helyhiány gyakori kezelési lehetősége a fülek használata. Általában az angolban ezt úgy hívjuk, hogy tab, de a TK-ban valamiért notebook (füzet) lett az elnevezése. Fül az alap TK-ban nincs, a ttk.Notebook komponenst használhatjuk. Példa három fülre:

import tkinter as tk
import tkinter.ttk as ttk
 
ablak = tk.Tk()
 
fülek = ttk.Notebook(ablak)
fülek.pack()
 
keret1 = tk.Frame(fülek)
tk.Label(keret1, text='Ez az első fül').pack()
keret1.pack()
fülek.add(keret1, text='Első')
 
keret2 = tk.Frame(fülek)
tk.Label(keret2, text='Ez a második fül').pack()
keret2.pack()
fülek.add(keret2, text='Második')
 
keret3 = tk.Frame(fülek)
tk.Label(keret3, text='Ez a harmadik fül').pack()
keret3.pack()
fülek.add(keret3, text='Harmadik')
 
ablak.mainloop()
notebook_fül.png

Átméretező (ttk.Sizegrip)

Ez a komponens egy példa arra, hogy milyen apróságokra is lehet ill. sokszor kell is figyelni. Ez a jobb alsó sarokban helyezett néhány halványszürke pöttyel jelzi, hogy az adott ablak átméretezhető, ill. segítséget ad a felhasználónak arra is, hogy jobban "meg tudja fogni" a sarkat.

import tkinter as tk
import tkinter.ttk as ttk
 
ablak = tk.Tk()
tk.Label(ablak, text='Méretezz át!').pack()
ttk.Sizegrip(ablak).pack(expand=True, anchor=tk.SE, padx=5, pady=5)
ablak.mainloop()
sizegrip_átméretező.png

Az egérrel elég a pöttyök környékén kattintani, és sokkal nagyobb területet biztosít.

Az ilyen apróságokon múlik az, hogy az alkalmazásunk mennyire hat professzionálisnak. Természetesen enélkül is lehet jól funkcionáló alkalmazást készíteni, de sokat segít a felhasználónak. És egy vérprofi grafikus felhasználói felület fejlesztő (aki nem én vagyok) jól ismeri az ilyenek csínját-bínját.

Saját widget készítése

Mi magunk is tudunk grafikus komponenst készíteni, majd úgy felhasználni, mintha az beépített lenne. Ehhez a tk.Frame-ből kell származtatni.

Az alábbi példa egy logaritmus skálát valósít meg:

import tkinter as tk
 
class LogScale(tk.Frame):
    def __init__(self, ablak, eleje, lépték, lépések_száma):
        self.aktuális_érték = eleje
        self.eleje = eleje
        self.lépték = lépték
        self.lépések_száma = lépések_száma
        tk.Frame.__init__(self, ablak)
        selfrték = tk.Label(self, text=str(eleje))
        selfrték.pack()
        self.skála = tk.Scale(self, orient=tk.HORIZONTAL, showvalue=0, command=self.módosít)
        self.skála.pack()
 
    def módosít(self, új_érték):
        self.aktuális_érték = self.eleje * self.lépték ** (self.lépések_száma * int(új_érték) / 100)
        selfrték['text'] = f'{self.aktuális_érték:.4}'
 
    def get(self):
        return self.aktuális_érték
 
ablak = tk.Tk()
skála = LogScale(ablak, 0.01, 10, 4)
skála.pack()
nyomógomb = tk.Button(ablak, text='Kattints', command=lambda: print(skála.get()))
nyomógomb.pack()
ablak.mainloop()
logscale.png

Dialógus ablakok

Információ

Információ megjelenítése:

import tkinter.messagebox
tkinter.messagebox.showinfo('Információ', 'Ez egy információ.')
információ.png

Figyelmeztetés

Figyelmeztetés megjelenítése:

import tkinter.messagebox
tkinter.messagebox.showwarning('Figyelmeztetés', 'Ez egy figyelmeztetés.')
figyelmeztetés.png

Hiba

Hiba megjelenítése:

import tkinter.messagebox
tkinter.messagebox.showerror('Hiba', 'Valami hiba történt.')
hiba.png

Eldöntendő kérdés

Igen/nem kérdést a következőképpen tudunk feltenni:

import tkinter.messagebox
válasz = tkinter.messagebox.askyesno('Kérdés', 'Tetszik a leírás?')
print(válasz)
igennem.png

A Yes és a No sajnos angolul van. Az eredmény típusa logikai: True az igen és False a nem.

Megerősítés

Az eldöntendő kérdés speciális esete. A program a felhasználó jóváhagyását kéri egy általában nem visszavonható művelet végrehajtása előtt.

import tkinter.messagebox
válasz = tkinter.messagebox.askokcancel('Biztos?', 'Ez a művelet nem visszavonható. Folytassuk?')
print(válasz)
megerősítés.png

Az eltérés az előzőtől annyi, hogy a Yes és a No helyett OK és Cancel szerepel, szintén csak angolul.

Szöveg bekérés

Szöveget nemcsak beviteli mezővel, hanem dialógus ablakkal is be tudunk kérni:

import tkinter.simpledialog
válasz = tkinter.simpledialog.askstring('Hogy hívnak?', 'Név:')
print(válasz)
szövegbevitel.png

Egész szám bekérés

Egész szám bekérése:

import tkinter.simpledialog
válasz = tkinter.simpledialog.askinteger('Hány éves vagy?', 'Életkor:')
print(válasz)
egészbevitel.png

Lebegőpontos szám bekérés

Mivel a lebegőpontos számok számábrázolása eltér az egész számokétól, a bekérést is külön valósították meg.

import tkinter.simpledialog
válasz = tkinter.simpledialog.askfloat('Mekkora a testmagasságod méterben?', 'Magasság:')
print(válasz)
lebegőpontos_bevitel.png

Fájlválasztó

Fájlt az alábbi módon tudunk választani:

import tkinter.filedialog
válasz = tkinter.filedialog.askopenfile()
print(válasz.name)
fájlválasztás.png

Könyvtár választó

A fájl választáshoz hasonló:

import tkinter.filedialog
válasz = tkinter.filedialog.askdirectory()
print(válasz)

Inkonzisztens módon az eredmény egy string, és nem egy struktúra, mint a fájl esetén.

könyvtár_választás.png

Színválasztó

A színválasztót az alábbi módon tudjuk használni:

import tkinter.colorchooser
válasz = tkinter.colorchooser.askcolor()
print(válasz[0])

Az eredmény egy elem kettes, melynek első eleme a piros-kék-zöld elem hármasa, a második meg a hexadecimális formája.

színválasztó.png

Kinézet

A fentiekben csak arra fókuszáltunk, hogy működjön; most megnézzük azt is, hogy hogyan lehet szép.

Téma komponensek

Már láttunk ttk komponenseket. Az alap TK komponensek mindegyikét elkészítették TTK formában is, melynek a következők az okai:

  • Lehetővé teszi a stílus kezelést (ld. lejjebb).
  • Modernebb kinézetet kölcsönöz.

Példaként tekintsük a "Heló, felhasználó!" programot, de most tisztán TTK komponensekkel:

import tkinter as tk
import tkinter.ttk as ttk
 
def nyomógomb_kattintás():
    név = str(név_bevitel.get())
    if név != "":
        üdvözlés_felirat['text'] = f'Szia, {név}!'
 
ablak = tk.Tk()
ablak.title('TK példa')
név_keret = ttk.Frame(ablak)
név_felirat = ttk.Label(név_keret, text='Név:')
név_felirat.grid(column=0, row=0)
név_bevitel = ttk.Entry(név_keret)
név_bevitel.grid(column=1, row=0)
név_keret.pack()
nyomógomb = ttk.Button(ablak, text='Kattints', command=nyomógomb_kattintás)
nyomógomb.pack()
üdvözlés_felirat = ttk.Label(ablak, text='Szia!')
üdvözlés_felirat.pack()
ablak.mainloop()
ttk_komponensek.png

Különbségek az alap és a TTK között, amit idáig észrevettem:

  • A TTK nyomógombnak modernebb a kinézete mint az alapé.
  • Az alap nyomógombnak két állapota van: alap és lenyomott, míg a TTK nyomógombnak négy: alap, felette van az egér, lenyomott, felengedett aktív.
  • Az alap beviteli mező mindig ugyanúgy néz ki, míg a TTK háromféle állapotot vesz fel: alap, felette van az egér, aktív.

Minden bizonnyal vannak még egyebek, amelyek fel sem tűntek, ill. a többi komponens is sok újdonságot tartalmazhat.

Stílusok

Módosítsunk az alapértelmezett kinézeten! A módosítás legyen az alábbi: a háttérszín legyen sárga, a betűtípus Verdana, a betűméret pedig 14.

import tkinter as tk
 
def nyomógomb_kattintás():
    név = str(név_bevitel.get())
    if név != "":
        üdvözlés_felirat['text'] = f'Szia, {név}!'
 
ablak = tk.Tk()
ablak.title('TK példa')
név_keret = tk.Frame(ablak)
név_felirat = tk.Label(név_keret, text='Név:', background='yellow', font=('Verdana', 14))
név_felirat.grid(column=0, row=0)
név_bevitel = tk.Entry(név_keret, background='yellow', font=('Verdana', 14))
név_bevitel.grid(column=1, row=0)
név_keret.pack()
nyomógomb = tk.Button(ablak, text='Kattints', background='yellow', font=('Verdana', 14), command=nyomógomb_kattintás)
nyomógomb.pack()
üdvözlés_felirat = tk.Label(text='Szia!', background='yellow', font=('Verdana', 14))
üdvözlés_felirat.pack()
ablak.mainloop()

Az eredmény:

sárga.png

Így tehát lényegében bármit be tudunk állítani, viszont az vele a probléma, hogy minden egyes komponensnél ezt meg kell adni. És elég macerás lenne átállítani, ha a megbízó azt mondja, hogy "szép, szép, de a betűméret legyen egy picivel kisebb és a szín pedig a sárgának egy halványabb árnyalata". Ez esetben ha van mondjuk 500 grafikus elem, akkor mind az 500 helyen egyesével át kell írni.

Ehelyett emiatt is érdemes a TTK komponenseket alkalmazni, ugyanis ott központilag tudjuk ezeket konfigurálni. Mindegyik TTK komponensnek van egy stílusa, amit pl. a következőképpen tudunk lekérdezni:

import tkinter.ttk as ttk
print(ttk.Button().winfo_class()) # TButton

A legtöbb esetben egy T betűt kell az osztálynév elé írni (TLabel, TEntry, TButton), bár nem minden esetben; szükség esetén járjunk utána annak, hogy mi a megnevezés.

Saját stílust is létre tudunk hozni a következő konvenció betartásával: megadjuk a saját stílus nevét, majd pontot írunk, és az eredeti stílust, pl. Saját.TButton. Itt pedig át tudjuk állítani azt, amit szeretnénk. Pl.:

import tkinter as tk
import tkinter.ttk as ttk
 
def nyomógomb_kattintás():
    név = str(név_bevitel.get())
    if név != "":
        üdvözlés_felirat['text'] = f'Szia, {név}!'
 
ablak = tk.Tk()
ablak.title('TK példa')
 
stílus = ttk.Style()
stílus.configure('Saját.TLabel', background='yellow', font=('Verdana', 14))
stílus.configure('Saját.TEntry', background='yellow', font=('Verdana', 14))
stílus.configure('Saját.TButton', background='yellow', font=('Verdana', 14))
 
név_keret = ttk.Frame(ablak)
név_felirat = ttk.Label(név_keret, text='Név:', style='Saját.TLabel')
név_felirat.grid(column=0, row=0)
név_bevitel = ttk.Entry(név_keret, style='Saját.TEntry')
név_bevitel.grid(column=1, row=0)
név_keret.pack()
nyomógomb = ttk.Button(ablak, text='Kattints', style='Saját.TButton', command=nyomógomb_kattintás)
nyomógomb.pack()
üdvözlés_felirat = ttk.Label(ablak, text='Szia!', style='Saját.TLabel')
üdvözlés_felirat.pack()
ablak.mainloop()
saját_stílus.png

Itt tehát elég csak komponens típusonként egyszer megadni a kinézetet, és ha módosítani kell, akkor elég azon a pár helyen átírni. Tehát ha a programunk 500 grafikus komponenst tartalmaz, de csak 10 különböző fajtából, akkor a sárgát halványabb sárgára nem 500 csak 10 helyen kell átírni. Sőt, lehetőség van keverni is programon belül a stílusokat.

Ugyanakkor az eredmény nem pont ugyanúgy néz ki, mint amiből kiindultunk. Mintha a beviteli mezőre egyáltalán nem hatna a dolog, és a nyomógombra is másképp, mint számítottunk. Kellő ideig tartó kutakodással sem sikerült megfejtenem ennek az okát, így a leírás ezen része hiányos marad.

Ha a programban nem szeretnénk felhasználni az eredeti stílusokat, akkor eleve azt is módosíthatjuk, és ekkor nem kell megadni a stílust (mivel eleve az):

import tkinter as tk
import tkinter.ttk as ttk
 
def nyomógomb_kattintás():
    név = str(név_bevitel.get())
    if név != "":
        üdvözlés_felirat['text'] = f'Szia, {név}!'
 
ablak = tk.Tk()
ablak.title('TK példa')
 
stílus = ttk.Style()
stílus.configure('TLabel', background='yellow', font=('Verdana', 14))
stílus.configure('TEntry', background='yellow', font=('Verdana', 14))
stílus.configure('TButton', background='yellow', font=('Verdana', 14))
 
név_keret = ttk.Frame(ablak)
név_felirat = ttk.Label(név_keret, text='Név:')
név_felirat.grid(column=0, row=0)
név_bevitel = ttk.Entry(név_keret)
név_bevitel.grid(column=1, row=0)
név_keret.pack()
nyomógomb = ttk.Button(ablak, text='Kattints', command=nyomógomb_kattintás)
nyomógomb.pack()
üdvözlés_felirat = ttk.Label(ablak, text='Szia!')
üdvözlés_felirat.pack()
ablak.mainloop()

Sőt, itt van egy trükk: a pont megadásával egyszerre tudjuk módosítani az összes komponens típus adott tulajdonságát:

stílus.configure('.', background='yellow', font=('Verdana', 14))

Folytatva az előző témát: a configure() mellett használhatjuk a map() függvényt is, ami annyiban tér el az előbbitől, hogy állapotfüggő módon tudjuk megadni a kinézetet. Példa mindkettőre egyszerre:

stílus.configure('TButton', background='yellow', font=('Verdana', 14))
stílus.map('TButton', background=[('pressed', 'red'), ('active', 'blue')])

Ezzel azt mondtuk meg, hogy a nyomógombok színe (valójában: körvonala) alapból legyen sárga; legyen piros, ha le van nyomva; legyen kék, ha nincs lenyomva, de az egérkurzor felette van.

aktív_nyomógomb.png

Itt a sorrend számít; tehát a [('active', 'blue'), ('pressed', 'red')] nem ugyanaz, mint a fenti, és ennek az okát sem sikerült kiderítenem elég hosszú kutakodás árán sem. Valamint sajnos nem találtam összefoglaló leírást a lehetséges értékekről, így kicsit én magam is sötétben tapogatózom ezzel kapcsolatban.

Ez a szakasz így kissé foghíjas lett. Ha sikerül kiderítenem a homályos részeket, akkor módosítom, kiegészítem.

Témák

Ahelyett, hogy egyesével módosítanánk a komponenseket, lehetőségünk van komplett témát választani. Ehhez a ttk.Style osztály megfelelő függvényeit kell használni.

A témák kilistázása és az aktuális téma megjelenítése:

import tkinter as tk
import tkinter.ttk as ttk
 
ablak = tk.Tk()
stílus = ttk.Style(ablak)
print(stílus.theme_names())
print(stílus.theme_use())

Az eredmény rendszerfüggő. Nálam az alábbi témák szerepelnek: winnative, clam, alt, default, classic}, {{vista, xpnative. Tehát látható, hogy elsősorban a Windows-os témák szerepelnek, mivel Windows operációs rendszert használok. Az alapértelmezett - kissé meglepő módon - a vista.

A téma megváltoztatásához az alábbi két sort kell beszúrni:

stílus = ttk.Style(ablak)
stílus.theme_use('winnative')

A teljes kód pl. ez:

import tkinter as tk
import tkinter.ttk as ttk
 
def nyomógomb_kattintás():
    név = str(név_bevitel.get())
    if név != "":
        üdvözlés_felirat['text'] = f'Szia, {név}!'
 
ablak = tk.Tk()
ablak.title('TK példa')
stílus = ttk.Style(ablak)
stílus.theme_use('winnative')
név_keret = ttk.Frame(ablak)
név_felirat = ttk.Label(név_keret, text='Név:')
név_felirat.grid(column=0, row=0)
név_bevitel = ttk.Entry(név_keret)
név_bevitel.grid(column=1, row=0)
név_keret.pack()
nyomógomb = ttk.Button(ablak, text='Kattints', command=nyomógomb_kattintás)
nyomógomb.pack()
üdvözlés_felirat = ttk.Label(ablak, text='Szia!')
üdvözlés_felirat.pack()
ablak.mainloop()

Az eredmény:

téma_winnative.png

Ugyanez clam stílussal:

téma_clam.png

További támákat találunk a https://wiki.tcl-lang.org/page/List+of+ttk+Themes oldalon. Úgy tapasztaltam, hogy témafüggő, hogyan kell telepíteni. Egy példa: Sun Valley (https://github.com/rdbende/Sun-Valley-ttk-theme). Telepítés:

pip install sv-ttk

Használata pl.:

import sv_ttk
sv_ttk.use_dark_theme()

Valójában elég ennyi, és átállítja erre a témára az egész programot:

téma_sun_valley.png

Példák

Lássunk néhány példát grafikus felhasználói felülettel rendelkező alkalmazásra!

Hőmérséklet konverter

Gyakori példafeladat a hőmérséklet konverter, pl. Fahrenheit fokból Celsius fokba. Ez nem sokban tér el egyébként az alapként megadott névbekérős, köszönős programtól. Egy lehetséges megvalósítás az alábbi:

import tkinter as tk
 
def fahrenheitból_celsiusba():
    celsius = (5 / 9) * (float(fahrenheit_beviteli.get()) - 32)
    eredmény_felirat['text'] = f'{round(celsius, 2)} \N{DEGREE CELSIUS}'
 
ablak = tk.Tk()
 
fahrenheit_keret = tk.Frame(ablak)
fahrenheit_beviteli = tk.Entry(fahrenheit_keret, width=10)
fahrenheit_beviteli.pack(side=tk.LEFT)
fahrenheit_felirat = tk.Label(fahrenheit_keret, text='\N{DEGREE FAHRENHEIT}')
fahrenheit_felirat.pack(side=tk.LEFT)
fahrenheit_keret.pack()
 
konvertál_nyomógomb = tk.Button(ablak, text='Konvertál', command=fahrenheitból_celsiusba)
konvertál_nyomógomb.pack()
 
eredmény_felirat = tk.Label(ablak, text='')
eredmény_felirat.pack()
 
ablak.mainloop()
hőmérséklet_konverter.png

Szövegszerkesztő

Egyszerű szövegszerkesztő készítése szintén az elsőként elkészített grafikus felülettel rendelkező programok közé tartozik. Az alábbi példában tudunk szerkeszteni, le tudjuk menteni és be tudjuk olvasni:

import tkinter as tk
import tkinter.filedialog
 
def nyit():
    fájlnév = tkinter.filedialog.askopenfilename()
    if not fájlnév:
        return
    szöveg.delete('1.0', tk.END)
    with open(fájlnév) as input_fájl:
        szöveg.insert(tk.END, input_fájl.read())
    ablak.title(fájlnév)
 
def ment():
    fájlnév = tkinter.filedialog.asksaveasfilename()
    if not fájlnév:
        return
    with open(fájlnév, mode='w') as output_fájl:
        output_fájl.write(szöveg.get('1.0', tk.END))
    ablak.title(fájlnév)
 
ablak = tk.Tk()
ablak.title('Névtelen')
 
gombok = tk.Frame(ablak)
tk.Button(gombok, text='Nyit', command=nyit).pack(side=tk.LEFT)
tk.Button(gombok, text='Ment', command=ment).pack(side=tk.LEFT)
gombok.pack()
 
bevitel = tk.Frame(ablak)
szöveg = tk.Text(bevitel, width=40, height=10)
függőleges_gördülősáv = tk.Scrollbar(bevitel, orient=tk.VERTICAL)
függőleges_gördülősáv.config(command=szöveg.yview)
szöveg.config(yscrollcommand=függőleges_gördülősáv.set)
szöveg.pack(side=tk.LEFT)
függőleges_gördülősáv.pack(side=tk.RIGHT, fill=tk.Y)
bevitel.pack()
 
ablak.mainloop()
szövegszerkesztő.png

Az a jó ebben, hogy már ebben az egyszerű formában is használható, ugyanakkor viszonylag egyszerűen tovább is fejleszthető.

Számológép

A számológép szintén a kedvenc "gyógypéldák" közé tartozik. A Pythonban különösen egyszerű a megvalósítása, ugyanis itt kihasználhatjuk azt, hogy az eval() kiértékeli a szövegként átadott kifejezést, mintha Python program volna.

A kinézetén és a használhatóságán is van még mit csiszolni, de egy viszonylag egyszerű megoldás az alábbi:

import tkinter as tk
 
def kattintás(gomb):
    global kifejezés
    if gomb == '=':
        kifejezés = str(eval(kifejezés))
    elif gomb == 'C':
        kifejezés = ''
    else:
        kifejezés += gomb
    művelet['text'] = kifejezés
 
kifejezés=''
ablak = tk.Tk()
tk.Button(ablak, text='1', command=lambda: kattintás('1')).grid(row=0, column=0)
tk.Button(ablak, text='2', command=lambda: kattintás('2')).grid(row=0, column=1)
tk.Button(ablak, text='3', command=lambda: kattintás('3')).grid(row=0, column=2)
tk.Button(ablak, text='4', command=lambda: kattintás('4')).grid(row=1, column=0)
tk.Button(ablak, text='5', command=lambda: kattintás('5')).grid(row=1, column=1)
tk.Button(ablak, text='6', command=lambda: kattintás('6')).grid(row=1, column=2)
tk.Button(ablak, text='7', command=lambda: kattintás('7')).grid(row=2, column=0)
tk.Button(ablak, text='8', command=lambda: kattintás('8')).grid(row=2, column=1)
tk.Button(ablak, text='9', command=lambda: kattintás('9')).grid(row=2, column=2)
tk.Button(ablak, text='0', command=lambda: kattintás('0')).grid(row=3, column=0)
tk.Button(ablak, text='C', command=lambda: kattintás('C')).grid(row=3, column=1)
tk.Button(ablak, text='=', command=lambda: kattintás('=')).grid(row=3, column=2)
tk.Button(ablak, text='+', command=lambda: kattintás('+')).grid(row=0, column=3)
tk.Button(ablak, text='-', command=lambda: kattintás('-')).grid(row=1, column=3)
tk.Button(ablak, text='*', command=lambda: kattintás('*')).grid(row=2, column=3)
tk.Button(ablak, text='/', command=lambda: kattintás('/')).grid(row=3, column=3)
művelet = tk.Label(ablak, text='')
művelet.grid(row=4, column=0, columnspan=3)
ablak.mainloop()
számológép.png

A fenti példában túl soknak tűnik a kódismétlés. Meg lehet oldani a problémát, bár nem triviális. Adná magát az alábbi átalakítás:

for index, felirat in enumerate(['1', '2', '3', '+', '4', '5', '6', '-', '7', '8', '9', '*', '0', 'C', '=', '/']):
    tk.Button(ablak, text=felirat, command=lambda: kattintás(felirat)).grid(row=index//4, column=index%4)

A probléma ezzel az, hogy a lambda kifejezés hívásakor mindig a legutolsó karakter adódik át, tehát a /. A megoldás a következő:

for index, felirat in enumerate(['1', '2', '3', '+', '4', '5', '6', '-', '7', '8', '9', '*', '0', 'C', '=', '/']):
    tk.Button(ablak, text=felirat, command=lambda paraméter=felirat: kattintás(paraméter)).grid(row=index//4, column=index%4)

Tehát létre kell hozni a lambdánál egy segédváltozót, és azt kell átadni paraméterül. A teljes kód:

import tkinter as tk
 
def kattintás(gomb):
    global kifejezés
    if gomb == '=':
        kifejezés = str(eval(kifejezés))
    elif gomb == 'C':
        kifejezés = ''
    else:
        kifejezés += gomb
    művelet['text'] = kifejezés
 
kifejezés=''
ablak = tk.Tk()
for index, felirat in enumerate(['1', '2', '3', '+', '4', '5', '6', '-', '7', '8', '9', '*', '0', 'C', '=', '/']):
    tk.Button(ablak, text=felirat, command=lambda paraméter=felirat: kattintás(paraméter)).grid(row=index//4, column=index%4)
művelet = tk.Label(ablak, text='')
művelet.grid(row=4, column=0, columnspan=3)
ablak.mainloop()

Ahhoz képest nem túl hosszú, hogy egy komplett (bár egyszerű) számológépet valósít meg.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License