首页 > 解决方案 > Tkinter 按钮工具提示线程问题

问题描述

您好我正在尝试创建在悬停时具有工具提示气泡的 Tkinter 按钮。我希望工具提示在输入时延迟,并且只显示一定的时间。我正在使用线程来显示工具提示,以免阻止单击按钮。我似乎非常接近解决方案,但我认为有一个问题与线程有关。工具提示是一个没有任何窗口装饰(标题栏、边框等)的顶级窗口。输入按钮时,会显示工具提示,但有时它有装饰,有时没有,有时它出现在正确的位置,有时却没有。我不知道为什么会发生这种行为,下面我发布了我用来解决这个问题的测试代码,但现在我失去了任何帮助将不胜感激。

from time import sleep
from tkinter import Tk, Button, Label, Toplevel
from threading import Thread


class _Button(Button):
    def __init__(self, parent, *args, **kwargs):
        self.tooltip_text = kwargs.pop('tooltip', None)
        super().__init__(*args, **kwargs)

        self.t = None
        self.parent = parent
        self.btn_tooltip = None

    def tooltip(self):
        if not self.t:
            self.t = Thread(target=self.tooltip_render)
            self.t.start()

    def tooltip_render(self):
        sleep(0.5)
        if not self.btn_tooltip:
            self.btn_tooltip = Toplevel()
            self.btn_tooltip.wm_overrideredirect(True)

            x, y, cx, cy = self.bbox('insert')
            x += self.winfo_rootx() + 25
            y += self.winfo_rooty() + 25

            self.btn_tooltip.geometry('+%d+%d' % (x, y))
            label = Label(
                self.btn_tooltip, text=self.tooltip_text, background='yellow', borderwidth=1,
            )
            label.pack(ipadx=5, ipady=2)

            sleep(1)
            if self.btn_tooltip:
                self.btn_tooltip.destroy()
                self.btn_tooltip = None
                self.t = None


def enter(e):
    e.widget.tooltip()


def leave(e):
    if e.widget.btn_tooltip:
        e.widget.btn_tooltip.destroy()
        e.widget.btn_tooltip = None
        e.widget.t = None


root = Tk()

bt1 = _Button(root, text='Button 1', tooltip='Tooltip1')
bt1.bind('<Enter>', enter)
bt1.bind('<Leave>', leave)
bt1.grid()

bt2 = _Button(root, text='Button 2', tooltip='Tooltip2')
bt2.bind('<Enter>', enter)
bt2.bind('<Leave>', leave)
bt2.grid(row=0, column=1)


root.mainloop()

标签: pythonmultithreadingtkinter

解决方案


您不需要线程来显示工具提示,然后在几秒钟后消失。即使您有丰富的线程经验,线程也很困难。如果不这样做会更加困难,而将其与基于事件的程序结合起来会更加困难。

相反,您应该使用 tkinter 提供的工具。即,after可以调度代码运行到未来的方法。

您需要做的就是使用after来显示您的工具提示,然后after再次使用来关闭它。

基本模式如下所示:

def tooltip_render(self):
    # create the tooltip
    self.btn_tooltip = Toplevel()
    ... 
    <the rest of your code to render the tooltip> 
    ...

    # schedule it to go away
    self.after(1000, self.btn_tooltip.destroy)

接下来,render_tooltip再次使用after

def tooltip(self):
    self.after(500, self.tooltip_render)

仅此一项不会为您提供完美的工具提示。如果用户快速移动鼠标,您仍然需要防止尝试渲染和销毁多个工具提示,但这为您提供了通用框架,而无需求助于线程。


推荐阅读