Az előző két bejegyzésben (itt és itt) egy pont adott forgáspont körül, megadott szöggel való forgatását és tengelyes tükrözését megvalósító függvények előállításával foglalkoztunk.
Ha tudunk egy pontot forgatni vagy tükrözni, akkor lehetőség nyílik egy sokszöget is forgatni, vagy tengelyesen, illetve középpontosan tükrözni, hiszen ilyenkor nem kell mást tenni, mint a sokszög csúcspontjaira elvégezni az említett geometriai transzformációkat, mert a forgatott vagy tükrözött csúcspontok meghatározzák a transzformált sokszöget.
Ennek szemléltetésére készítünk most egy egyszerű GUI alkalmazást a szabványos könyvtár tkinter moduljának használatával. A grafikus felület induló képernyőképét mutatja ez a kép:

Itt a főablak két részre tagolódik. Felül helyezkednek el a forgatást és tükrözést indító nyomógombok, valamint egy beviteli mező, amelyben meg lehet adni a forgatási szöget fokban. Ezek alatt látható az a felület, amelyen a sokszög, a forgáspont és tükrözési tengelyvonal rajzolását végezhetjük. Ezt a vászon grafikus elemmel (canvas widget) valósítjuk meg.
Az alkalmazást egy App nevű osztály képviseli, amely maga a főablak is, ezért a tkinter Tk osztály utódja lesz. Az alábbi kód mutatja az App __init__ metódusá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 |
import tkinter as tk from collections import namedtuple from math import cos, sin, radians Point = namedtuple('Point', 'x y') class App(tk.Tk): def __init__(self): super().__init__() self.title('Forgatás és tükrözés') self.geometry('800x600') self.frm = tk.Frame(self) # A beviteli mezőt és a nyomógombokat tartalmazó keret. configs = dict(font=('Arial', 12, 'bold')) # A grafikus elemek közös konfigurációs jellemzőinek szótára. self.angle_var = tk.StringVar(self, '0') # A beviteli mező kontrollváltozója. self.angle_box = tk.Entry(self.frm, textvariable=self.angle_var, width=4, **configs) self.rotate_button = tk.Button(self.frm, text='Forgatás', **configs, command=self.display_rotated_polygon) self.reflect_button = tk.Button(self.frm, text='Tükrözés a tengelyre', **configs, command=self.display_reflected_polygon) self.central_inversion_button = tk.Button(self.frm, text='Tükrözés a pontra', **configs, command=self.display_centrally_inverted_polygon) self.cnv = tk.Canvas(self, bg='light yellow') self.polygon_vertices = [] # A sokszög pontjait tároló lista. self.polygon_created = False # Annak jelölője, hogy a sokszög elkészült, további pontok nem adhatók meg. self.center_of_rotation_and_inversion = Point(0, 0) # A forgáspont, ill. a középpontos tükrözés középpontja. self.line_start_point = None # A tükrözési tengelyvonal kezdőpontja. self.line_end_point = None # A tükrözési tengelyvonal végpontja. self.placing_widgets() # A grafikus elemek lehelyezése. self.event_bindings() # Az események és eseménykezelők összerendelése. |
Az __init__ elején az előbb említett grafikus elemeket hozzuk létre. Ezt követően az egyes geometriai elemeket, azaz a sokszöget, a forgáspontot és a tükrözési tengelyt meghatározó pontok számára inicializálunk attribútumokat. Ezután a grafikus elemeket lehelyezzük az erre szolgáló placing_widget() metódus hívással. A geometriai elemek meghatározásához és megjelenítéséhez eseményeket definiálunk és eseménykezelőket rendelünk az event_bindings() metódusban, amit az __init__ utolsó sorában meghívunk.
A placing_widget() és az event_bindings() metódusok definíciója látható alább. Ezek elég egyszerű felépítésűek és a kommentek is segítik a megértésüket.
|
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 |
import tkinter as tk from collections import namedtuple from math import cos, sin, radians Point = namedtuple('Point', 'x y') class App(tk.Tk): def __init__(self): super().__init__() self.title('Forgatás és tükrözés') self.geometry('800x600') self.frm = tk.Frame(self) # A beviteli mezőt és a nyomógombokat tartalmazó keret. configs = dict(font=('Arial', 12, 'bold')) # A grafikus elemek közös konfigurációs jellemzőinek szótára. self.angle_var = tk.StringVar(self, '0') # A beviteli mező kontrollváltozója. self.angle_box = tk.Entry(self.frm, textvariable=self.angle_var, width=4, **configs) self.rotate_button = tk.Button(self.frm, text='Forgatás', **configs, command=self.display_rotated_polygon) self.reflect_button = tk.Button(self.frm, text='Tükrözés a tengelyre', **configs, command=self.display_reflected_polygon) self.central_inversion_button = tk.Button(self.frm, text='Tükrözés a pontra', **configs, command=self.display_centrally_inverted_polygon) self.cnv = tk.Canvas(self, bg='light yellow') self.polygon_vertices = [] # A sokszög pontjait tároló lista. self.polygon_created = False # Annak jelölője, hogy a sokszög elkészült, további pontok nem adhatók meg. self.center_of_rotation_and_inversion = Point(0, 0) # A forgáspont, ill. a középpontos tükrözés középpontja. self.line_start_point = None # A tükrözési tengelyvonal kezdőpontja. self.line_end_point = None # A tükrözési tengelyvonal végpontja. self.placing_widgets() # A grafikus elemek lehelyezése. self.event_bindings() # Az események és eseménykezelők összerendelése. def placing_widgets(self): """Az egyes grafikus elemek lehelyezése. A beviteli mező és a nyomógombok a főablak felső részen egy keretben vannak egymás mellé helyezve. A pontokat, sokszöget és tengelyvonalat megjelenítő vászon elem e keret alatt helyezkedik el, kitöltve a főablakban rendelkezésre álló területet. """ self.frm.pack() self.angle_box.pack(side=tk.LEFT) self.rotate_button.pack(side=tk.LEFT) self.central_inversion_button.pack(side=tk.LEFT) self.reflect_button.pack(side=tk.LEFT) self.cnv.pack(fill=tk.BOTH, expand=True) def event_bindings(self): """Események és eseménykezelők összerendelése.""" # A tükrözési tengelyvonal rajzolása az egymást követő eseménnyel megadott két pont alapján. self.cnv.bind("<Shift Button-1>", self.display_line) # A sokszög egy csúcspontjának meghatározása és megjelenítése. self.cnv.bind("<Control Button-1>", self.display_polygon_vertex) # A sokszög megjelenítése a meghatározott csúcspontok alapján. self.bind('<Double Button 1>', self.display_polygon) # Forgáspont vagy a középpontos tükrözés középpontjának meghatározása és megjelenítése. self.cnv.bind("<Alt Button-1>", self.display_center_of_rotation_and_inversion) # Adott pont forgatott és tengelyesen tükrözött megfelelőjét visszaadó metódusok. @staticmethod def rotate_point(point: Point, cr: Point, alpha): """A megadott point pont cr forgáspont körül alpha radian szöggel elforgatott megfelelőjével tér vissza.""" def reflect_point_about_line(self, point: Point, p1: Point, p2: Point): """A megadott point pontnak a p1 és p2 pontok által meghatározott tengelyre vett tükörkép pontjával tér vissza.""" # A sokszög csúcspontjainak forgatott és tengelyesen tükrözött megfelelőit visszaadó metódusok. def rotate_polygon(self, alpha: "fok") -> list[Point]: """A sokszög csúcspontjainak forgatása a meghatározott forgáspont körül egy megadható, fokban mért szöggel.""" return [self.rotate_point(point, self.center_of_rotation_and_inversion, radians(alpha)) for point in self.polygon_vertices] def reflect_polygon(self) -> list[Point]: """A sokszög csúcspontjainak tükrözése a két pontjával meghatározott tengely körül.""" if self.line_start_point and self.line_end_point: return [self.reflect_point_about_line(point, self.line_start_point, self.line_end_point) for point in self.polygon_vertices] return [] |
A használathoz összefoglaljuk az eseményeket:
- a Ctrl+bal egérgomb lenyomásával lehet a sokszög pontjait kijelölni a felületen.
- a bal egérgomb dupla kattintásával lehet jelezni, hogy több pontot már nem akarunk felvenni, és jelenítse meg a sokszöget.
- A forgáspontot az Alt+bal egérgombbal határozhatjuk meg.
- A tükrözési tengelyszakasz kezdőpontját a Shift+bal egér kattintással adhatjuk meg. Egy ezt követő szintén Shift+bal egér kattintással adjuk meg a szakasz végpontját, és ekkor a vonal meg is jelenik.
A fenti kódban szereplő további metódusok végzik a matematikai számításokat a forgatott, illetve a tükrözött pontok meghatározásához. Az adott pontot forgató és tengelyesen tükröző metódusok az előző két bejegyzésben tárgyalt bármelyik algoritmusát használhatják. Ezért nem adtuk itt meg a metódustörzseket, mert mindenki a számára valamilyen szempontból preferáltat adhatja meg.
Bővítsük az App osztályt egy pontot kirajzoló, valamint a csúcspontok ismeretében egy új sokszöget kirajzoló két metódussal, amelyek neve draw_point() és draw_new_polygon(). Ezek megértése a tkinter Canvas metódusainak ismeretében nem okozhat gondot.
|
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 |
import tkinter as tk from collections import namedtuple from math import cos, sin, radians Point = namedtuple('Point', 'x y') class App(tk.Tk): def __init__(self): super().__init__() self.title('Forgatás és tükrözés') self.geometry('800x600') self.frm = tk.Frame(self) # A beviteli mezőt és a nyomógombokat tartalmazó keret. configs = dict(font=('Arial', 12, 'bold')) # A grafikus elemek közös konfigurációs jellemzőinek szótára. self.angle_var = tk.StringVar(self, '0') # A beviteli mező kontrollváltozója. self.angle_box = tk.Entry(self.frm, textvariable=self.angle_var, width=4, **configs) self.rotate_button = tk.Button(self.frm, text='Forgatás', **configs, command=self.display_rotated_polygon) self.reflect_button = tk.Button(self.frm, text='Tükrözés a tengelyre', **configs, command=self.display_reflected_polygon) self.central_inversion_button = tk.Button(self.frm, text='Tükrözés a pontra', **configs, command=self.display_centrally_inverted_polygon) self.cnv = tk.Canvas(self, bg='light yellow') self.polygon_vertices = [] # A sokszög pontjait tároló lista. self.polygon_created = False # Annak jelölője, hogy a sokszög elkészült, további pontok nem adhatók meg. self.center_of_rotation_and_inversion = Point(0, 0) # A forgáspont, ill. a középpontos tükrözés középpontja. self.line_start_point = None # A tükrözési tengelyvonal kezdőpontja. self.line_end_point = None # A tükrözési tengelyvonal végpontja. self.placing_widgets() # A grafikus elemek lehelyezése. self.event_bindings() # Az események és eseménykezelők összerendelése. def placing_widgets(self): """Az egyes grafikus elemek lehelyezése. A beviteli mező és a nyomógombok a főablak felső részen egy keretben vannak egymás mellé helyezve. A pontokat, sokszöget és tengelyvonalat megjelenítő vászon elem e keret alatt helyezkedik el, kitöltve a főablakban rendelkezésre álló területet. """ self.frm.pack() self.angle_box.pack(side=tk.LEFT) self.rotate_button.pack(side=tk.LEFT) self.central_inversion_button.pack(side=tk.LEFT) self.reflect_button.pack(side=tk.LEFT) self.cnv.pack(fill=tk.BOTH, expand=True) def event_bindings(self): """Események és eseménykezelők összerendelése.""" # A tükrözési tengelyvonal rajzolása az egymást követő eseménnyel megadott két pont alapján. self.cnv.bind("<Shift Button-1>", self.display_line) # A sokszög egy csúcspontjának meghatározása és megjelenítése. self.cnv.bind("<Control Button-1>", self.display_polygon_vertex) # A sokszög megjelenítése a meghatározott csúcspontok alapján. self.bind('<Double Button 1>', self.display_polygon) # Forgáspont vagy a középpontos tükrözés középpontjának meghatározása és megjelenítése. self.cnv.bind("<Alt Button-1>", self.display_center_of_rotation_and_inversion) # Adott pont forgatott és tengelyesen tükrözött megfelelőjét visszaadó metódusok. @staticmethod def rotate_point(point: Point, cr: Point, alpha): """A megadott point pont cr forgáspont körül alpha radian szöggel elforgatott megfelelőjével tér vissza.""" def reflect_point_about_line(self, point: Point, p1: Point, p2: Point): """A megadott point pontnak a p1 és p2 pontok által meghatározott tengelyre vett tükörkép pontjával tér vissza.""" # A sokszög csúcspontjainak forgatott és tengelyesen tükrözött megfelelőit visszaadó metódusok. def rotate_polygon(self, alpha: "fok") -> list[Point]: """A sokszög csúcspontjainak forgatása a meghatározott forgáspont körül egy megadható, fokban mért szöggel.""" return [self.rotate_point(point, self.center_of_rotation_and_inversion, radians(alpha)) for point in self.polygon_vertices] def reflect_polygon(self) -> list[Point]: """A sokszög csúcspontjainak tükrözése a két pontjával meghatározott tengely körül.""" if self.line_start_point and self.line_end_point: return [self.reflect_point_about_line(point, self.line_start_point, self.line_end_point) for point in self.polygon_vertices] return [] # Pont és sokszög kirajzolását végző metódusok. def draw_point(self, point: Point, color='blue', tag=''): r = 3 # A kör befoglaló négyzetének bal felső és jobb alsó pontjai. point_top_left, point_bottom_right = Point(point.x - r, point.y - r), Point(point.x + r, point.y + r) self.cnv.create_oval(point_top_left, point_bottom_right, fill=color, tag=tag) def draw_new_polygon(self, polygon_vertices, **polygon_configs): self.cnv.delete('polygon') self.cnv.create_polygon(polygon_vertices, fill='light green', outline='black', width=2, tag='polygon') self.cnv.itemconfig('polygon', **polygon_configs) self.polygon_vertices[:] = polygon_vertices # Eseménykezelők az egyes geometriai elemek (pontok, sokszög, tengelyvonal) megjelenítésére. def display_polygon_vertex(self, event):... def display_center_of_rotation_and_inversion(self, event):... def display_line(self, event):... def display_polygon(self, event):... # A forgatott, tengelyesen vagy középpontosan tükrözött sokszög megjelenítését végző metódusok. def display_rotated_polygon(self):... def display_centrally_inverted_polygon(self):... def display_reflected_polygon(self):... # Alkalmazás indítása. def run(self): self.mainloop() if __name__ == '__main__': app = App() app.run() |
Ez a sokszögrajzó nem csak a kezdeti sokszög, hanem a transzformálás utáni új sokszög kirajzolására is használatos lesz, ezért látható a draw_new_polygon() metódus elején, hogy töröljük a meglévő sokszöget, valamint a végén, hogy az új sokszög csúcspontjai lesznek az aktuális sokszög csúcspontok.
A fenti osztálydefiníció további metódusai egyrészt eseménykezelők az egyes geometriai elemek (pontok, sokszög, tengelyvonal) megjelenítésére, másrészt a forgatott, tengelyesen vagy középpontosan tükrözött sokszög megjelenítésére szolgálnak. Ezeknek egyelőre csak a fejléce van feltüntetve, mert ezekkel fogjuk folytatni a következő bejegyzésben. De az eddig közölt kódok is már lefutnak és fent bemutatott kiinduló ablak megjelenését eredményezik.
A GUI alkalmazásokkal 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” fejezete foglalkozik meglehetősen részletesen, számos példával segítve az ismeretek elsajátítását.