How do I display tooltips in Tkinter?
I tried the code in the blog post mentioned by ars, and also tried the code from the IDLE lib.
While both worked, I didn't like how the tooltip from IDLE was limited in size (had to manually enter new lines as separate lists) , and how the tips appeared immediately in the code form the blog post.
So I made a hybrid between the two. It lets you specify a wrap length and hover time, with no restriction on each:
""" tk_ToolTip_class101.pygives a Tkinter widget a tooltip as the mouse is above the widgettested with Python27 and Python34 by vegaseat 09sep2014www.daniweb.com/programming/software-development/code/484591/a-tooltip-class-for-tkinterModified to include a delay time by Victor Zaccardo, 25mar16"""try: # for Python2 import Tkinter as tkexcept ImportError: # for Python3 import tkinter as tkclass CreateToolTip(object): """ create a tooltip for a given widget """ def __init__(self, widget, text='widget info'): self.waittime = 500 #miliseconds self.wraplength = 180 #pixels self.widget = widget self.text = text self.widget.bind("<Enter>", self.enter) self.widget.bind("<Leave>", self.leave) self.widget.bind("<ButtonPress>", self.leave) self.id = None self.tw = None def enter(self, event=None): self.schedule() def leave(self, event=None): self.unschedule() self.hidetip() def schedule(self): self.unschedule() self.id = self.widget.after(self.waittime, self.showtip) def unschedule(self): id = self.id self.id = None if id: self.widget.after_cancel(id) def showtip(self, event=None): x = y = 0 x, y, cx, cy = self.widget.bbox("insert") x += self.widget.winfo_rootx() + 25 y += self.widget.winfo_rooty() + 20 # creates a toplevel window self.tw = tk.Toplevel(self.widget) # Leaves only the label and removes the app window self.tw.wm_overrideredirect(True) self.tw.wm_geometry("+%d+%d" % (x, y)) label = tk.Label(self.tw, text=self.text, justify='left', background="#ffffff", relief='solid', borderwidth=1, wraplength = self.wraplength) label.pack(ipadx=1) def hidetip(self): tw = self.tw self.tw= None if tw: tw.destroy()# testing ...if __name__ == '__main__': root = tk.Tk() btn1 = tk.Button(root, text="button 1") btn1.pack(padx=10, pady=5) button1_ttp = CreateToolTip(btn1, \ 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, ' 'consectetur, adipisci velit. Neque porro quisquam est qui dolorem ipsum ' 'quia dolor sit amet, consectetur, adipisci velit. Neque porro quisquam ' 'est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.') btn2 = tk.Button(root, text="button 2") btn2.pack(padx=10, pady=5) button2_ttp = CreateToolTip(btn2, \ "First thing's first, I'm the realest. Drop this and let the whole world " "feel it. And I'm still in the Murda Bizness. I could hold you down, like " "I'm givin' lessons in physics. You should want a bad Vic like this.") root.mainloop()
Screenshot:
The Pmw.Balloon class from the Pmw toolkit for Tkinter will draw tool tips.
Also take a look at this blog post, which adapts some code from IDLE used for displaying tool tips with Tkinter.
First of all, I really like Alberto Vassena's tool tip and I tried to comment on his post with this bug correction, but as a new user I do not have enough points to make a comment, so I am making an answer. I hope this is acceptable.
There was a very small bug in Alberto Vassena's excellent answer and improved ToolTip.
Bug:For the actual label his code calls ttk.Label instead of tk.LabelThis resulted in the tooltip box being rendered but not the actual text until a further UI event such as another mouse move or a keyboard event.
Here is the corrected code for a full copy & paste:
import tkinter as tkimport tkinter.ttk as ttkclass Tooltip: ''' It creates a tooltip for a given widget as the mouse goes on it. see: http://stackoverflow.com/questions/3221956/ what-is-the-simplest-way-to-make-tooltips- in-tkinter/36221216#36221216 http://www.daniweb.com/programming/software-development/ code/484591/a-tooltip-class-for-tkinter - Originally written by vegaseat on 2014.09.09. - Modified to include a delay time by Victor Zaccardo on 2016.03.25. - Modified - to correct extreme right and extreme bottom behavior, - to stay inside the screen whenever the tooltip might go out on the top but still the screen is higher than the tooltip, - to use the more flexible mouse positioning, - to add customizable background color, padding, waittime and wraplength on creation by Alberto Vassena on 2016.11.05. Tested on Ubuntu 16.04/16.10, running Python 3.5.2 TODO: themes styles support ''' def __init__(self, widget, *, bg='#FFFFEA', pad=(5, 3, 5, 3), text='widget info', waittime=400, wraplength=250): self.waittime = waittime # in miliseconds, originally 500 self.wraplength = wraplength # in pixels, originally 180 self.widget = widget self.text = text self.widget.bind("<Enter>", self.onEnter) self.widget.bind("<Leave>", self.onLeave) self.widget.bind("<ButtonPress>", self.onLeave) self.bg = bg self.pad = pad self.id = None self.tw = None def onEnter(self, event=None): self.schedule() def onLeave(self, event=None): self.unschedule() self.hide() def schedule(self): self.unschedule() self.id = self.widget.after(self.waittime, self.show) def unschedule(self): id_ = self.id self.id = None if id_: self.widget.after_cancel(id_) def show(self): def tip_pos_calculator(widget, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)): w = widget s_width, s_height = w.winfo_screenwidth(), w.winfo_screenheight() width, height = (pad[0] + label.winfo_reqwidth() + pad[2], pad[1] + label.winfo_reqheight() + pad[3]) mouse_x, mouse_y = w.winfo_pointerxy() x1, y1 = mouse_x + tip_delta[0], mouse_y + tip_delta[1] x2, y2 = x1 + width, y1 + height x_delta = x2 - s_width if x_delta < 0: x_delta = 0 y_delta = y2 - s_height if y_delta < 0: y_delta = 0 offscreen = (x_delta, y_delta) != (0, 0) if offscreen: if x_delta: x1 = mouse_x - tip_delta[0] - width if y_delta: y1 = mouse_y - tip_delta[1] - height offscreen_again = y1 < 0 # out on the top if offscreen_again: # No further checks will be done. # TIP: # A further mod might automagically augment the # wraplength when the tooltip is too high to be # kept inside the screen. y1 = 0 return x1, y1 bg = self.bg pad = self.pad widget = self.widget # creates a toplevel window self.tw = tk.Toplevel(widget) # Leaves only the label and removes the app window self.tw.wm_overrideredirect(True) win = tk.Frame(self.tw, background=bg, borderwidth=0) label = tk.Label(win, text=self.text, justify=tk.LEFT, background=bg, relief=tk.SOLID, borderwidth=0, wraplength=self.wraplength) label.grid(padx=(pad[0], pad[2]), pady=(pad[1], pad[3]), sticky=tk.NSEW) win.grid() x, y = tip_pos_calculator(widget, label) self.tw.wm_geometry("+%d+%d" % (x, y)) def hide(self): tw = self.tw if tw: tw.destroy() self.tw = Noneif __name__ == '__main__': import random def further_text(): # texts generated at http://lorem-ipsum.perbang.dk/ short_text = ('Lorem ipsum dolor sit amet, mauris tellus, ' 'porttitor torquent eu. Magna aliquet lorem, ' 'cursus sit ac, in in. Dolor aliquet, cum integer. ' 'Proin aliquet, porttitor pulvinar mauris. Tellus ' 'lectus, amet cras, neque lacus quis. Malesuada ' 'nibh. Eleifend nam, in eget a. Nec turpis, erat ' 'wisi semper') medium_text = ('Lorem ipsum dolor sit amet, suspendisse aenean ' 'ipsum sollicitudin, pellentesque nunc ultrices ac ' 'ut, arcu elit turpis senectus convallis. Ac orci ' 'pretium sed gravida, tortor nulla felis ' 'consectetuer, mauris egestas est erat. Ut enim ' 'tellus at diam, ac sagittis vel proin. Massa ' 'eleifend orci tortor sociis, scelerisque in pede ' 'metus phasellus, est tempor gravida nam, ante ' 'fusce sem tempor. Mi diam auctor vel pede, mus ' 'non mi luctus luctus, lectus sit varius repellat ' 'eu') long_text = ('Lorem ipsum dolor sit amet, velit eu nam cursus ' 'quisque gravida sollicitudin, felis arcu interdum ' 'error quam quis massa, et velit libero ligula est ' 'donec. Suspendisse fringilla urna ridiculus dui ' 'volutpat justo, quisque nisl eget sed blandit ' 'egestas, libero nullam magna sem dui nam, auctor ' 'vehicula nunc arcu vel sed dictum, tincidunt vitae ' 'id tristique aptent platea. Lacus eros nec proin ' 'morbi sollicitudin integer, montes suspendisse ' 'augue lorem iaculis sed, viverra sed interdum eget ' 'ut at pulvinar, turpis vivamus ac pharetra nulla ' 'maecenas ut. Consequat dui condimentum lectus nulla ' 'vitae, nam consequat fusce ac facilisis eget orci, ' 'cras enim donec aenean sed dolor aliquam, elit ' 'lorem in a nec fringilla, malesuada curabitur diam ' 'nonummy nisl nibh ipsum. In odio nunc nec porttitor ' 'ipsum, nunc ridiculus platea wisi turpis praesent ' 'vestibulum, suspendisse hendrerit amet quis vivamus ' 'adipiscing elit, ut dolor nec nonummy mauris nec ' 'libero, ad rutrum id tristique facilisis sed ' 'ultrices. Convallis velit posuere mauris lectus sit ' 'turpis, lobortis volutpat et placerat leo ' 'malesuada, vulputate id maecenas at a volutpat ' 'vulputate, est augue nec proin ipsum pellentesque ' 'fringilla. Mattis feugiat metus ultricies repellat ' 'dictum, suspendisse erat rhoncus ultricies in ipsum, ' 'nulla ante pellentesque blandit ligula sagittis ' 'ultricies, sed tortor sodales pede et duis platea') text = random.choice([short_text, medium_text, long_text, long_text]) return '\nFurther info: ' + text def main_01(wraplength=200): # alias stuff = further_text root = tk.Tk() frame = ttk.Frame(root) btn_ne = ttk.Button(frame, text='North East') btn_se = ttk.Button(frame, text='South East') btn_sw = ttk.Button(frame, text='South West') btn_nw = ttk.Button(frame, text='North West') btn_center = ttk.Button(frame, text='Center') btn_n = ttk.Button(frame, text='North') btn_e = ttk.Button(frame, text='East') btn_s = ttk.Button(frame, text='South') btn_w = ttk.Button(frame, text='West') Tooltip(btn_nw, text='North West' + stuff(), wraplength=wraplength) Tooltip(btn_ne, text='North East' + stuff(), wraplength=wraplength) Tooltip(btn_se, text='South East' + stuff(), wraplength=wraplength) Tooltip(btn_sw, text='South West' + stuff(), wraplength=wraplength) Tooltip(btn_center, text='Center' + stuff(), wraplength=wraplength) Tooltip(btn_n, text='North' + stuff(), wraplength=wraplength) Tooltip(btn_e, text='East' + stuff(), wraplength=wraplength) Tooltip(btn_s, text='South' + stuff(), wraplength=wraplength) Tooltip(btn_w, text='West' + stuff(), wraplength=wraplength) r = 0 c = 0 pad = 10 btn_nw.grid(row=r, column=c, padx=pad, pady=pad, sticky=tk.NW) btn_n.grid(row=r, column=c + 1, padx=pad, pady=pad, sticky=tk.N) btn_ne.grid(row=r, column=c + 2, padx=pad, pady=pad, sticky=tk.NE) r += 1 btn_w.grid(row=r, column=c + 0, padx=pad, pady=pad, sticky=tk.W) btn_center.grid(row=r, column=c + 1, padx=pad, pady=pad, sticky=tk.NSEW) btn_e.grid(row=r, column=c + 2, padx=pad, pady=pad, sticky=tk.E) r += 1 btn_sw.grid(row=r, column=c, padx=pad, pady=pad, sticky=tk.SW) btn_s.grid(row=r, column=c + 1, padx=pad, pady=pad, sticky=tk.S) btn_se.grid(row=r, column=c + 2, padx=pad, pady=pad, sticky=tk.SE) frame.grid(sticky=tk.NSEW) for i in (0, 2): frame.rowconfigure(i, weight=1) frame.columnconfigure(i, weight=1) root.rowconfigure(0, weight=1) root.columnconfigure(0, weight=1) root.title('Tooltip wraplength = {}'.format(wraplength)) root.mainloop() def main(): print('Trying out three different wraplengths:') for i, wl in enumerate((200, 250, 400), 1): print(' ', i) main_01(wl) print('Done.') main()