Az előző bejegyzésben odáig jutottunk a közlekedési tábla rajzoló programunk fejlesztésében, hogy a ProhibitorySign osztály példánya – amely egyben egy konkrét KRESZ tábla is – reagál az egérgörgővel vagy bizonyos billentyűkombinációkkal kiváltott eseményekre, amelyek hatására a kirajzolt tábla mérete változtatható.
Most innen lépünk tovább, és egy osztályt fogunk készíteni, amelyek példányai olyan tiltó táblák, amelyek közepén valamilyen szövegesen megjeleníthető információ helyezkedik el. Ilyenek tipikusan a sebességkorlátozó táblák. Ezért az új osztály neve SpeedLimitSign lesz.
Ezt a SpeedLimitSign osztályt a ProhibitorySign osztály alosztályaként definiáljuk. Ezzel azt érjük el, hogy a tábla körformája a piros szegéllyel rendelkezésre áll, vagyis ennek kódolásával nem kell foglalkozni. Összesen annyit kell csak megoldani, hogy a kívánt szöveg megjelenjen a tábla közepén.
Ezen felül a ProhibitorySign osztályból történő öröklés azt is biztosítja, hogy az új osztály példányai is reagálnak majd a fent említett eseményekre, és így a tábla méretét ezekkel tudjuk változtatni.
Ez utóbbi állítás igaz ugyan, de az öröklés csak azt biztosítja, hogy a körtábla (pontosabban a külső és belső körök), valamint a vászon mérete változzon az eseményekre. Az új táblák közepén megjelenő szöveg méretét ezekkel egyelőre nem tudjuk változtatni. Ez természetes is, hiszen a ProhibitorySign mint szülőosztály nem tartalmazott semmilyen információt és kódot szöveges rajzelem, vagy más grafikus objektum méretének változtatására.
A feladat tehát, hogy
1) elhelyezzük a kívánt szöveget (számot) a SpeedLimitSign példányok közepén,
2) biztosítsuk azt, hogy a szöveg mérete a tábla átméretezésekor arányosan változzon, persze megőrizve a középre helyezett pozícióját.
Az első feladat elvben nem túl nehéz, mert csak annyit kell tenni, hogy a SpeedLimitSign __init__ metódusában argumentumként a példányosításkor bekérjük a kiírandó sebességlimit számot, majd létrehozunk egy szöveges rajzelemet a vászonra (amit ugye most a self reprezentál) meghívott create_text() metódussal. Ennek első argumentumaiként a szöveg középpontjának koordinátáit kell megadni, ami jelen esetben a körök közös középpontja lesz, ami az öröklés révén rendelkezésre áll. A create_text() metódus text parméteréhez rendeljük a bekért számot szövegként, a font paraméterhez pedig egy számunkra szimpatikus betűtípust rendelünk. Ezt a tkinter.font csomag beimportálása után a Font osztállyal határozzuk meg. Ebben a betűtípuscsaládot (pl. Arial, Calibri stb) karakterláncként a family paraméterrel kell megadni. Ha akarjuk, megadhatjuk a weight paraméterrel, hogy a betű félkövér legyen. Jelen esetben élünk ezzel a lehetőséggel. Be lehetne állítani, hogy dőlt vagy áthúzott legyen a betű, de a feladatunk szempontjából ezek most nem relevánsak.
Izgalmas kérdés viszont a betűméret megadása, amit a Font konstruktorának size paraméteréhez kell rendelni egész számként.
Ha előre ismert méretű lenne a táblánk, és az nem is változna, akkor könnyű dolgunk lenne, mert a belső kör átmérőjét figyelembe véve meg tudnánk választani azt a megfelelő betűméretet, amellyel a szöveg nem lóg ki a körből, de nem is túl kicsi. De most nem ez a helyzet, mert nem tudjuk előre a kezdőméretet, és ráadásul a tábla méretét futási időben is bármikor változtathatjuk. Ehhez kell a szöveg méretének mindig igazodnia.
A nehézség ott van, hogy – ellentétben pl. a körökkel – a szöveg rajzelem méretét (beleértve a betű nagyságát) nem lehet szélesség és magasság értékek megadásával változtatni. A szöveg méretét valójában az alkalmazott betűtípus aktuális mérete szabja meg. Ezért nem tudunk más tenni, mint kezdetnek a legkisebb betűmérettel indulunk, ezzel hozzuk létre a szöveg rajzelemet, majd egy megfelelő saját készítésű, _fit_text_size() nevű metódussal addig növeljük a betűméretet, amíg az megfelelő nem lesz az éppen aktuális táblamérethez.
Az eddig elmondottak kódban a SpeedLimitSign __init__ metódusában követhetők alább:
|
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 |
class SpeedLimitSign(ProhibitorySign): def __init__(self, master, speed_limit, size: int = None): super().__init__(master, size=size) self.speed_limit = speed_limit self.name = 'Sebességkorlátozás' # A tábla közepén megjelenítendő szöveg betűtípusának definiálása. self.font_type = ft.Font(family='Noto Sans Cond', size=0, weight="bold") self.txt = self.create_text(*self._ck, text=str(self.speed_limit), font=self.font_type) # A szöveget az aktuális táblamérethez igazítjuk. self._fit_text_size() def _fit_text_size(self): # Belső kör befoglaló négyzetének koordinátái. k2x1, k2y1, k2x2, k2y2 = self.bbox(self._circle2) # Csökkentett meretű négyzet koordináták előállítása. d = 0.08 * (k2x2 - k2x1) k2x1, k2y1, k2x2, k2y2 = k2x1 + d, k2y1 + d, k2x2 - d, k2y2 - d font_size = 0 self.font_type.config(size=font_size) self.itemconfig(self.txt, font=self.font_type) while True: # Előállítjuk a szöveg rajzelem befoglaló négyzetének koordinátáit. x1, y1, x2, y2 = self.bbox(self.txt) # Ha ez túllóg a belső kör redukált befoglaló négyzetén, akkor elértük a megfelelő méretet, ezért kilépünk. if x1 < k2x1 or y1 < k2y1: break # Ha a szöveg mérete nem lép túl a belső kör redukált befoglaló négyzetén, akkor eggyel növeljük # a betűméretet, és ezzel újrakonfiguráljuk a betűtípust, és azzal pedig a szöveg rajzelemet. font_size += 1 self.font_type.config(size=font_size) self.itemconfig(self.txt, font=self.font_type) def set_radius(self, r): # A szülőosztály körei méretének változtatása. super().set_radius(r) # A szöveget az új középpontba helyezzük self.coords(self.txt, *self._ck) # A szöveget az aktuális táblamérethez igazítjuk. self._fit_text_size() |
És ezzel át is kerültünk a fenti 2) feladathoz. Vagyis ahhoz, hogy hogyan valósítjuk meg hogy a szöveg, illetve annak betűmérete megfelelő legyen.
Elsőnek meghatározzuk, hogy mi az a maximális méretű terület, amelyet a szöveg rajzelem által foglalt terület nem léphet túl. Ehhez előállítjuk a belső kör befoglaló négyzetének koordinátáit. Mivel ez a belső kört kívülről határolja, ez még túl nagy lenne megengedhető területnek. Ezért ebből kiindulva egy olyan négyzet koordinátáit állítjuk elő, amelynek területe nem, vagy nem nagyon nyúlik túl a belső kör területén. Ez lesz tehát az a négyzet, amelyen a szöveg nem lóghat túl.
Ezt követően a szöveg rajzelemet a legkisebb betűméretű betűtípussal konfiguráljuk. Ez lesz a kiindulás. Utána egy ciklusban addig növeljük a betűméretet és végezzük el a betűtípus és szöveges rajzelem újrakonfigurálását, amíg annak mérete nagyobb nem lesz a fentebb meghatározott redukált négyzet méreténél. Amikor ez bekövetkezik, akkor leállunk a betűméretnöveléssel.
Ezen elvek megvalósítását látjuk a _fit_text_size() metódus törzsében.
Ezzel még csak a példányosítás utáni kezdeti szövegméret beállítását végeztük el. De, arról is kell gondoskodni, hogy méretváltoztatást kezdeményező esemény hatására megváltozó táblamérethez is igazodjon a szöveg rajzelem mérete.
Az átméretezést végző eseménykezelők (lásd előző bejegyzésben) a körök sugarát változtatták a set_radius() metódus segítségével. Ezt kell tehát felülírnunk az új SpeedLimitSign osztályban úgy, hogy elsőként meghívjuk a szülőosztály azonos nevű metódusát, amivel a körök mérete fog megváltozni. Miután ez megtörtént, rendelkezésre állnak az új körök, azok méretei, valamint az új középpont koordinátái. Ez utóbbit felhasználva a vászon példányra meghívott coords() metódussal újra pozícionáljuk a szöveg rajzelemet. Ezt követően a fentebb ismertetett _fit_text_size() metódussal az új helyzethez igazítjuk a szöveg rajzelem méretét. Ezeket a lépéseket láthatjuk a set_radius() definíciójában.
A tesztfuttáshoz a teljes kód így néz ki:
|
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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
import tkinter as tk from collections import namedtuple import tkinter.font as ft # A koordinátákkal való munka megkönnyítésére egy Point típust hozunk létre. Point = namedtuple('Point', 'x y') class ProhibitorySign(tk.Canvas): def __init__(self, master, size: int = None): super().__init__(master, bg='gray90') self.name = 'Mindkét irányból behajtani tilos' self.addtag_all('átméretezhető') self.addtag_all('áthelyezhető') # Ha nem adjuk meg létrehozáskor a vászonpéldány oldalméretét, akkor a szülőelem méretéhez igazítjuk azt. if size is None: # A helyes méretek lekérdezéséhez előbb frissíteni kell az ablakot. master.update() m_w, m_h = master.winfo_width(), master.winfo_height() size = min(int(m_h), int(m_w)) self.config(height=size, width=size) # A körök közös középpontja, amelynek koordinátái a vászon aktuális oldalméretének felével egyezik meg. cc = Point(size / 2, size / 2) # Külső kör sugara, amely a vászon oldalméretének fele. r1 = size / 2 # A számítási pontosság korlátai miatt a kör széle nem mindig látszana, ezért egy egész kicsit csökkentjük a sugarat. r1 -= 2 # Beállítjuk a belső és a külső kör sugarának arányát, ami meghatározza, hogy milyen széles legyen a tiltó tábla piros szegélye. r2 = r1 * 0.8 # A későbbi használathoz a középpontot és sugarakat attribútumként tároljuk. self._ck, self._r1, self._r2 = cc, r1, r2 # A külső kör befoglaló négyzetének pontjai a középponttal és sugárral meghatározva. k1p1, k1p2 = Point(cc.x - r1, cc.y - r1), Point(cc.x + r1, cc.y + r1) # A külső kör létrehozása a befoglaló négyzet pontjait használva. # A külső kört piros színnel töltjük ki. A kisebb belső kör pedig fehér lesz. Így alakul ki majd a piros szegély. self._circle1 = self.create_oval(*k1p1, *k1p2, fill='red') # A belső kör befoglaló négyzetének pontjai a középponttal és sugárral meghatározva. k2p1, k2p2 = Point(cc.x - r2, cc.y - r2), Point(cc.x + r2, cc.y + r2) # A belső kör létrehozása a befoglaló négyzet pontjait használva. self._circle2 = self.create_oval(*k2p1, *k2p2, fill='white') self.bind_events_and_event_handlers() def bind_events_and_event_handlers(self): """Események és eseménykezelők összerendelése""" self.bind('<Control MouseWheel>', self.resizing_mousewheel_handler) self.bind('<Shift MouseWheel>', lambda e: self.resizing_mousewheel_handler(e, 0.1)) self.bind('<Button 1>', lambda e: self.focus_set()) self.bind('<Control plus>', lambda e: self.resizing_event_handler(e, 0.1)) self.bind('<Control minus>', lambda e: self.resizing_event_handler(e, 0.1, False)) def resizing_event_handler(self, e: tk.Event, g=0.01, increase=True): if increase: self.set_radius(self.get_radius() * (1 + g)) else: self.set_radius(self.get_radius() * (1 - g)) def resizing_mousewheel_handler(self, e: tk.Event, g=0.01): self.set_radius(self.get_radius() * (1 - g) if getattr(e, 'delta') < 0 else self.get_radius() * (1 + g)) def get_radius(self): return self._r1 def set_radius(self, r): scalefactor_x = scalefactor_y = r / self._r1 old_ck = self._ck self.config(width=int(self.cget('width')) * scalefactor_x, height=int(self.cget('height')) * scalefactor_y) self._ck = Point(int(self.cget('width')) / 2, int(self.cget('height')) / 2) new_ck = self._ck self.move(self._circle1, new_ck.x - old_ck.x, new_ck.y - old_ck.y) self.move(self._circle2, new_ck.x - old_ck.x, new_ck.y - old_ck.y) self.scale(self._circle1, *self._ck, scalefactor_x, scalefactor_y) self.scale(self._circle2, *self._ck, scalefactor_x, scalefactor_y) class SpeedLimitSign(ProhibitorySign): def __init__(self, master, speed_limit, size: int = None): super().__init__(master, size=size) self.speed_limit = speed_limit self.name = 'Sebességkorlátozás' # A tábla közepén megjelenítendő szöveg betűtípusának definiálása. self.font_type = ft.Font(family='Noto Sans Cond', size=0, weight="bold") self.txt = self.create_text(*self._ck, text=str(self.speed_limit), font=self.font_type) # A szöveget az aktuális táblamérethez igazítjuk. self._fit_text_size() def _fit_text_size(self): # Belső kör befoglaló négyzetének koordinátái. k2x1, k2y1, k2x2, k2y2 = self.bbox(self._circle2) # Csökkentett meretű négyzet koordináták előállítása. d = 0.08 * (k2x2 - k2x1) k2x1, k2y1, k2x2, k2y2 = k2x1 + d, k2y1 + d, k2x2 - d, k2y2 - d font_size = 0 self.font_type.config(size=font_size) self.itemconfig(self.txt, font=self.font_type) while True: # Előállítjuk a szöveg rajzelem befoglaló négyzetének koordinátáit. x1, y1, x2, y2 = self.bbox(self.txt) # Ha ez túllóg a belső kör redukált befoglaló négyzetén, akkor elértük a megfelelő méretet, ezért kilépünk. if x1 < k2x1 or y1 < k2y1: break # Ha a szöveg mérete nem lép túl a belső kör redukált befoglaló négyzetén, akkor eggyel növeljük # a betűméretet, és ezzel újrakonfiguráljuk a betűtípust, és azzal pedig a szöveg rajzelemet. font_size += 1 self.font_type.config(size=font_size) self.itemconfig(self.txt, font=self.font_type) def set_radius(self, r): # A szülőosztály körei méretének változtatása. super().set_radius(r) # A szöveget az új középpontba helyezzük self.coords(self.txt, *self._ck) # A szöveget az aktuális táblamérethez igazítjuk. self._fit_text_size() # TESZT root = tk.Tk() root.geometry('500x300') for sk in SpeedLimitSign(root, 50, 150), SpeedLimitSign(root, 90, 150), SpeedLimitSign(root, 130, 150): sk.pack(side=tk.LEFT) root.mainloop() |
A teszt során három sebességkorlátozó táblát hoztunk létre azonos kezdeti mérettel, majd egérgörgővel eltérő nagyságú táblákká alakítottuk azokat. Ezt az állapotot mutatja ez a képernyőkép:

A következő bejegyzésben olyan tiltó táblákat fogunk készíteni, amelyek közepén valamilyen képi tartalom (piktogram) van. Ezek képezik a KRESZ tiltó tábláinak többségét. Itt is elsősorban a méretezés jelent majd némi kihívást.
E bejegyzéshez kapcsolódóan 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 alábbi alfejezeteiben találunk részletes információkat: „A grafikus elemek fajtái, létrehozásuk és konfigurálásuk” – „Vászon” alcím és abban a szöveg rajzelem. Továbbá az „Osztály vigyázz! – típuslétrehozás osztályokkal”, és „Öröklődés” fejezetek.