如何在 Tkinter 中显示工具提示?

2025-03-05 09:14:00
admin
原创
65
摘要:问题描述:工具提示是当鼠标悬停在小部件上一定时间时弹出的小段文本。如何向我的tkinterPython 应用程序添加工具提示消息?解决方案 1:我尝试了ars提到的博客文章中的代码,也尝试了IDLE lib中的代码。虽然两种方法都可以,但我不喜欢 IDLE 的工具提示大小受限(必须手动输入新行作为单独的列表)...

问题描述:

工具提示是当鼠标悬停在小部件上一定时间时弹出的小段文本。

如何向我的tkinterPython 应用程序添加工具提示消息?

工具提示示例


解决方案 1:

我尝试了ars提到的博客文章中的代码,也尝试了IDLE lib中的代码。

虽然两种方法都可以,但我不喜欢 IDLE 的工具提示大小受限(必须手动输入新行作为单独的列表),以及提示如何立即出现在博客文章的代码中。

所以我把两者结合起来。它允许您指定包裹长度和悬停时间,而对每个都没有任何限制:

""" tk_ToolTip_class101.py
gives a Tkinter widget a tooltip as the mouse is above the widget
tested with Python27 and Python34  by  vegaseat  09sep2014
www.daniweb.com/programming/software-development/code/484591/a-tooltip-class-for-tkinter

Modified to include a delay time by Victor Zaccardo, 25mar16
"""

try:
    # for Python2
    import Tkinter as tk
except ImportError:
    # for Python3
    import tkinter as tk

class 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, \n   '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, \n    "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()

截屏:

悬停文本示例

解决方案 2:

Python 3.7+ 中的一个简单解决方案

图片

import tkinter as tk
from idlelib.tooltip import Hovertip
    
app = tk.Tk()
myBtn = tk.Button(app,text='?')
myBtn.pack(pady=30)
myTip = Hovertip(myBtn,'This is 
a multiline tooltip.')
app.mainloop()

解决方案 3:

Tkinter 的Pmw 工具包中的Pmw.Balloon类将绘制工具提示。

还请查看这篇博客文章,它改编了 IDLE 中的一些代码,用于使用 Tkinter 显示工具提示。

解决方案 4:

首先,我非常喜欢Alberto Vassena 的工具提示,并尝试在他的帖子中评论此错误更正,但作为新用户,我没有足够的积分来发表评论,所以我正在做出回答。我希望这是可以接受的。

Alberto Vassena 的出色回答和改进的工具提示中有一个非常小的错误。

错误:对于实际的标签,他的代码调用的是 ttk.Label 而不是 tk.Label,这导致工具提示框被呈现,但是实际文本不会被呈现,直到发生进一步的 UI 事件(例如另一次鼠标移动或键盘事件)。

以下是完整复制和粘贴的更正代码:

import tkinter as tk
import tkinter.ttk as ttk


class 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 = None


if __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 '
Further 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()

解决方案 5:

我不建议使用Tix小部件,因为Tix它基本上不再受支持并且通常会导致很多问题。

以下是直接从 Python 模块中获取的工具提示示例idlelib

# general purpose 'tooltip' routines - currently unused in idlefork
# (although the 'calltips' extension is partly based on this code)
# may be useful for some purposes in (or almost in ;) the current project scope
# Ideas gleaned from PySol

from tkinter import *


class ToolTipBase:

    def __init__(self, button):
        self.button = button
        self.tipwindow = None
        self.id = None
        self.x = self.y = 0
        self._id1 = self.button.bind("<Enter>", self.enter)
        self._id2 = self.button.bind("<Leave>", self.leave)
        self._id3 = self.button.bind("<ButtonPress>", self.leave)

    def enter(self, event=None):
        self.schedule()

    def leave(self, event=None):
        self.unschedule()
        self.hidetip()

    def schedule(self):
        self.unschedule()
        self.id = self.button.after(1500, self.showtip)

    def unschedule(self):
        id = self.id
        self.id = None
        if id:
            self.button.after_cancel(id)

    def showtip(self):
        if self.tipwindow:
            return
        # The tip window must be completely outside the button;
        # otherwise when the mouse enters the tip window we get
        # a leave event and it disappears, and then we get an enter
        # event and it reappears, and so on forever :-(
        x = self.button.winfo_rootx() + 20
        y = self.button.winfo_rooty() + self.button.winfo_height() + 1
        self.tipwindow = tw = Toplevel(self.button)
        tw.wm_overrideredirect(1)
        tw.wm_geometry("+%d+%d" % (x, y))
        self.showcontents()

    def showcontents(self, text="Your text here"):
        # Override this in derived class
        label = Label(self.tipwindow, text=text, justify=LEFT,
                      background="#ffffe0", relief=SOLID, borderwidth=1)
        label.pack()

    def hidetip(self):
        tw = self.tipwindow
        self.tipwindow = None
        if tw:
            tw.destroy()


class ToolTip(ToolTipBase):

    def __init__(self, button, text):
        ToolTipBase.__init__(self, button)
        self.text = text

    def showcontents(self):
        ToolTipBase.showcontents(self, self.text)


class ListboxToolTip(ToolTipBase):

    def __init__(self, button, items):
        ToolTipBase.__init__(self, button)
        self.items = items

    def showcontents(self):
        listbox = Listbox(self.tipwindow, background="#ffffe0")
        listbox.pack()
        for item in self.items:
            listbox.insert(END, item)

您也可以直接导入模块并使用它:

from idlelib.tooltip import * ##corrected wrong import

def main():
    root = Tk()
    b = Button(root, text="Hello", command=root.destroy)
    b.pack()
    root.update()
    tip = ListboxToolTip(b, ["Hello", "world"])
    root.mainloop()

if __name__ == '__main__':
    main()

我使用的是Python 3.4,其他Python发行版可能不包含此ToolTip模块。

解决方案 6:

由于您使用的是 Windows 7,您的 Python 安装很可能已经包含Tix。使用 Tix.Balloon 小部件。示例代码存在于 Python 源代码树中。

基本上,您创建一个Tix.Balloon小部件,通过其方法将其绑定到其他小部件.bind_widget,并使用其参数提供气球消息balloonmsg

解决方案 7:

我修改了crxguy52建议的Tooltip类。下面的类现在几乎可以在任何情况下工作,无论您需要在哪里实例化它: NWNNEESESSWW

我的课程目前无法管理的唯一情况是工具提示高于整个屏幕(可能非常罕见,但通过手动传递更大的包裹长度来加宽它也可以立即解决这种情况)。

import tkinter as tk
import tkinter.ttk as ttk


class Tooltip:
    '''
    It creates a tooltip for a given widget as the mouse goes on it.

    see:

    https://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 = ttk.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 = None


if __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 '
Further 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()

HTH。我在这里发布了一个CanvasTooltip类,它允许将工具提示绑定到在 tkinter Canvas 内创建的项目。

解决方案 8:

尽管答案有多个,但我还是想发表一下我的看法。

class ToolTip:
    def __init__(self,widget,text=None):

        def on_enter(event):
            self.tooltip=tk.Toplevel()
            self.tooltip.overrideredirect(True)
            self.tooltip.geometry(f'+{event.x_root+15}+{event.y_root+10}')

            self.label=tk.Label(self.tooltip,text=self.text)
            self.label.pack()

        def on_leave(event):
            self.tooltip.destroy()

        self.widget=widget
        self.text=text

        self.widget.bind('<Enter>',on_enter)
        self.widget.bind('<Leave>',on_leave)

这是一个相对于光标位置显示的基本工具提示。

解决方案 9:

from tkinter import *
from tkinter.tix import *
root = Tk()
btn1 = Button(root, text="hello")
btn1.grid(row=0, column=0)
balloon = Balloon(root, bg="white", title="Help")
balloon.bind_widget(btn1, balloonmsg="Click to Exit")
root.mainloop()

解决方案 10:

这一系列答案对我非常有用,我想分享我对这个社区工作的改进。在我的用例中,几个嵌套的小部件可以有工具提示。因此,以前的实现让所有工具提示同时出现,堆叠它们并使其笨拙且无法阅读。我修改后的工具提示使用一个通用信号量来同步所有工具提示。(此外,我重新格式化了文档字符串以使其在 sphinx 渲染中更美观)

class Semaphore:
    "A semaphore letting several tooltips synchronising themself."

    def __init__(self):
        self.state = None
        self.held = False

    def request(self, ident):
        if self.held:
            return False

        self.state = ident
        return True

    def hold(self, ident):
        if self.held:
            return False

        if self.state is not ident:
            return False

        self.held = True
        return True

    def release(self, ident):
        if self.held and self.state is ident:
            self.held = False
            self.state = None


_default_sem = Semaphore()


class Tooltip:
    """Create a floating tooltip next to the attached widget.

    Credits:

    - Originally written by vegaseat on 2014.09.09 (`blog post`_).
    - Modified to include a delay time by Victor Zaccardo on 2016.03.25 (`stackoverflow answer 1`_).
    - Modified by Alberto Vassena on 2016.11.05 (`stackoverflow answer 2`_):

        - 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

    - Modified to fix a scheduling bug by Erik Bethke on 2016.12.29 (`stackoverflow answer 3`_).
    - Modified by Théo Cavignac to prevent more than one visible tooltip at
      a time, causing superpositions of tooltips in complex widget tree,
      on 2022.09.04 (in `tkgen sources`_).

    Tested on Archlinux (kernel 6.2.2), running Python 3.10.9

    .. _blog post: http://www.daniweb.com/programming/software-development/code/484591/a-tooltip-class-for-tkinter
    .. _stackoverflow answer 1: https://stackoverflow.com/a/36221216/6324751
    .. _stackoverflow answer 2: https://stackoverflow.com/a/41079350/6324751
    .. _stackoverflow answer 3: https://stackoverflow.com/a/41381685/6324751
    .. _tkgen sources: https://git.sr.ht/~lattay/python-tkgen/tree/bdb9ba3c1ee173d2765966cd23f5acdb6f07007f/item/tkform/tooltip.py#L36-197
    """

    def __init__(
        self,
        widget,
        *,
        bg="#FFFFEA",
        pad=(5, 3, 5, 3),
        text="widget info",
        waittime=200,
        wraplength=250,
        sem=_default_sem
    ):

        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
        self.ident = object()
        self.sem = sem

    def onEnter(self, event=None):
        self.schedule()

    def onLeave(self, event=None):
        self.unschedule()
        self.hide()

    def schedule(self):
        if self.sem.request(self.ident):
            self.unschedule()
            self.id = self.widget.after(self.waittime, self.show)

    def unschedule(self):
        self.sem.release(self.ident)
        id_ = self.id
        self.id = None
        if id_:
            self.widget.after_cancel(id_)

    def show(self):
        if not self.sem.hold(self.ident):
            return

        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 = ttk.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 = None

解决方案 11:

tkinter-tooltip已经很成熟,对我来说非常好用。我可以跳过所有冗长的 tkinter 代码,直接使用代码。

以下是他们的 Hello World:

import tkinter as tk
import tkinter.ttk as ttk
from tktooltip import ToolTip

app = tk.Tk()
b = ttk.Button(app, text="Button")
b.pack()
ToolTip(b, msg="Hover info")
app.mainloop()
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3970  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2740  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   79  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   87  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   74  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用