Hovertip/Tooltip 对于 Python ttk 组合框中的每个项目

Hovertip/Tooltip for each item in Python ttk combobox

这是关于 ttk 组合框的。 我需要通过工具提示为 ttk.combobox 中的每个项目(文本)显示帮助信息。

最小示例如下:

import tkinter as tk
from tkinter import ttk
from idlelib.tooltip import Hovertip
names =["One", "Two", "Three"]
root = tk.Tk()
root.title("Combo Test GUI")
Frame1 = ttk.Frame(root, padding="3 3 12 12")
Frame1.grid(column= 0, row = 0)
label_1 = ttk.Label(Frame1, text="Select Item:", anchor=tk.W)
label_1.grid(column= 0, row = 1)
item1 = ttk.Combobox(Frame1, state="readonly", values=names, width=35)
item1.grid(column= 0, row = 2)
m1 = Hovertip(item1, "One test")
m2 = Hovertip(label_1, "Another test")
root.mainloop()

问题:我想将 Hovertip 绑定到组合列表中的文本,即“一”、“二”、“三”。

当用户将鼠标悬停在“一”上时,Hovertip/Tooltip 中应显示“这是选项一”的描述;其他项目“二”和“三”也一样。

我到处搜索,在 Whosebug 上,在参考文档中,其他网站上,但似乎 Hovertip/Tooltip 只能与小部件一起使用,而不能与组合框中的简单文本一起使用。

有没有办法让它成为可能?

我们的想法是制作一个类似于 Hovertip 的 class,但它适用于列表框项目而不是小部件。当前项目存储在 self._current_item 中,当鼠标在列表框中移动时,如果项目发生变化,工具提示的显示将重新安排。每个项目的文本存储在字典 self.tips = {item index: text, ...}.

主要问题是 Listbox 显示 Combobox 的选择不能直接通过 python 访问。所以我不得不使用一些 Tcl 命令来完成它:

proc callback {y} {
     event generate .!combobox <<OnMotion>> -y $y
}

set popdown [ttk::combobox::PopdownWindow .!combobox]
bind $popdown.f.l <Motion> {callback %y}

上面的代码在鼠标移动时在组合框中生成一个虚拟事件<<OnMotion>>(其中.!comboboxCombobox小部件的名称,y是小部件中的鼠标相对 y 坐标)。

然后我将组合框的 _on_motion() 方法绑定到此 <<OnMotion>> 事件以检查当前项目是否已更改。要获取当前项,我使用 Listbox 方法 nearest(y) 但来自 Tcl.

我还修改了 get_position() 方法以在当前项目下方显示工具提示,并修改 showcontents() 以显示与项目对应的文本。

完整代码如下:

import tkinter as tk
from tkinter import ttk
from idlelib.tooltip import OnHoverTooltipBase

class ComboboxTip(OnHoverTooltipBase):
    def __init__(self, combobox_widget, hover_delay=1000):
        super(ComboboxTip, self).__init__(combobox_widget, hover_delay=hover_delay)
        self.tips = {}
        self._current_item = 0

        combobox_widget.tk.eval("""
proc callback {y} {
     event generate %(cb)s <<OnMotion>> -y $y
}

set popdown [ttk::combobox::PopdownWindow %(cb)s]
bind $popdown.f.l <Motion> {callback %%y}
""" % ({"cb": combobox_widget}))

        self._id4 = combobox_widget.bind("<<OnMotion>>", self._on_motion)

    def _on_motion(self, event):
        current_item = int(self.anchor_widget.tk.eval("$popdown.f.l nearest %i" % event.y))
        if current_item != self._current_item:
            self._current_item = current_item
            self.hidetip()
            if current_item in self.tips:
                self.schedule()
            else:
                self.unschedule()

    def __del__(self):
        try:
            self.anchor_widget.unbind("<<OnMotion>>", self._id4)
        except tk.TclError:
            pass
        super(ComboboxTip, self).__del__()

    def add_tooltip(self, index, text):
        self.tips[index] = text

    def get_position(self):
        """choose a screen position for the tooltip"""
        try:
            h = self.anchor_widget.winfo_height()
            bbox = self.anchor_widget._getints(self.anchor_widget.tk.eval("$popdown.f.l bbox %i" % self._current_item))
            return bbox[0] + bbox[2], bbox[1] + bbox[-1] + h
        except Exception:
            return 20, self.anchor_widget.winfo_height() + 1

    def showcontents(self):
        label = tk.Label(self.tipwindow, text=self.tips[self._current_item], justify=tk.LEFT,
                         background="#ffffe0", relief=tk.SOLID, borderwidth=1)
        label.pack()


names = ["One", "Two", "Three"]
root = tk.Tk()
cb = ttk.Combobox(root, values=names)
cb.pack()

t = ComboboxTip(cb)
t.add_tooltip(0, "This is One")
t.add_tooltip(1, "This is Two")
t.add_tooltip(2, "This is Three")

root.mainloop()