在 tkinter 中,有没有办法改变重叠画布的绘制堆栈顺序?

In tkinter, is there a way to change the draw stack order of overlapping canvases?

问题

使用 Python 的 tkinter,我试图通过扩展 Canvas widget 创建自定义按钮和其他小部件。如何更改在用户与之交互时将哪些自定义 canvas 小部件绘制在顶部?

lift() works for regular tkinter Buttons and other widgets, but raises an error when I try to use it to lift a Canvas, because Canvas has its own lift() method. Canvas's lift() is deprecated for Canvas in favor of tag_raise(). However, tag_raise() 文档说它 "doesn’t work with window items",这符合我的经验,并指导我改用 lift()。我的大脑追逐着这个看似循环的文档,直到它引发了它自己的一种 Whosebug 异常,这让我想问你。

代码说明

这里有一些基本代码可以运行并说明我的问题。我已经包括了 button3,一个可以按预期 lift() 的常规按钮。但是,如果我单击 custom_button1,click_handler 会引发异常。

from tkinter import Button, Canvas, Frame, Tk
from tkinter.constants import NW

class Example(Frame):

    def __init__(self, root):
        Frame.__init__(self, root)
        self.canvas = Canvas(self, width=200, height=200, background="black")
        self.canvas.grid(row=0, column=0, sticky="nsew")

        self.button3 = Button(self.canvas, text="button3")
        self.custom_button1 = MyCustomButton(self.canvas)
        self.custom_button2 = MyCustomButton(self.canvas)

        self.canvas.create_window(20, 20, anchor=NW, window=self.button3)
        self.canvas.create_window(40, 40, anchor=NW, window=self.custom_button1)
        self.canvas.create_window(34, 34, anchor=NW, window=self.custom_button2)

        self.button3.bind("<Button-1>", self.click_handler)
        self.custom_button1.bind("<Button-1>", self.click_handler)
        self.custom_button2.bind("<Button-1>", self.click_handler)


    def click_handler(self,event):
        event.widget.lift() #raises exception if event.widget is a MyCustomButton
                            #note that Canvas.lift() is deprecated, but documentation
                            #says Canvas.tag_raise() doesn't work with window items

class MyCustomButton(Canvas):
    def __init__(self, master):
        super().__init__(master, width=40, height=25, background='blue')


if __name__ == "__main__":
    root = Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()

这适用于 button3,但对于 custom_button1,引发的异常是:

_tkinter.TclError: wrong # args: should be ".!example.!canvas.!mycustombutton2 raise tagOrId ?aboveThis?"

在 Canvas.lift() 和 Canvas.tag_raise() 通常用于通过标签或 ID 影响 canvas 上的项目而不是 canvas 本身。我只是不知道如何更改 canvas 本身的堆栈顺序,以便我可以将其用作自定义小部件。

我考虑过的解决方法

我可以在 canvas 上管理一堆自定义小部件,只需让 一个 canvas 处理 所有[=42] =] 绘图和 all 鼠标事件 all 小部件。我仍然可以为小部件使用 类,但它们不会继承 Canvas,而是接受 Canvas 参数。所以添加看起来像下面的代码,我必须编写类似的代码来提升、移动、确定是否将单击事件应用于此按钮、更改活动状态等等。

def add_to_canvas(self, canvas, offset_x=0, offset_y=0):

        self.button_border = canvas.create_rectangle(
                                        offset_x + 0, offset_y + 0,
                                        offset_x + 40, offset_y + 25
                                        )

        #create additional button features

不过,这种变通办法似乎违背了 tkinter 中已建立的编码范例。此外,我相信这种方法会阻止我在其他 window 对象上方绘制这些自定义按钮。 (根据 create_window() documentation "You cannot draw other canvas items on top of a widget." 在这个解决方法中,所有自定义按钮都是 canvas 项目,所以如果我没看错的话,不能绘制在顶部其他小部件。)更不用说实现所需的额外代码了。也就是说,我目前对如何实现它没有更好的想法。

提前感谢您的帮助!

不幸的是,您无意中发现了 tkinter 实现中的错误。您可以通过多种方式解决此问题。您可以创建一个方法来执行 tkinter lift 方法所做的事情,或者您可以直接调用 tkinter Misc class.

中的方法

由于您正在创建自己的 class,因此您可以重写 lift 方法以使用其中任何一种方法。

以下是使用现有函数的方法。请务必从 tkinter 导入 Misc

from tkinter import Misc
...
class MyCustomButton(Canvas):
    ...
    def lift(self, aboveThis=None):
        Misc.tkraise(self)

下面是直接调用底层 tk 解释器的方法:

class MyCustomButton(Canvas):
    ...
    def lift(self, aboveThis=None):
        self.tk.call('raise', self._w, aboveThis)

有了它,您可以通过调用 lift 方法将一个按钮提升到另一个按钮上:

def click_handler(self,event):
    event.widget.lift()