如何为 Tkinter 按钮上的文本描述添加悬停功能?

How to add hover feature for text description on a Tkinter button?

我想在 Tkinter 按钮上添加悬停功能,如果用户悬停鼠标光标,则会显示描述文本。我还想为该描述的显示添加一些延迟,以免造成干扰。

我可以尝试使用 "<Enter>""<Leave>" 将按钮绑定到一个函数,并使一些“标签”出现在应用程序的某个角落。但这种方法可能不是最优雅的。

这是一个使用 Pmw(python 大型小部件)作为工具提示的小片段。

首先从安装开始:

pip install Pmw

然后这里是一个片段,以了解 Pmw 可以做什么:

from tkinter import *
import Pmw

root = Tk()

Pmw.initialise(root) #initializing it in the root window

l = Label(root,text='Random Text')
l.pack()

b = Button(root,text='Hover me')
b.pack()

tooltip_1 = Pmw.Balloon(root) #Calling the tooltip
tooltip_1.bind(b,'This is the hover Text\nHope you get an idea of whats going on here.') #binding it and assigning a text to it

root.mainloop()

希望这能给你一个更好的主意。请记住,Pmw 可能会在稍后将 py 转换为 exe 时造成混乱(如果您有任何意图)。还是有办法的。

干杯

这可以通过 tkinter 轻松完成。通过将 EnterLeave 事件添加到您想要添加工具提示的任何内容,我们可以轻松地 show/hide 我们想要的任何东西,无论我们想要什么。在我的示例中,我使用 stripped-down tk.Toplevel 因此我们可以有一个简单的淡入淡出动画,并且工具提示不会局限于根 window.

#widgets.py

import tkinter as tk, tkinter.ttk as ttk
from typing import Union

Widget = Union[tk.Widget, ttk.Widget]

class ToolTip(tk.Toplevel):
    #amount to adjust fade by on every animation frame
    FADE_INC:float = .07
    #amount of milliseconds to wait before next animation state
    FADE_MS :int   = 20
    
    def __init__(self, master, **kwargs):
        tk.Toplevel.__init__(self, master)
        #make window invisible, on the top, and strip all window decorations/features
        self.attributes('-alpha', 0, '-topmost', True)
        self.overrideredirect(1)
        #style and create label. you can override style with kwargs
        style = dict(bd=2, relief='raised', font='courier 10 bold', bg='#FFFF99', anchor='w')
        self.label = tk.Label(self, **{**style, **kwargs})
        self.label.grid(row=0, column=0, sticky='w')
        #used to determine if an opposing fade is already in progress
        self.fout:bool = False
        
    def bind(self, target:Widget, text:str, **kwargs):
        #bind Enter(mouseOver) and Leave(mouseOut) events to the target of this tooltip
        target.bind('<Enter>', lambda e: self.fadein(0, text, e))
        target.bind('<Leave>', lambda e: self.fadeout(1-ToolTip.FADE_INC, e))
        
    def fadein(self, alpha:float, text:str=None, event:tk.Event=None):
        #if event and text then this call came from target
        #~ we can consider this a "fresh/new" call
        if event and text:
            #if we are in the middle of fading out jump to end of fade
            if self.fout:
                self.attributes('-alpha', 0)
                #indicate that we are fading in
                self.fout = False
            #assign text to label
            self.label.configure(text=f'{text:^{len(text)+2}}')
            #update so the proceeding geometry will be correct
            self.update()
            #x and y offsets
            offset_x = event.widget.winfo_width()+2
            offset_y = int((event.widget.winfo_height()-self.label.winfo_height())/2)
            #get geometry
            w = self.label.winfo_width()
            h = self.label.winfo_height()
            x = event.widget.winfo_rootx()+offset_x
            y = event.widget.winfo_rooty()+offset_y
            #apply geometry
            self.geometry(f'{w}x{h}+{x}+{y}')
               
        #if we aren't fading out, fade in
        if not self.fout:
            self.attributes('-alpha', alpha)
        
            if alpha < 1:
                self.after(ToolTip.FADE_MS, lambda: self.fadein(min(alpha+ToolTip.FADE_INC, 1)))

    def fadeout(self, alpha:float, event:tk.Event=None):
        #if event then this call came from target 
        #~ we can consider this a "fresh/new" call
        if event:
            #indicate that we are fading out
            self.fout = True
        
        #if we aren't fading in, fade out        
        if self.fout:
            self.attributes('-alpha', alpha)
        
            if alpha > 0:
                self.after(ToolTip.FADE_MS, lambda: self.fadeout(max(alpha-ToolTip.FADE_INC, 0)))

#main.py ~ EXAMPLE USAGE OOP

import tkinter as tk
from widgets import ToolTip


class Root(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        
        #instantiate ToolTip
        tt = ToolTip(self)
        
        #create first button and bind a tooltip to it
        btn = tk.Button(self, text='hover')
        btn.grid(column=0, row=0)
        tt.bind(btn, 'first button is hovered')
        
        #create second button and bind a tooltip to it
        btn2 = tk.Button(self, text='hover2')
        btn2.grid(column=1, row=0)
        tt.bind(btn2, 'second button is hovered')
        

if __name__ == "__main__":
    root = Root()
    root.title("ToolTip Example")
    root.mainloop()

#main.py ~ EXAMPLE USAGE PROCEDURAL

import tkinter as tk 
from widgets import ToolTip      


if __name__ == "__main__":
    root = tk.Tk()
    root.title("ToolTip Example")
    
    #instantiate ToolTip
    tt = ToolTip(root)
    
    #create first button and bind a tooltip to it
    btn = tk.Button(root, text='hover')
    btn.grid(column=0, row=0)
    tt.bind(btn, 'first button is hovered')
    
    #create second button and bind a tooltip to it
    btn2 = tk.Button(root, text='hover2')
    btn2.grid(column=1, row=0)
    tt.bind(btn2, 'second button is hovered')
    
    root.mainloop()