【发布时间】:2013-12-22 08:22:27
【问题描述】:
我有一个用 Python 中的 TKinter 制作的 GUI。我希望能够在鼠标光标移动时显示一条消息,例如,在标签或按钮的顶部。这样做的目的是向用户解释按钮/标签的作用或代表什么。
有没有办法在 Python 中将鼠标悬停在 tkinter 对象上时显示文本?
【问题讨论】:
我有一个用 Python 中的 TKinter 制作的 GUI。我希望能够在鼠标光标移动时显示一条消息,例如,在标签或按钮的顶部。这样做的目的是向用户解释按钮/标签的作用或代表什么。
有没有办法在 Python 中将鼠标悬停在 tkinter 对象上时显示文本?
【问题讨论】:
我认为这会满足您的要求。
输出如下所示:
首先,一个名为ToolTip 的类具有showtip 和hidetip 方法定义如下:
from tkinter import *
class ToolTip(object):
def __init__(self, widget):
self.widget = widget
self.tipwindow = None
self.id = None
self.x = self.y = 0
def showtip(self, text):
"Display text in tooltip window"
self.text = text
if self.tipwindow or not self.text:
return
x, y, cx, cy = self.widget.bbox("insert")
x = x + self.widget.winfo_rootx() + 57
y = y + cy + self.widget.winfo_rooty() +27
self.tipwindow = tw = Toplevel(self.widget)
tw.wm_overrideredirect(1)
tw.wm_geometry("+%d+%d" % (x, y))
label = Label(tw, text=self.text, justify=LEFT,
background="#ffffe0", relief=SOLID, borderwidth=1,
font=("tahoma", "8", "normal"))
label.pack(ipadx=1)
def hidetip(self):
tw = self.tipwindow
self.tipwindow = None
if tw:
tw.destroy()
def CreateToolTip(widget, text):
toolTip = ToolTip(widget)
def enter(event):
toolTip.showtip(text)
def leave(event):
toolTip.hidetip()
widget.bind('<Enter>', enter)
widget.bind('<Leave>', leave)
小部件是您要添加提示的位置。例如,如果您希望在将鼠标悬停在按钮、条目或标签上时获得提示,则应在调用时提供相同的实例。
快速说明:上面的代码使用from tkinter import *
那里的一些程序员没有建议,他们有正确的观点。在这种情况下,您可能需要进行必要的更改。
要将提示移动到您想要的位置,您可以更改代码中的x 和y。
CreateToolTip() 函数有助于轻松创建此提示。只需将要在提示框中显示的小部件和字符串传递给此函数,就可以了。
这就是你如何称呼上面的部分:
button = Button(root, text = 'click mem')
button.pack()
CreateToolTip(button, text = 'Hello World\n'
'This is how tip looks like.'
'Best part is, it\'s not a menu.\n'
'Purely tipbox.')
如果将之前的大纲保存在不同的python文件中,请不要忘记导入模块,并且不要将文件保存为CreateToolTip或ToolTip以避免混淆。
Fuzzyman 的This 帖子也有一些类似的想法,值得一试。
【讨论】:
您需要对<Enter> 和<Leave> 事件设置绑定。
注意:如果您选择弹出一个窗口(即:工具提示),请确保不要直接在鼠标下方弹出它。将会发生的是它会导致一个离开事件触发,因为光标离开标签并进入弹出窗口。然后,您的离开处理程序将关闭窗口,您的光标将进入标签,这会导致一个 enter 事件,它会弹出窗口,这会导致一个离开事件,它会关闭窗口,这会导致一个 enter 事件,...无限。
为简单起见,下面是一个更新标签的示例,类似于某些应用使用的状态栏。创建工具提示或其他显示信息的方式仍然从绑定到<Enter> 和<Leave> 的相同核心技术开始。
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.l1 = tk.Label(self, text="Hover over me")
self.l2 = tk.Label(self, text="", width=40)
self.l1.pack(side="top")
self.l2.pack(side="top", fill="x")
self.l1.bind("<Enter>", self.on_enter)
self.l1.bind("<Leave>", self.on_leave)
def on_enter(self, event):
self.l2.configure(text="Hello world")
def on_leave(self, enter):
self.l2.configure(text="")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand="true")
root.mainloop()
【讨论】:
你可以参考这个-HoverClass
这正是您所需要的。不多也不少
from Tkinter import *
import re
class HoverInfo(Menu):
def __init__(self, parent, text, command=None):
self._com = command
Menu.__init__(self,parent, tearoff=0)
if not isinstance(text, str):
raise TypeError('Trying to initialise a Hover Menu with a non string type: ' + text.__class__.__name__)
toktext=re.split('\n', text)
for t in toktext:
self.add_command(label = t)
self._displayed=False
self.master.bind("<Enter>",self.Display )
self.master.bind("<Leave>",self.Remove )
def __del__(self):
self.master.unbind("<Enter>")
self.master.unbind("<Leave>")
def Display(self,event):
if not self._displayed:
self._displayed=True
self.post(event.x_root, event.y_root)
if self._com != None:
self.master.unbind_all("<Return>")
self.master.bind_all("<Return>", self.Click)
def Remove(self, event):
if self._displayed:
self._displayed=False
self.unpost()
if self._com != None:
self.unbind_all("<Return>")
def Click(self, event):
self._com()
使用 HoverInfo 的示例应用:
from Tkinter import *
from HoverInfo import HoverInfo
class MyApp(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.grid()
self.lbl = Label(self, text='testing')
self.lbl.grid()
self.hover = HoverInfo(self, 'while hovering press return \n for an exciting msg', self.HelloWorld)
def HelloWorld(self):
print('Hello World')
app = MyApp()
app.master.title('test')
app.mainloop()
截图:
【讨论】:
HoverInfo 的行为更像是一个项目菜单,而不是一个静态信息框。
我有一个非常老套的解决方案,但它比当前的答案有一些优势,所以我想我会分享它。
lab=Label(root,text="hover me")
lab.bind("<Enter>",popup)
def do_popup(event):
# display the popup menu
root.after(1000, self.check)
popup = Menu(root, tearoff=0)
popup.add_command(label="Next")
popup.tk_popup(event.x_root, event.y_root, 0)
def check(event=None):
x, y = root.winfo_pointerxy()
widget = root.winfo_containing(x, y)
if widget is None:
root.after(100, root.check)
else:
leave()
def leave():
popup.delete(0, END)
唯一真正的问题是它留下了一个小框,将焦点从主窗口移开 如果有人知道如何解决这些问题,请告诉我
【讨论】:
如果有人使用 Mac OSX 并且工具提示不起作用,请查看以下示例:
https://github.com/python/cpython/blob/master/Lib/idlelib/tooltip.py
基本上,让它在 Mac OSX 上为我工作的两行是:
tw.update_idletasks() # Needed on MacOS -- see #34275.
tw.lift() # work around bug in Tk 8.5.18+ (issue #24570)
【讨论】:
这是一个解决您的问题的简单解决方案,它是tk.Button 对象的子类。我们创建了一个 tk.Button 继承的特殊类,赋予它工具提示功能。 tk.Labels 也是如此。
我不知道什么是最简洁和最简单的方法来维护用于跟踪进入工具提示的文本的代码。我在这里介绍一种方式,其中我将唯一的小部件 ID 传递给 MyButtons,并访问用于存储工具提示文本的字典。您可以将此文件存储为 JSON、类属性或全局变量(如下所示)。或者,也许最好在 MyButton 中定义一个 setter 方法,并且每次创建一个应该有工具提示的新小部件时调用这个方法。尽管您必须将小部件实例存储在一个变量中,但要为所有要包含的小部件添加一行。
下面代码的一个缺点是self.master.master 语法依赖于确定“小部件深度”。一个简单的递归函数将捕获大多数情况(只需要输入一个小部件,因为根据定义,你会离开你曾经所在的地方)。
无论如何,下面是一个适用于任何感兴趣的人的工作 MWE。
import tkinter as tk
tooltips = {
'button_hello': 'Print a greeting message',
'button_quit': 'Quit the program',
'button_insult': 'Print an insult',
'idle': 'Hover over button for help',
'error': 'Widget ID not valid'
}
class ToolTipFunctionality:
def __init__(self, wid):
self.wid = wid
self.widet_depth = 1
self.widget_search_depth = 10
self.bind('<Enter>', lambda event, i=1: self.on_enter(event, i))
self.bind('<Leave>', lambda event: self.on_leave(event))
def on_enter(self, event, i):
if i > self.widget_search_depth:
return
try:
cmd = f'self{".master"*i}.show_tooltip(self.wid)'
eval(cmd)
self.widget_depth = i
except AttributeError:
return self.on_enter(event, i+1)
def on_leave(self, event):
cmd = f'self{".master" * self.widget_depth}.hide_tooltip()'
eval(cmd)
class MyButton(tk.Button, ToolTipFunctionality):
def __init__(self, parent, wid, **kwargs):
tk.Button.__init__(self, parent, **kwargs)
ToolTipFunctionality.__init__(self, wid)
class MyLabel(tk.Label, ToolTipFunctionality):
def __init__(self, parent, wid, **kwargs):
tk.Label.__init__(self, parent, **kwargs)
ToolTipFunctionality.__init__(self, wid)
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.tooltip = tk.StringVar()
self.tooltip.set(tooltips['idle'])
self.frame = tk.Frame(self, width=50)
self.frame.pack(expand=True)
MyLabel(self.frame, '', text='One Cool Program').pack()
self.subframe = tk.Frame(self.frame, width=40)
self.subframe.pack()
MyButton(self.subframe, 'button_hello', text='Hello!', command=self.greet, width=20).pack()
MyButton(self.subframe, 'button_insutl', text='Insult', command=self.insult, width=20).pack()
MyButton(self.subframe, 'button_quit', text='Quit', command=self.destroy, width=20).pack()
tk.Label(self.subframe, textvar=self.tooltip, width=20).pack()
def show_tooltip(self, wid):
try:
self.tooltip.set(tooltips[wid])
except KeyError:
self.tooltip.set(tooltips['error'])
def hide_tooltip(self):
self.tooltip.set(tooltips['idle'])
def greet(self):
print('Welcome, Fine Sir!')
def insult(self):
print('You must be dead from the neck up')
if __name__ == '__main__':
app = Application()
app.mainloop()
【讨论】:
我发现创建弹出帮助窗口的最佳方法是使用tix.Balloon。我在下面对其进行了修改以使其看起来更好并显示一个示例(注意使用tix.Tk):
import tkinter as tk
import tkinter.tix as tix
class Balloon(tix.Balloon):
# A modified tix popup balloon (to change the default delay, bg and wraplength)
init_after = 1250 # Milliseconds
wraplength = 300 # Pixels
def __init__(self, master):
bg = root.cget("bg")
# Call the parent
super().__init__(master, initwait=self.init_after)
# Change background colour
for i in self.subwidgets_all():
i.config(bg=bg)
# Modify the balloon label
self.message.config(wraplength=self.wraplength)
root = tix.Tk()
l = tk.Label(root, text="\n".join(["text"] * 5))
l.pack()
b = Balloon(root.winfo_toplevel())
b.bind_widget(l, balloonmsg="Some random text")
root.mainloop()
旧答案:
这是一个使用<enter> 和<leave> 的示例,正如@bryanoakley 建议的顶级(overridedirect 设置为true)。使用hover_timer 类可以轻松使用它。这需要小部件和帮助文本(带有可选的延迟参数 - 默认为 0.5 秒),并且只需启动类然后取消它即可轻松调用。
import threading, time
from tkinter import *
class hover_window (Toplevel):
def __init__ (self, coords, text):
super ().__init__ ()
self.geometry ("+%d+%d" % (coords [0], coords [1]))
self.config (bg = "white")
Label (self, text = text, bg = "white", relief = "ridge", borderwidth = 3, wraplength = 400, justify = "left").grid ()
self.overrideredirect (True)
self.update ()
self.bind ("<Enter>", lambda event: self.destroy ())
class hover_timer:
def __init__ (self, widget, text, delay = 2):
self.wind, self.cancel_var, self.widget, self.text, self.active, self.delay = None, False, widget, text, False, delay
threading.Thread (target = self.start_timer).start ()
def start_timer (self):
self.active = True
time.sleep (self.delay)
if not self.cancel_var: self.wind = hover_window ((self.widget.winfo_rootx (), self.widget.winfo_rooty () + self.widget.winfo_height () + 20), self.text)
self.active = False
def delayed_stop (self):
while self.active: time.sleep (0.05)
if self.wind:
self.wind.destroy ()
self.wind = None
def cancel (self):
self.cancel_var = True
if not self.wind: threading.Thread (target = self.delayed_stop).start ()
else:
self.wind.destroy ()
self.wind = None
def start_help (event):
# Create a new help timer
global h
h = hover_timer (l, "This is some additional information.", 0.5)
def end_help (event):
# If therre is one, end the help timer
if h: h.cancel ()
if __name__ == "__main__":
# Create the tkinter window
root = Tk ()
root.title ("Hover example")
# Help class not created yet
h = None
# Padding round label
Frame (root, width = 50).grid (row = 1, column = 0)
Frame (root, height = 50).grid (row = 0, column = 1)
Frame (root, width = 50).grid (row = 1, column = 2)
Frame (root, height = 50).grid (row = 2, column = 1)
# Setup the label
l = Label (root, text = "Hover over me for information.", font = ("sans", 32))
l.grid (row = 1, column = 1)
l.bind ("<Enter>", start_help)
l.bind ("<Leave>", end_help)
# Tkinter mainloop
root.mainloop ()
【讨论】:
我想为@squareRoot17 的答案做出贡献,因为他启发我在提供相同功能的同时缩短他的代码:
import tkinter as tk
class ToolTip(object):
def __init__(self, widget, text):
self.widget = widget
self.text = text
def enter(event):
self.showTooltip()
def leave(event):
self.hideTooltip()
widget.bind('<Enter>', enter)
widget.bind('<Leave>', leave)
def showTooltip(self):
self.tooltipwindow = tw = tk.Toplevel(self.widget)
tw.wm_overrideredirect(1) # window without border and no normal means of closing
tw.wm_geometry("+{}+{}".format(self.widget.winfo_rootx(), self.widget.winfo_rooty()))
label = tk.Label(tw, text = self.text, background = "#ffffe0", relief = 'solid', borderwidth = 1).pack()
def hideTooltip(self):
tw = self.tooltipwindow
tw.destroy()
self.tooltipwindow = None
这个类可以被导入并用作:
import tkinter as tk
from tooltip import ToolTip
root = tk.Tk()
your_widget = tk.Button(root, text = "Hover me!")
ToolTip(widget = your_widget, text = "Hover text!")
root.mainloop()
【讨论】: