【问题标题】:Display message when hovering over something with mouse cursor in Python在 Python 中用鼠标光标悬停在某物上时显示消息
【发布时间】:2013-12-22 08:22:27
【问题描述】:

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

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

【问题讨论】:

标签: python tkinter tooltip


【解决方案1】:

我认为这会满足您的要求。

输出如下所示:

首先,一个名为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 * 那里的一些程序员没有建议,他们有正确的观点。在这种情况下,您可能需要进行必要的更改。

要将提示移动到您想要的位置,您可以更改代码中的xyCreateToolTip() 函数有助于轻松创建此提示。只需将要在提示框中显示的小部件和字符串传递给此函数,就可以了。

这就是你如何称呼上面的部分:

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以避免混淆。 Fuzzyman 的This 帖子也有一些类似的想法,值得一试。

【讨论】:

  • 似乎是一个非常好的解决方案,但就我而言,我得到了这个错误:TypeError: cannot unpack non-iterable NoneType object :/
  • 已修复...但谢谢。如果我没记错的话,我需要先将它集成到布局中才能将其注册到某个位置。但是我真的不记得了^^
  • 我真的很喜欢这个,但是从“Enter”事件到工具提示出现时引入一点延迟也会很好。
  • 您的代码与voidspace.org.uk/python/weblog/arch_d7_2006_07_01.shtml#e387 非常接近,您是该页面的所有者并已在此处重新发布吗?如果没有,最好引用您的来源,而不是声称该代码是您自己的。
  • 这正是我要找的,非常感谢
【解决方案2】:

您需要对&lt;Enter&gt;&lt;Leave&gt; 事件设置绑定。

注意:如果您选择弹出一个窗口(即:工具提示),请确保不要直接在鼠标下方弹出它。将会发生的是它会导致一个离开事件触发,因为光标离开标签并进入弹出窗口。然后,您的离开处理程序将关闭窗口,您的光标将进入标签,这会导致一个 enter 事件,它会弹出窗口,这会导致一个离开事件,它会关闭窗口,这会导致一个 enter 事件,...无限。

为简单起见,下面是一个更新标签的示例,类似于某些应用使用的状态栏。创建工具提示或其他显示信息的方式仍然从绑定到&lt;Enter&gt;&lt;Leave&gt; 的相同核心技术开始。

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

【讨论】:

  • 这不只是改变标签的文字吗?如果是这样,那不是 OP 想要的。
  • @user2188329:OP 没有明确表示他们想要一个工具提示。他们要求“显示一条信息”。他们可能想要一个工具提示,也可能不想要。无论如何,第一句话描述了数据是显示为工具提示还是显示在状态栏中的机制。
【解决方案3】:

你可以参考这个-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 的行为更像是一个项目菜单,而不是一个静态信息框。
  • 嗨,即使它在 Enter 上加载,如果我离开父小部件,它也不会自行删除。我必须单击父级以使其消失。任何想法为什么?
【解决方案4】:

我有一个非常老套的解决方案,但它比当前的答案有一些优势,所以我想我会分享它。

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】:

如果有人使用 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)

【讨论】:

    【解决方案6】:

    这是一个解决您的问题的简单解决方案,它是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()
    

    【讨论】:

      【解决方案7】:

      我发现创建弹出帮助窗口的最佳方法是使用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()
      


      旧答案:

      这是一个使用&lt;enter&gt;&lt;leave&gt; 的示例,正​​如@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 ()
      

      【讨论】:

        【解决方案8】:

        我想为@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()
        

        【讨论】:

        • 如果你使用 widget.bind('', lambda e:self.showtip),你可以进一步缩短它
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-28
        • 2011-02-02
        • 1970-01-01
        • 1970-01-01
        • 2020-10-11
        • 1970-01-01
        相关资源
        最近更新 更多