Az előző néhány bejegyzésben táblázatba rendezhető adatok feldolgozásával és grafikus megjelenítésével foglalkoztunk. Először egyszerű mátrix létrehozása és ehhez alapműveletek definiálása volt a téma. Ezt követően készítettünk egy Sudoku, majd pedig egy Wordle játékot a táblázatos adatok és azok grafikus felhasználó felületen (GUI) történő megjelentésének valós alkalmazásban történő bemutatására. Láthattunk, hogy a rácsos elrendezés kirajzolása alapjaiban hasonló mintát követett, de a két alkalmazásban e tekintetben mégis volt egy kis eltérés, valami plusz, amiből lehetett újat tanulni.
A mostani bejegyzésben szintén egy olyan programot készítünk, amelynél táblázatba rendezhető adatokkal dolgozunk és jelenítünk meg grafikus felületen: a célunk egy egyszerű naptár alkalmazás elkészítése. Ennek azt kell tudnia, hogy ha megadunk egy évszámot és egy hónapnevet, akkor megjeleníti, hogy a hónap napjai a hét mely napjára esnek.
A napsorszámokat egy olyan táblázatban szeretnénk kiíratni, amelynek oszlopai a hét napjait jelentik hétfőtől vasárnapig. A táblázat annyi sor tartalmaz, amennyi szükséges a hét napjaihoz való hozzárendeléshez. Azon napoknál, amelyekre nem esik az adott hónapban nap, a táblázatcella üres marad.
A grafikus felület táblázatos részét alapjaiban most is hasonló elvek szerint készítjük el a tkinter modullal, mint a Sudoku és Wordle esetében: a táblázat egy keret (Frame) típusú grafikus elemen jelenik meg úgy, hogy a táblázat egyes celláit egy kis, négyzetalakú keret elem rácsos elrendezésével, azaz a grid() metódussal helyezzük le. Az egyes cellakeretekben most viszont nem beviteli mezőket, hanem címke (Label) típusú grafikus elemeket helyezünk le, mert most csak kiírni akarunk adatot és nem bevinni.
A mostani táblázat két szempontból mindenképp eltér a korábbi bejegyzésekben látottaktól. Egyrész az első sor mindig ugyanaz marad, mert itt jelennek meg a hét napjainak a rövidítései.
A másik, hogy most nem egy statikus, azaz változatlan méretű táblázatot kell alkotni, hanem egy olyat, amelyben a sorok száma dinamikusan jelenik meg attól függően, hogy az adott hónap napjai hogyan illeszkednek a hét napjaihoz. Ezért nem elég egyszer létrehozni a táblázatot, és csupán a cellákban levő címkékhez tartozó kontrollváltozók értékét változtatni, hanem minden alkalommal, amikor az évszámot és/vagy a hónapnevet változtatjuk, az ehhez illeszkedő számú sorral magát a táblázatot is újra kell generálni. (És persze az előzőt törölni a keretből). Ehhez definiálunk egy naptárfrissítés() függvényt.
A teljes alkalmazás kódját láthatjuk alább. A részletes kommentek segítik a felépítés és működés megértését.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
from itertools import product import locale, datetime, calendar, tkinter as tk # Beállítjuk a kívánt nyelvterületet, hogy a hónapok nevei, és a napok neveinek rövidítése az adott nyelven jelenjen meg. locale.setlocale(locale.LC_ALL, 'hu_HU') # A napnevek rövidítésének és hónapoknevek összegyűjtése a nyelvterületi beállításnak megfelelően. nap_nevek_röviden = tuple(calendar.day_abbr) hónapnevek = list(calendar.month_name) def naptárfrissítés(naptár: tk.Frame, évszám: tk.StringVar, hónapnév: tk.StringVar, címke_vars: dict): # Az évszámot és hónapnevet tartalmazó kontrollváltozókból kinyerjük az értékeket egész számra # konvertálva, amelyeket felhasználva előállítjuk az ezeknek megfelelő naptármátrixot. Ennek a sorainak száma # határozza meg, hogy a megjelenítéskor a naptár hány soros legyen. év, hó = int(évszám.get()), hónapnevek.index(hónapnév.get()) naptár_mátrix:list[list] = calendar.monthcalendar(év, hó) naptár_sorok_száma = len(naptár_mátrix) # A naptártáblázat számértékcelláiban kiírandó számokat (a hónap hanyadik napja) tároló kontrollváltozók értékét # aktualizáljuk a naptármátrix értékei alapján. Ha az érték 0, akkor a kontrollváltzó értéke üres string lesz. címke_vars.update({(si, oi): tk.StringVar(root, str(nap) if (nap := naptár_mátrix[si - 1][oi]) else ' ') for si, oi in product(range(1, naptár_sorok_száma + 1), range(7))}) # A táblázat feltöltése előtt a naptár keret elem addigi tartalmát (gyerek elemek) töröljük. for grafikus_elem in naptár.winfo_children(): grafikus_elem.destroy() # Létrehozzuk a táblázatot a hét napjainak megfelelően hét oszloppal és a fentebb meghatározott számú sorral. # A naptár keretben rácsosan helyezünk el négyzet alakú kereteket, amelyeken egy címke elemen jelenik meg az érték. for si, oi in product(range(naptár_sorok_száma + 1), range(7)): cella_keret = tk.Frame(naptár, bg='white', width=45, height=45) cella_keret.grid(row=si, column=oi, pady=(0, 0 if si else 2)) cella_keret.propagate(False) # Címkék létrehozása és lehelyezése a táblázat megfelelő pozíciójú cellájának keretébe. lbl = tk.Label(cella_keret, textvariable=címke_vars.get((si, oi)), font=('Consolas', 14, 'bold'), width=3, justify=tk.CENTER, bg='light blue' if si == 0 else 'white', relief=tk.GROOVE if si == 0 else tk.SUNKEN) lbl.pack(fill=tk.BOTH, expand=True) # Segédfüggvény egy lista elemeinek n-szeri forgatásához. def forgat(lista: list, n): return lista[n:] + lista[:n] # -------- A grafikus felület kialakítása ---------- root = tk.Tk() # A főablak (gyökérelem) létrehozása. root.title('NAPTÁR') # A főablak címfelirata. w_width, w_height = 500, 500 # A főablak szélessége és magassága. root.geometry(f'{w_width}x{w_height}') # A táblázat első sorának celláiban levő címkék kontrollváltozóinak létrehozása a rövidített napnevekkel. címkék_kontrollváltozói = {(0, oi): tk.StringVar(root, nap_nevek_röviden[oi]) for oi in range(7)} # Az évszámok és hónapok léptetődobozainak közös jellemzőinek meghatározása. # Minden léptetéskor a command argumentumában szereplő függvény hívódik meg a léptetődobozok és címkék # kontrollváltozóival, bennük az éppen aktuális értékekkel. spb_közös = dict(state='readonly', bg='light green', readonlybackground='azure', fg='blue', bd=3, relief=tk.GROOVE, font=('Courier', '14', 'bold'), justify=tk.CENTER, width=10, wrap=True, buttonbackground='gold', buttonuprelief=tk.GROOVE, buttondownrelief=tk.SUNKEN, command=lambda: naptárfrissítés(naptár_keret, év_var, hónap_var, címkék_kontrollváltozói)) # A naptár induláskor az aznapi dátum szerint jelenik meg. Ehhez előállítjuk az aktuálisan érvényes dátumot. mai_dátum = datetime.date.today() # Létrehozzuk az évszámok léptetődobozát és a hozzátartozó kontrollváltozót, amelyek kezdőértéke az aznapi # dátum évszáma. év_var = tk.StringVar(root, mai_dátum.year) spb_év = tk.Spinbox(root, **spb_közös, textvariable=év_var, from_=2000, to=2100, increment=1) # Létrehozzuk a hónapok léptetődobozát és a hozzátartozó kontrollváltozót. Ahhoz, hogy kezdéskor az aznapi dátum # hónapneve jelenjen meg, és ettől kezdve lehessen léptetni, a hónapnevek listájának elemeit e hónaphoz kell # "forgatni", és a forgatott listával konfigurálni a léptetődobozt. hónap_var = tk.StringVar() spb_hónap = tk.Spinbox(root, **spb_közös, textvariable=hónap_var, values=forgat(hónapnevek[1:], mai_dátum.month - 1)) # Létrehozzuk a naptárat képviselő keret típusú grafikus elemet. naptár_keret = tk.Frame(root, bg='grey') # A naptár mint keret tartalmát feltöltjük az aktuális év és hónap napjainak táblázatával. naptárfrissítés(naptár_keret, év_var, hónap_var, címkék_kontrollváltozói) # Meghatározzuk a naptár mint keret elem bal és felső szegélyének a főablak oldalaitól vett távolságát, hogy a keret # a főablak középeben jelenjen meg. bal_margó = (w_width - 7 * naptár_keret.winfo_children()[0].cget('width')) / 2 felső_margó = (w_height - 8 * naptár_keret.winfo_children()[0].cget('height')) / 2 # Lehelyezzük a léptetődobozokat egymás mellé, majd a keretet ezek alá rácsos elrendezésben. spb_év.grid(row=0, column=0, sticky='ew', padx=(bal_margó, 5), pady=(felső_margó, 5)) spb_hónap.grid(row=0, column=1, sticky='ew', pady=(felső_margó, 5)) naptár_keret.grid(row=1, column=0, columnspan=2, sticky='ew', padx=(bal_margó, 0)) root.mainloop() |
A futtatás után megjelenő képet, valamint az évszám és hónap változtatása utáni állapotot mutatja a következő kép.

A programban az egyik kulcsszereplő a calendar modul, ezen belül is a monthcalendar() függvény, amely az argumentumként megadott év és hónap alapján egy lista elemeket tartalmazó listát, vagyis lényegében egy mátrixot ad vissza, amely épp a megjeleníteni kívánt számokat tartalmazza.
A calendar modult érdemes használni a hónapok neveinek és a napnevek rövidítéseinek meghatározására, amiket rendre a month_name és day_abbr objektumokból lehet kinyeni. Nem azért érdemes ezeket alkalmazni, mert gépelési munkát spórolunk meg, hanem azért, mert így nem csak a magyar, hanem bármilyen más nyelvterület hónapneveit és napnévrövidítéseit megkaphatjuk. Ehhez csak annyit kell tenni, hogy a locale modul setlocale() függvényével meg kell változtatni a kívánt területi beállítást.
Az alkalmazás indítása után az éppen aktuális év és hónap szerinti táblázat jelenik meg. Ehhez az aktuális aznapi dátumra van szükség, amelyet a datetime modul date.today() függvényével kaphatunk meg.
Az évszámokat és a hónapok neveit egy-egy léptetődobozzal (Spinbox) tudjuk változtatni. Az, hogy milyen értékeken haladjon végig két módon adhatjuk meg. Számok esetén specifikálható a kezdő- és végérték, valamint a növekmény. Ezt a megadási módot alkalmaztuk az évszámoknál. A hónapneveknél, mivel azok karakterláncok, ez nem járható őt. Ebben az esetben a másik megoldás lehetséges csak, ami az, hogy a léptetődoboz values paraméteréhez hozzárendeljük a hónapnevek sorozatát tartalmazó konténert. Ekkor a léptetődoboz induláskor mindig a sorozat első elemét veszi. Jelen esetben a month_name által szolgáltatott sorozat januárától decemberig tartalmazza a neveket. Ezért ahhoz, hogy az éppen aktuális hónap jelenjen meg, el kell érni, hogy a values argumentuma egy olyan sorozat legyen, amelynek első eleme az aktuális hónap, és a többi ezt kövesse. Ezt úgy tudjuk elérni, hogy a month_name által szolgáltatott sorozatból egy listát készítünk és ennek elemeit körkörösen „forgatjuk” addig, amíg az éppen aktuális hónap hónap kerül az első helyre. Ehhez a művelethez kell a forgat() nevű segédfüggvény.
A setlocal() függvény megfelelő paraméterezéséről a tudnivalókat a Python tudásépítés lépésről lépésre című e-könyv „Nemzetközi vizeken – helyben szokásos adatformátumok kezelése” alfejezete tartalmazza.
A calendar modullal a „Grafikus felhasználói felület készítése” fejezetben a léptetődoboz elem ismertetése kapcsán egy más jellegű példa keretében találkozhatunk, ahol további két hasznos függvényét alkalmazzuk.
A dátum- és időfüggvényekkel a „Mikor, hol hány óra? dátum, napi idő és időzónák” alfejezetben ismerkedhetünk meg az időszámítás elvi alapjainak tárgyalása után.
Ehhez az alkalmazáshoz néhány továbbfejlesztési ötlet, amellyel gyakorolni lehet a locale, calendar és datetime modul használatát, valamint a GUI készítést:
- Nyelvterületválasztás. A kiválasztás után a hónapnevek és hét napjainak nevei az adott nyelven jelennek meg.
- A szombathoz és vasárnaphoz tartozó oszlopok celláinak színezése térjen el a hétköznapok cellaszínétől.
- Az aktuális mai dátumnak megfelelő cella legyen más megjelenésű (szín, keretezés, betűstílus stb).
- Legyen megadható egy dátum, amelynek megfelelő táblázatoldalra ugrik a program.