Hogyan készítsünk egyszerű naptár alkalmazást?

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.

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.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.