ToolTip box

Member #562630 1 Tallied Votes 366 Views Share
from Tkinter import *
root = Tk()

tipwindow = None

# Creates a tooptip box for a widget.
def createToolTip( widget, text ):
    def enter( event ):
        global tipwindow
        x = y = 0
        if tipwindow or not text:
            return
        x, y, cx, cy = widget.bbox( "insert" )
        x += widget.winfo_rootx() + 27
        y += widget.winfo_rooty() + 27
        # Creates a toplevel window
        tipwindow = tw = Toplevel( widget )
        # Leaves only the label and removes the app window
        tw.wm_overrideredirect( 1 )
        tw.wm_geometry( "+%d+%d" % ( x, y ) )
        label = Label( tw, text = text, justify = LEFT,
                       background = "#ffffe0", relief = SOLID, borderwidth = 1,
                       font = ( "tahoma", "8", "normal" ) )
        label.pack( ipadx = 1 )
        
    def close( event ):
        global tipwindow
        tw = tipwindow
        tipwindow = None
        if tw:
            tw.destroy()
            
    widget.bind( "<Enter>", enter )
    widget.bind( "<Leave>", close )

b = Button( root, text = "Mouse over" ); b.pack()
createToolTip( b, "Mouse is over the button" )

mainloop()
Gribouillis commented: useful routine +14
I searched for the tooltip box in the forum and couldn't find a Python version for it.
So here's what I am using.

P.S I found it in google somewhere :)

Dani AI

Generated

Nice, minimal snippet — and is right about the Python‑3 naming. The example is a great quick solution, but it has a few practical limitations that commonly trip people up:

  • It calls bbox("insert"), which only works for text widgets and will raise an error for buttons and many other widgets.
  • It uses a single global tipwindow, so multiple tooltips can interfere with each other.
  • No show delay or screen‑edge handling, so tooltips can appear immediately or offscreen.
  • Bindings replace existing handlers unless you use add="+", and there’s no keyboard (focus) accessibility.

Below is a compact, class‑based alternative that addresses those points: per‑widget instances, a configurable delay, optional follow‑mouse behavior, wraplength, safe binding (uses add="+"), screen‑edge checks, and focus bindings for keyboard users. It uses Python 3 tkinter names; for Python 2 use import Tkinter as tk instead.

import tkinter as tk

class ToolTip:
    def __init__(self, widget, text="", delay=500, wraplength=200, follow=False):
        self.widget = widget
        self.text = text
        self.delay = delay
        self.wraplength = wraplength
        self.follow = follow
        self.tw = None
        self._after_id = None
        self.mouse_x = 0
        self.mouse_y = 0

        widget.bind("<Enter>", self._on_enter, add="+")
        widget.bind("<Leave>", self._on_leave, add="+")
        widget.bind("<ButtonPress>", self._on_leave, add="+")
        widget.bind("<Motion>", self._on_motion, add="+")
        widget.bind("<FocusIn>", self._on_enter, add="+")
        widget.bind("<FocusOut>", self._on_leave, add="+")

    def _on_enter(self, event=None):
        if event:
            self.mouse_x = event.x_root
            self.mouse_y = event.y_root
        self._schedule()

    def _on_motion(self, event):
        self.mouse_x = event.x_root
        self.mouse_y = event.y_root
        if self.tw and self.follow:
            self._reposition()

    def _schedule(self):
        self._unschedule()
        self._after_id = self.widget.after(self.delay, self._show)

    def _unschedule(self):
        if self._after_id:
            try:
                self.widget.after_cancel(self._after_id)
            except Exception:
                pass
            self._after_id = None

    def _show(self):
        if self.tw:
            return
        x = (self.mouse_x or self.widget.winfo_rootx()) + 20
        y = (self.mouse_y or self.widget.winfo_rooty()) + 10

        self.tw = tw = tk.Toplevel(self.widget)
        tw.wm_overrideredirect(True)
        label = tk.Label(tw, text=self.text, justify=tk.LEFT, background="#ffffe0",
                         relief=tk.SOLID, borderwidth=1, wraplength=self.wraplength)
        label.pack(ipadx=1)
        tw.update_idletasks()
        sw, sh = tw.winfo_screenwidth(), tw.winfo_screenheight()
        tw_w, tw_h = tw.winfo_reqwidth(), tw.winfo_reqheight()
        if x + tw_w > sw:
            x = sw - tw_w - 10
        if y + tw_h > sh:
            y = sh - tw_h - 10
        tw.wm_geometry("+%d+%d" % (x, y))

    def _reposition(self):
        if not self.tw:
            return
        x = self.mouse_x + 20
        y = self.mouse_y + 10
        self.tw.wm_geometry("+%d+%d" % (x, y))

    def _on_leave(self, event=None):
        self._unschedule()
        if self.tw:
            self.tw.destroy()
            self.tw = None

Usage example: attach ToolTip(widget, "Helpful hint", delay=600, wraplength=240, follow=True).

Troubleshooting tips: add add="+" to your binds to avoid clobbering other handlers; call update_idletasks() before reading requested geometry; use a sensible delay (250–700 ms) to avoid accidental popups; bind focus events for keyboard users.

richard.grigonis 0 Newbie Poster

I used this routine to add "balloon help" to a toolbar of icons. For Python 3.4 I simply had to make sure that Tkinter is lowercase: "tkinter" Ingeniously straightforward and simple. Bravo!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.