在Python中将鼠标光标悬停在某些内容上时显示消息

时间:2013-12-05 11:49:36

标签: python tkinter tooltip

我在Python中使用TKinter制作了一个GUI。我希望能够在鼠标光标移动时显示消息,例如,在标签或按钮的顶部。这样做的目的是向用户解释按钮/标签的作用或代表。

当将鼠标悬停在Python中的tkinter对象上时,有没有办法显示文本?

8 个答案:

答案 0 :(得分:10)

您需要在<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()

答案 1 :(得分:9)

你可以参考这个 - 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()

截图:

Testing hoverbox

答案 2 :(得分:1)

这里是将tk.Button对象子类化的简单问题解决方案。我们制作了一个tk.Button继承的特殊类,并为其提供了工具提示功能。对于tk.Label来说也是一样。

我不知道什么是最干净,最简单的方式来维护用于跟踪工具提示中的文本的代码。我在这里提出一种方法,在该方法中,我将唯一的窗口小部件ID传递给MyButton,并访问用于存储工具提示文本的字典。您可以将此文件存储为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()

答案 3 :(得分:0)

这里是一个使用<enter><leave>的示例,如@bryanoakley建议的那样,它具有顶层(overridedirect设置为true)。使用hover_timer类可轻松实现此目的。这需要窗口小部件和帮助文本(带有可选的delay参数-默认值为0.5s),并且只需启动类然后将其取消即可轻松调用。

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 ()

答案 4 :(得分:0)

我有一个非常棘手的解决方案,但是与当前答案相比,它具有一些优势,因此我想与大家分享。

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)

唯一真正的问题是它留下了一个小框,该框将焦点从主窗口移开 如果有人知道如何解决这些问题,请告诉我

答案 5 :(得分:0)

我认为这可以满足您的要求。

以下是输出内容:

the output

首先,我创建了一个名为ToolTip的类,该类具有方法showtiphidetip

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 * 那里的一些程序员没有建议这样做,而且他们有道理。在这种情况下,您可能需要进行必要的更改。

要将笔尖移动到所需位置,可以在代码中更改xy。 函数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文件中,请不要忘记导入模块,也不要将文件另存为CreateToolTipToolTip以避免混淆。

答案 6 :(得分:0)

如果任何人都在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)

答案 7 :(得分:0)

我想为@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()
相关问题