继承 tkinter.canvas 类时出现意外行为

问题描述 投票:0回答:2

我正在开发一个可拖动的 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()
python tkinter inheritance tkinter-canvas
2个回答
0
投票

您将

Frame
放入
Canvas
(在
self
中使用
Frame(self)

当您使用

self.status_bar.pack()
时,它会自动计算父对象的大小并更改画布大小。

您可以使用

self.propagate(False)
(甚至在
__init__
中)来停止它。


正如@acw1668在评论中建议的那样,您也可以使用

place()
代替
pack()
,并且它不会重新计算画布大小。但我不知道当你调整窗口大小时它会如何表现。我得检查一下。


其他想法:不要将元素放入

Canvas
中,而是使用
Frame
创建类并将
Canvas
放入此
Frame
中,并且
other Frame
也放入此
Frame
中,而不是放入
Canvas
中。


0
投票

正如评论中所解释的,您无法将纯

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()
© www.soinside.com 2019 - 2024. All rights reserved.