Az előző bejegyzésben egy olyan, egyszerű grafikus alkalmazást kezdtünk el felépíteni, amely egy csúcspontjaival definiálható sokszöget egy megadható forgáspont körül, szintén megadható szöggel elforgat, vagy egy kezdő- és végponttal meghatározható vonalra vonatkozóan tengelyesen tükröz.
Az előző bejegyzésben az alkalmazás alapfelépítést kezdtük meg számos szükséges metódus definiálásával. Azzal hagytuk abba, hogy a geometriai elemek (pont, sokszög és szakasz) megjelenítése marad hátra még. Ezeket vesszük sorra most.
A megjelenítést, kirajzolást végző metódusokat két csoportba rendeztük. Az egyik a kiinduló geometriai elemek megjelenítését végzik az előző bejegyzésben szereplő egéresemények hatására. Ezek tehát eseménykezelő metódusok lesznek és ezért argumentumként eseményobjektumot fogadnak. Ezek kódját láthatjuk alább. A megértést a kommentek segítik.
|
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 |
class App(tk.Tk): # A korábbiakban definiált metódusok itt nincsenek feltüntetve. # Eseménykezelők az egyes geometriai elemek (pontok, sokszög, tengelyvonal) megjelenítésére. def display_polygon_vertex(self, event): """Ha még nem jeleztük, hogy befejeztük a sokszög készítését, akkor az egérmutató helyén megjelenít egy újabb csúcspontot. """ if not self.polygon_created: point = Point(event.x, event.y) self.polygon_vertices.append(point) # A pontot felvesszük a sokszög csúcspontjai közé. self.draw_point(point, tag='polygon_vertex') # A pont kirajzolás. def display_center_of_rotation_and_inversion(self, event): """Az egérmutató helyén megjelenít egy pontot, amely forgatás esetén forgáspont lesz, vagy középpontos tükrözés esetén a tükrözési középpont. Ha volt már korábban ilyen pont megjelenítve, akkor az törlődik. """ point = Point(event.x, event.y) self.center_of_rotation_and_inversion = point # A pontot a megfelelő attribútum értékeként eltároljuk. self.cnv.delete('center_point') # A korábbi pont törlése. self.draw_point(point, color='red', tag='center_point') # A pont kirajzolás. def display_line(self, event): """"A tengelyes tükrözés tengelyvonalának megjelenítése. Első meghívással a vonal kezdőpontja rögzül az egérmutató helyén, második meghívással a végpont, és egyúttal a vonal ki is rajzolódik. Új vonal rajzolása esetén, az előző törlődik. """ point = Point(event.x, event.y) if self.line_start_point is None: # Ha még nincs kezdőpont, akkor annak kirajzolása és eltárolása. self.draw_point(point, color='black', tag='start_point') self.line_start_point = point elif self.line_end_point is None: # Ha még nincs végpont, akkor annak kirajzolása és eltárolása. Utána a vonal kirajzolása a két pont között. self.draw_point(point, color='black', tag='end_point') # A végpont kirajzolás. self.line_end_point = point self.cnv.create_line(self.line_start_point, self.line_end_point, width=2, tag='line') else: # Ha már van kezdő- és végpont, akkor e pontok és a vonal törlődik és új vonal kirajzolás kezdődik. self.cnv.delete('start_point', 'end_point', 'line') self.line_start_point, self.line_end_point = point, None self.draw_point(point, color='black', tag='start_point') def display_polygon(self, event): """A csúcspontok alapján megjeleníti a sokszöget, ha van csúcspont meghatározva.""" if self.polygon_vertices: self.draw_new_polygon(self.polygon_vertices) self.cnv.delete('polygon_vertex') # Az eddig megjelenített csúcspontok törlődnek. self.polygon_created = True # Jelezzük, hogy a sokszög elkészült, így ezek után újat nem lehet meghatározni. |
A másik metóduscsoportba tartozók az egyes nyomógombok hatására végbemenő transzformációkat (tükrözések vagy forgatás) hajtják végre. Mivel nyomógombokhoz, azok parancsvégrehajtásához kapcsolódnak, azaz ismert az esemény, ezért ezek argumentum nélküli metódusok lesznek. Ezek definíciója í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 |
class App(tk.Tk): # A korábbiakban definiált metódusok itt nincsenek feltüntetve. # 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): """A beviteli mezőben megadott szög, az előzetesen meghatározott forgáspont, valamint a sokszög eltárolt csúcspontjai alapján elforgatja a sokszöget, majd az elforgatott sokszög csúcspontjai alapján kirajzolja az új, elforgatott sokszöget.""" rotated_vertices = self.rotate_polygon(float(self.angle_var.get())) if rotated_vertices: self.draw_new_polygon(rotated_vertices) def display_centrally_inverted_polygon(self): """A sokszög eltárolt csúcspontjait középpontosan tükrözi az előzetesen meghatározott tükrözési pontra, majd az tükrözött sokszög csúcspontjai alapján kirajzolja az új sokszöget.""" # A középpontos tükrözést 180 fokos elforgatással valósítjuk meg. rotated_vertices = self.rotate_polygon(180) if rotated_vertices: self.draw_new_polygon(rotated_vertices) def display_reflected_polygon(self): """A sokszög eltárolt csúcspontjait tengelyesen tükrözi az előzetesen meghatározott tengelyvonalra, majd a tükrözött sokszög csúcspontjai alapján kirajzolja az új sokszöget.""" reflected_vertices = self.reflect_polygon() if reflected_vertices: self.draw_new_polygon(reflected_vertices) |
Mindezek után az elkészített, teljes alkalmazás kódja a következő:
|
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
import tkinter as tk from collections import namedtuple from math import cos, sin, radians, atan2 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.""" p, cr, j = complex(*point), complex(*cr), complex(0, 1) pr = (p - cr) * (cos(alpha) + sin(alpha) * j) + cr return Point(pr.real, pr.imag) 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.""" # Azzal a vektorral toljuk el a pontot, amellyel a szakaszt kelle, hogy a kezdete az origóba kerüljön. ps = Point(point.x - p1.x, point.y - p1.y) # Az eltolt pontot tükrözzük az x tengelyre. psi = Point(ps.x, -ps.y) # Elforgatjuk 2alpha szöggel. alpha = atan2(p2.y - p1.y, p2.x - p1.x) psir = self.rotate_point(psi, Point(0, 0), 2 * alpha) # Visszatoljuk a pontot. p_reflected = Point(psir.x + p1.x, psir.y + p1.y) return p_reflected # 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): """Ha még nem jeleztük, hogy befejeztük a sokszög készítését, akkor az egérmutató helyén megjelenít egy újabb csúcspontot. """ if not self.polygon_created: point = Point(event.x, event.y) self.polygon_vertices.append(point) # A pontot felvesszük a sokszög csúcspontjai közé. self.draw_point(point, tag='polygon_vertex') # A pont kirajzolás. def display_center_of_rotation_and_inversion(self, event): """Az egérmutató helyén megjelenít egy pontot, amely forgatás esetén forgáspont lesz, vagy középpontos tükrözés esetén a tükrözési középpont. Ha volt már korábban ilyen pont megjelenítve, akkor az törlődik. """ point = Point(event.x, event.y) self.center_of_rotation_and_inversion = point # A pontot a megfelelő attribútum értékeként eltároljuk. self.cnv.delete('center_point') # A korábbi pont törlése. self.draw_point(point, color='red', tag='center_point') # A pont kirajzolás. def display_line(self, event): """"A tengelyes tükrözés tengelyvonalának megjelenítése. Első meghívással a vonal kezdőpontja rögzül az egérmutató helyén, második meghívással a végpont, és egyúttal a vonal ki is rajzolódik. Új vonal rajzolása esetén, az előző törlődik. """ point = Point(event.x, event.y) if self.line_start_point is None: # Ha még nincs kezdőpont, akkor annak kirajzolása és eltárolása. self.draw_point(point, color='black', tag='start_point') self.line_start_point = point elif self.line_end_point is None: # Ha még nincs végpont, akkor annak kirajzolása és eltárolása. Utána a vonal kirajzolása a két pont között. self.draw_point(point, color='black', tag='end_point') # A végpont kirajzolás. self.line_end_point = point self.cnv.create_line(self.line_start_point, self.line_end_point, width=2, tag='line') else: # Ha már van kezdő- és végpont, akkor e pontok és a vonal törlődik és új vonal kirajzolás kezdődik. self.cnv.delete('start_point', 'end_point', 'line') self.line_start_point, self.line_end_point = point, None self.draw_point(point, color='black', tag='start_point') def display_polygon(self, event): """A csúcspontok alapján megjeleníti a sokszöget, ha van csúcspont meghatározva.""" if self.polygon_vertices: self.draw_new_polygon(self.polygon_vertices) self.cnv.delete('polygon_vertex') # Az eddig megjelenített csúcspontok törlődnek. self.polygon_created = True # Jelezzük, hogy a sokszög elkészült, így ezek után újat nem lehet meghatározni. # 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): """A beviteli mezőben megadott szög, az előzetesen meghatározott forgáspont, valamint a sokszög eltárolt csúcspontjai alapján elforgatja a sokszöget, majd az elforgatott sokszög csúcspontjai alapján kirajzolja az új, elforgatott sokszöget.""" rotated_vertices = self.rotate_polygon(float(self.angle_var.get())) if rotated_vertices: self.draw_new_polygon(rotated_vertices) def display_centrally_inverted_polygon(self): """A sokszög eltárolt csúcspontjait középpontosan tükrözi az előzetesen meghatározott tükrözési pontra, majd az tükrözött sokszög csúcspontjai alapján kirajzolja az új sokszöget.""" # A középpontos tükrözést 180 fokos elforgatással valósítjuk meg. rotated_vertices = self.rotate_polygon(180) if rotated_vertices: self.draw_new_polygon(rotated_vertices) def display_reflected_polygon(self): """A sokszög eltárolt csúcspontjait tengelyesen tükrözi az előzetesen meghatározott tengelyvonalra, majd a tükrözött sokszög csúcspontjai alapján kirajzolja az új sokszöget.""" reflected_vertices = self.reflect_polygon() if reflected_vertices: self.draw_new_polygon(reflected_vertices) # Alkalmazás indítása. def run(self): self.mainloop() if __name__ == '__main__': app = App() app.run() |
Az egyszerűség kedvéért a felületen mindig csak egy tükrözési tengely és egy forgáspont definiálható. Így egyértelmű, hogy a forgatást vagy tükrözést mire vonatkozóan kell elvégezni. Ha megengednénk több tengely vagy forgáspont kirajzolását, akkor a geometriai transzformációk elvégzése előtt külön meg kellene jelölni, hogy a több tengely, illetve forgáspont közül melyikre vonatkoztatva akarjuk végrehajtani. Ez azonban elvinné e mintaprogram alapcéljáról a figyelmet, ezért nem tesszük ezt lehetővé.
Ahhoz, hogy az egyes kirajzolt pontok szerepük szerint a kirajzolás után is megkülönböztethetőek legyenek, a sokszög csúcspontjai, a forgáspont és a tengelyvonal kezdő- és végpontja eltérő színnel jelennek meg a rajzfelületen.
Az alábbi képernyőképeken egy háromszög tengelyes tükrözése, majd egyik csúcspontja körül 180 fokkal történő forgatása látható.

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.