Ich habe dies auch schon mit einem relativen zentralelement versucht zu dem ich dann immer relativ den versatz berechnen kann. Dies ist jedoch sehr ineffizient. Am liebsten wär mir nur die Berechnung in der
methode anzupassen. Alternativ hatte ich schon vergeblich versucht den Sourcecode der methode zu findet, falls jemand weiß wie man soetwas anstellt würde das wahrscheinlich auch helfen.canvas_zoom(self, x, y, scale_multiplier)
Code: Alles auswählen
import tkinter as tk
class App(tk.Tk):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.status_bar : StatusBar = StatusBar(self, bg = "lightgrey")
self.status_bar.pack(fill = "x", side = "bottom")
self.map_canvas : MapCanvas = MapCanvas(self, self.status_bar, bg = "white")
self.map_canvas.pack(fill = "both", expand = True)
def mainloop(self):
super().mainloop()
class StatusBar(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.position_label : tk.Label = tk.Label(self, text = "Position: --, --")
self.position_label.pack(side = "left")
self.cursor_position_label : tk.Label = tk.Label(self, text = "Cursor: --, --")
self.cursor_position_label.pack(side = "left")
self.scale_label : tk.Label = tk.Label(self, text = f"Scale: --")
self.scale_label.pack(side = "right")
def set_position(self, position : tuple[float, float]):
x = position[0]
y = position[1]
self.position_label.config(text = f"Position: {x:.4f}, {y:.4f}")
def set_cursor_position(self, cursor_position : tuple[float, float]):
x = cursor_position[0]
y = cursor_position[1]
self.cursor_position_label.config(text = f"Cursor: {x:.4f}, {y:.4f}")
def set_scale(self, scale : float):
self.scale_label.config(text = f"Scale: {scale:.6f}")
class MapCanvas(tk.Canvas):
def __init__(self, master : App, statusBar : StatusBar, **kwargs):
super().__init__(master, **kwargs)
self.current_offset : tuple[float, float] = (0.0, 0.0)
self.current_scale : float = 20/1 # pixel / meter
self.last_x : float = 0
self.last_y : float = 0
self.width : int = self.winfo_width()
self.height : int = self.winfo_height()
self.bind("<Configure>", self.on_configure)
self.bind("<Motion>", self.on_motion)
self.bind("<Button-1>", self.on_b1_click)
self.bind("<Button-3>", self.on_b3_click)
self.bind("<B1-Motion>", self.on_b1_motion)
self.bind("<MouseWheel>", self.on_mouse_wheel)
self.status_bar : StatusBar = statusBar
self.update_status_position()
self.update_status_cursor_position()
self.update_status_scale()
def on_configure(self, event) -> None:
dx = (event.width - self.width)/2
dy = (event.height - self.height)/2
self.width = event.width
self.height = event.height
self.move("all", dx, dy)
def on_motion(self, event) -> None:
self.last_x = event.x
self.last_y = event.y
self.update_status_cursor_position()
def on_b1_click(self, event) -> None:
pass
def on_b3_click(self, event) -> None:
print(f"do something with right click at {self.get_cursor_position()}")
def on_b1_motion(self, event) -> None:
dx = event.x - self.last_x
dy = event.y - self.last_y
self.last_x = event.x
self.last_y = event.y
self.canvas_pan(dx, dy)
def on_mouse_wheel(self, event) -> None:
if event.delta > 0:
scale_multiplier = 1.1
else:
scale_multiplier = 0.9
self.canvas_zoom(event.x, event.y, scale_multiplier)
def canvas_pan(self, dx, dy) -> None:
self.move("all", dx, dy)
old_offset_x, old_offset_y = self.current_offset
cs = self.current_scale
self.current_offset = (old_offset_x - dx / cs, old_offset_y + dy / cs)
self.update_status_position()
def canvas_zoom(self, x, y, scale_multiplier) -> None:
self.scale("all", x, y, scale_multiplier, scale_multiplier)
self.current_scale *= scale_multiplier
# TODO: fix this
ds = scale_multiplier
cx, cy = self.current_offset
self.current_offset = (x + (cx - x) * ds, y + (cy - y) * ds)
self.current_offset = (cx + (ds - 1) * (cx - x), cy + (ds - 1) * (cy - y))
self.update_status_scale()
if x != self.width/2 or y != self.height/2:
self.update_status_position()
if self.last_x != x or self.last_y != y:
self.update_status_cursor_position()
def create_rectangle(self, x1, y1, x2, y2, **kwargs) -> int:
cs = self.current_scale
x1 = x1 * cs + self.current_offset[0] + self.width/2
y1 = -y1 * cs + self.current_offset[1] + self.height/2
x2 = x2 * cs + self.current_offset[0] + self.width/2
y2 = -y2 * cs + self.current_offset[1] + self.height/2
return super().create_rectangle(x1, y1, x2, y2, **kwargs)
def create_oval(self, x1, y1, x2, y2, **kwargs) -> int:
cs = self.current_scale
x1 = x1 * cs + self.current_offset[0] + self.width/2
y1 = -y1 * cs + self.current_offset[1] + self.height/2
x2 = x2 * cs + self.current_offset[0] + self.width/2
y2 = -y2 * cs + self.current_offset[1] + self.height/2
return super().create_oval(x1, y1, x2, y2, **kwargs)
def create_line(self, *args, **kwargs) -> int:
coords = list(args)
cs = self.current_scale
for i in range(0, len(coords), 2):
coords[i] = coords[i] * cs + self.current_offset[0] + self.width/2
coords[i + 1] = -coords[i + 1] * cs + self.current_offset[1] + self.height/2
return super().create_line(*coords, **kwargs)
def get_cursor_position(self) -> tuple[float, float]:
cs = self.current_scale
return (self.current_offset[0] + (self.last_x - self.width/2) / cs, self.current_offset[1] - (self.last_y - self.height/2) / cs)
return (self.current_offset[0] + (self.last_x) / cs, self.current_offset[1] - (self.last_y) / cs)
def update_status_position(self) -> None:
self.status_bar.set_position(self.current_offset)
def update_status_cursor_position(self) -> None:
cursor_position = self.get_cursor_position()
self.status_bar.set_cursor_position(cursor_position)
def update_status_scale(self) -> None:
self.status_bar.set_scale(self.current_scale)
def set_current_offset(self, offset : tuple[float, float]) -> None:
# TODO: Implement this method
pass
def set_current_scale(self, scale : float) -> None:
x = self.winfo_width()/2
y = self.winfo_height()/2
delta = self.current_scale / scale
self.canvas_zoom(x, y, delta)
if __name__ == "__main__":
app = App()
app.map_canvas.create_oval(-5, -5, 0, 0, fill = "red", outline = "red")
app.map_canvas.create_oval(0, 0, 5, 5, fill = "black", outline = "black")
grid_number = 10
grid_space = 1
for i in range(int(-grid_number/2), int(grid_number/2)+1):
app.map_canvas.create_line(-grid_number/2, i * grid_space, grid_number/2, i * grid_space, fill = "grey")
app.map_canvas.create_line(i * grid_space, -grid_number/2, i * grid_space, grid_number/2, fill = "grey")
app.mainloop()