我正在开发一个可拖动的 tkinter 画布类,它知道它的位置和比例,为此我想使用一个继承自
tkinter.canvas
的类。但是,继承类的行为与我使用 tkinter.canvas
类时不同。问题不在于初始化我自己的类时的大小不正确。我想知道为什么,以及如何解决这个差异...已知的AI工具重写了程序,但无法解释为什么继承行为与标准类不同。
我使用的是 win11 23H2 x64 系统,Python 3.10.7。只需将
True
类的 __init__
中的 App
更改为 False
,它的行为就会有所不同,至少在我的机器上是这样。
import tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
if True: # change True to False to see the difference
# using the ebenden class
self.canvas = OwnCanvas(self, width=400, height=400)
self.canvas.pack(side="top", fill="both", expand=True)
else:
# using the tkinter class
self.canvas = tk.Canvas(self, width=400, height=400)
self.canvas.pack(side="top", fill="both", expand=True)
for y in range(-10, 10):
for x in range(-10, 10):
self.canvas.create_oval(x-5, y-5, x+5, y+5)
class OwnCanvas(tk.Canvas):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.status_bar = tk.Frame(self)
self.ulc_position = [.0, .0] # upper left corner position
self.position_label = tk.Label(self.status_bar, text="Position: (0, 0)")
self.c_scale = 1.0 # canvas scale
self.scale_label = tk.Label(self.status_bar, text="Scale: 1.0")
self.bind("<Button-1>", self.on_b1_click)
self.bind("<B1-Motion>", self.on_b1_motion)
self.bind("<MouseWheel>", self.on_mousewheel)
self.lastX, self.lastY = 0, 0
def pack(self, *args, **kwargs):
super().pack(*args, **kwargs)
self.status_bar.pack(side="bottom", fill="x")
self.position_label.pack(side="left")
self.scale_label.pack(side="left")
def on_b1_click(self, event):
self.lastX, self.lastY = event.x, event.y
def on_b1_motion(self, event):
dx, dy = event.x-self.lastX, event.y-self.lastY
self.move(tk.ALL, dx, dy)
self.lastX, self.lastY = event.x, event.y
self.ulc_position[0] += dx
self.ulc_position[1] += dy
self.position_label.config(text=f"Position: ({self.ulc_position[0]}, {self.ulc_position[1]})")
def on_mousewheel(self, event):
if event.delta > 0:
self.scale(tk.ALL, 0, 0, 1.1, 1.1)
self.c_scale *= 1.1
else:
self.scale(tk.ALL, 0, 0, 0.9, 0.9)
self.c_scale *= 0.9
self.scale_label.config(text=f"Scale: {self.c_scale:.2f}")
def main():
app = App()
app.mainloop()
if __name__ == "__main__":
main()
您将
Frame
放入 Canvas
(在 self
中使用 Frame(self)
)
当您使用
self.status_bar.pack()
时,它会自动计算父对象的大小并更改画布大小。
您可以使用
self.propagate(False)
(甚至在 __init__
中)来停止它。
正如@acw1668在评论中建议的那样,您也可以使用
place()
代替pack()
,并且它不会重新计算画布大小。但我不知道当你调整窗口大小时它会如何表现。我得检查一下。
其他想法:不要将元素放入
Canvas
中,而是使用Frame
创建类并将Canvas
放入此Frame
中,并且other Frame
也放入此Frame
中,而不是放入Canvas
中。
正如评论中所解释的,您无法将纯
Canvas
的初始化与基本上创建同一级别的小部件组合进行比较。
标准 tk.Canvas 内部没有 Frame 和其他元素,并且不必使用 Frame 的大小来计算大小。如果你想将小部件放在 Canvas 上,那么你应该使用 canvas.create_window(widget) 而不是使用 widget.pack()。如果你想使用 pack() 那么也许你应该从 Frame 开始并将 Canvas 放入此 Frame 中,其他帧也放入此 Frame 中,而不是放入 Canvas 中。 – 弗拉斯
如果您在初始化中传递的大小仅适用于画布,并且整个小部件
OwnCanvas
可以更大一点,您可以将内容包装在container
框架中。否则,您可以设置 status_bar
的大小,并在初始化 Canvas
时从传递给类的几何图形中减去它。
这是容器实现的示例,无需计算大小以尊重
status_bar
的大小:
import tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# using the ebenden class
self.canvas = OwnCanvas(master=self, width=400, height=400)
self.canvas.pack(side="top", fill="both", expand=True)
for y in range(-10, 10):
for x in range(-10, 10):
self.canvas.create_oval(x - 5, y - 5, x + 5, y + 5)
class OwnCanvas(tk.Canvas):
def __init__(self, master, *args, **kwargs) -> None:
self.container = tk.Frame(master)
super().__init__(master=self.container, *args, **kwargs)
self.status_bar = tk.Frame(self.container)
self.ulc_position = [.0, .0] # upper left corner position
self.position_label = tk.Label(self.status_bar, text="Position: (0, 0)")
self.c_scale = 1.0 # canvas scale
self.scale_label = tk.Label(self.status_bar, text="Scale: 1.0")
self.bind("<Button-1>", self.on_b1_click)
self.bind("<B1-Motion>", self.on_b1_motion)
self.bind("<MouseWheel>", self.on_mousewheel)
self.lastX, self.lastY = 0, 0
def pack(self, *args, **kwargs):
self.container.pack()
super().pack(*args, **kwargs)
self.status_bar.pack(side="bottom", fill="x")
self.position_label.pack(side="left")
self.scale_label.pack(side="left")
def on_b1_click(self, event):
self.lastX, self.lastY = event.x, event.y
def on_b1_motion(self, event):
dx, dy = event.x - self.lastX, event.y - self.lastY
self.move(tk.ALL, dx, dy)
self.lastX, self.lastY = event.x, event.y
self.ulc_position[0] += dx
self.ulc_position[1] += dy
self.position_label.config(text=f"Position: ({self.ulc_position[0]}, {self.ulc_position[1]})")
def on_mousewheel(self, event):
if event.delta > 0:
self.scale(tk.ALL, 0, 0, 1.1, 1.1)
self.c_scale *= 1.1
else:
self.scale(tk.ALL, 0, 0, 0.9, 0.9)
self.c_scale *= 0.9
self.scale_label.config(text=f"Scale: {self.c_scale:.2f}")
def main():
app = App()
app.mainloop()
if __name__ == "__main__":
main()