Hogyan készítsünk KRESZ táblákat rajzoló programot? – 2.rész

Folytatjuk az előző bejegyzésben elkezdett, közlekedési tiltó táblákat rajzoló program továbbfejlesztését azzal, hogy egér és billentyűzet eseményeket, és ezekhez rendelt eseménykezelőket határozunk meg, amelyek hatására a táblák méretét arányosan növelni vagy csökkenteni tudjuk.

Az eseménykezelő olyan egyparaméteres hívható objektum (pl. függvény vagy metódus), amely az esemény bekövetkeztekor lesz meghívva és argumentumként egy eseményobjektumot kap, amely az adott eseményről tartalmaz információkat, amit az eseménykezelő szükség esetén fel tud dolgozni.

A programunkban, a ProhibitorySign osztályban egy resizing_event_handler() nevű metódust definiálunk, amely első paramétere fogadja az eseményobjektumot. Ezen kívül van még két alapértelmezett értékkel rendelkező paramétere. Ebből az egyikkel egy esemény hatására történő méretváltozás arányát lehet beállítani. Más szóval ezzel azt határozhatjuk meg, hogy milyen finom léptékekben történjen a tiltó tábla méretváltozása. Az eseménykezelőnknek van még egy harmadik, True vagy False értéket fogadó paramétere is, amellyel azt határozhatjuk meg, hogy a méretváltozás növelést vagy csökkentést jelentsen. Az alapértelmezett True érték növelést jelent. Tehát ha True az érték, és az arány mértéke pl. 0.01, az azt fogja jelenti, hogy az eredeti mérethez képest 1.01-szeres lesz az új méret. Ha a méretváltozás irányát False értékre állítjuk, akkor – változatlan arány mérték mellett – az új méret az eredeti 0.99-szerese lesz.

Ezen felül definiálunk még egy eseménykezelőt speciálisan az egérgörgő forgatás eseményhez resizing_mousewheel_handler() néven, ami az eseményt fogadó paraméter mellett csak a méretváltozás arányát meghatározó paramétert tartalmazza. Erre azért célszerű külön metódust definiálni, mert az egérgörgetésnél a görgetés iránya fogja meghatározni, hogy méretnövelést vagy csökkentést, tehát nem a metódusfejben kell megadni híváskor.

Maga az átméretezés elvi szinten úgy valósul meg, hogy a külső kör sugarát fogjuk változtatni. Ehhez kell két újabb metódust definiálni: a get_radius() fogja kiadni a tábla (azaz a külső kör) aktuális sugarát, a set_radius() metódussal pedig meg tudjuk változtatni a tábla sugarát, aminek hatására a tábla az új mérettel kell, hogy megjelenjen a vásznon.

A konkrét feladattól és használati céltól függő tervezési kérdés, hogy a méretváltoztatás csak a körtáblára vonatkozzon, vagy a táblával együtt a vászon mérete is arányosan változzon, vagyis a tábla széle mindig érintse a vászon oldalát. Mi most ez utóbbi mellett döntünk.

A fentiek figyelembevételével az egyes metódusok megvalósítási elve a következő.

A get_radius() a legegyszerűbb, mert csupán a külső kör privát attribútumként eltárolt sugarát kell visszaadnia. Ezt felhasználjuk a két fent említett eseménykezelőben úgy, hogy a kikért aktuális sugárértéket megszorozzuk a méretváltozás megadott iránya és mértéke szerinti tényezővel, majd az így kapott értékkel meghívjuk a set_radius() metódust. Az resizing_mousewheel_handler() esetén a méretváltozás irányát (növelés vagy csökkentés) az eseményobjektum delta attribútuma határozza meg. /Itt nem kell ellenőrizni, hogy van-e az eseményobjektumnak delta attribútuma, mert ezt a metódust csak az egérgörgetés, azaz a MouseWheel eseményhez rendeljük majd, és ekkor biztosan lesz ilyen attribútum/.

Ha a delta negatív, akkor lefelé történt görgetés, amihez a méretcsökkentést rendeltük, és fordítva, ha a delta pozitív, akkor felfelé lett az egérgörgő tekerve, aminek hatására a sugár méretét növeljük. Ezen elvek alapján a get_radius() és a két eseménykezelő alább látható implementációinak megértése vélhetően nem okozhat gondot.

A tényleges méretváltoztatást a set_radius() metódus végzi. Ebben elsőként meg kell határozni a méretváltozás arányát, amely nem más, mint e metódus argumentumaként kapott sugár és a külső kör eddigi sugarának aránya. Ezt skálafaktornak fogjuk nevezni. Mivel úgy döntöttünk, hogy a vászon méretét is változtatjuk, ezért ennek szélességét és magasságát a skálafaktorral arányosan módosítjuk a vászonpéldányra meghívott config() metódussal. Ez azonban az eddigi közös középpontot is eltolja, ezért a vászon méretezése előtt az aktuális közös középpontot eltároljuk, majd a vászon méretváltozása után az új középpontot meghatározzuk és rögzítjük.

Az új középpont ismeretében első lépésben a köröket áthelyezzük úgy, hogy középpontjuk egybeessen az új középponttal. Második lépésben pedig a koncentrikus körök méretét a skálafaktor arányában módosítjuk. A körök áthelyezését a vászonpéldányra meghívott move() metódussal, a méretváltoztatásukat pedig a scale() metódussal végezzük. Ezek első argumentuma két dolog lehet. Vagy a változással érintett rajzobjektum azonosítója, vagy egy címke (tag). Ha ez utóbbit adjuk meg, akkor minden olyan rajzelemre, amelyhez e címke van rendelve, a változás érvényes lesz. Több rajzelem esetén ez a kényelmes, kevesebb kóddal járó megoldás. Esetünkben két rajzelem, a két kör érintett, ezért címkéket adunk meg a move() és scale() metódusokban.

Ahhoz, hogy ez így működjön, a két körhöz a címkéket hozzá is kell rendelni. Ezt az __init__ metódusban tesszük meg, és itt is sem egyenként, hanem a kényelmesebb utat választva, egyszerre mindkét körre, amit a vászonpéldányra meghívott addtag_all() metódusokkal tudunk megtenni. Ezt a lépést és a set_radius() teljes implementációját az alábbi kódban követhetjük.

Egyetlen dolog maradt, mégpedig az események és eseménykezelők összerendelése, amit az bind_events_and_event_handlers() metódusban teszünk meg. Az egérgörgetéshez két eseménykezelőt rendelünk. Az egyik az alapértelmezett, finomabb léptékű méretváltoztatást teszi lehetővé, míg a másik egy nagyobb arányú, így gyorsabb változtatást. A két esemény megkülönböztetéséhez az elsőnél a Ctrl gomb lenyomva tartása mellett kell a görgőt forgatni, a másik esetben pedig a Shift gombot kell lenyomva tartani.

A méretváltoztatást azonban nem csak egérrel, hanem billentyűkombinációval is el akarjuk érni. Ezért a Ctrl és a + billentyű együttes lenyomása mint eseményhez az resizing_event_handler() metódust rendeljük úgy, hogy a méret növekedjen. A Ctrl és a – billentyű együttes lenyomása mint eseményhez pedig az resizing_event_handler() metódusban a logikai értéket False-ra állítjuk, ami méretcsökkenést vált ki. Ahhoz, hogy a billentyűzettel kiváltott események érvényesüljenek, a táblának fókuszba kell kerülni. Ezt vagy a Tab billenytűvel tudjuk elérni, vagy ha egéreseménnyel akarjuk, akkor ezt is definiálni kell. Mi most a bal egérgomb lenyomásához kötjük a fókuszbakerülést, amelyet a focus_set() meghívásával érünk el.

Az események és eseménykezelők egymáshoz rendelését a vászonpéldányra meghívott bind() metódussal tudjuk megtenni, amelynek első argumentuma az eseményt definiáló karakterlánc, a második pedig az eseménykezelő. Az bind_events_and_event_handlers() elmondottaknak megfelelő implementációját szintén láthatjuk a fenti kódban.

A tesztnél három egyforma méretű táblát hoztunk létre, és jelenítettünk meg a pack() metódussal balról jobbra történő lehelyezéssel. Utána a bal oldali méretét egérgörgetéssel növeltük, a jobb oldalit – a fókuszba hozás után, amelyet a keret jelez – billentyűkombinációval csökkentettük. A középső tábla a létrejöttekor kapott eredeti méreten maradt.

A következő bejegyzésben az eddigi munkánkra alapozva olyan tiltó táblákat fogunk készíteni, amelyek közepén valamilyen szövegesen megjeleníthető információ van (pl. sebességkorlátozó táblák).

Az események grafikus elemekhez történő rendelésének elvét, részleteit, szintaktikai előírásait és az implementálás bizonyos kérdéseivel kapcsolatos javaslatokat a Python tudásépítés lépésről lépésre című e-könyv „Grafikus felhasználói felület készítése” fejezetének „Események és programkód összerendelése” alfejezete tartalmazza.

É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.