【问题标题】:python tkinter tooltip on modal window模态窗口上的python tkinter工具提示
【发布时间】:2021-04-24 09:13:05
【问题描述】:

我搜索了为应用程序实现工具提示的方法,不久前我在本网站的一些评论或答案中找到了一个链接 to this page
从那以后我一直在使用这个类,我对结果很满意。
但最近我注意到工具提示出现在模态窗口后面,当它们引用该模态窗口上的小部件时。

在从该 GitHub 链接下载的代码下方,我刚刚将from tkinter import * 替换为import tkinter as tk,并在整个代码中相应地使用前缀tk

"""Tools for displaying tool-tips.
This includes:
 * an abstract base-class for different kinds of tooltips
 * a simple text-only Tooltip class
"""
import tkinter as tk


class TooltipBase:
    """abstract base class for tooltips"""

    def __init__(self, anchor_widget):
        """Create a tooltip.
        anchor_widget: the widget next to which the tooltip will be shown
        Note that a widget will only be shown when showtip() is called.
        """
        self.anchor_widget = anchor_widget
        self.tipwindow = None

    def __del__(self):
        self.hidetip()

    def showtip(self):
        """display the tooltip"""
        if self.tipwindow:
            return
        self.tipwindow = tw = tk.Toplevel(self.anchor_widget)
        # show no border on the top level window
        tw.wm_overrideredirect(1)
        try:
            # This command is only needed and available on Tk >= 8.4.0 for OSX.
            # Without it, call tips intrude on the typing process by grabbing
            # the focus.
            tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w,
                       "help", "noActivates")
        except tk.TclError:
            pass

        self.position_window()
        self.showcontents()
        self.tipwindow.update_idletasks()  # Needed on MacOS -- see #34275.
        self.tipwindow.lift()  # work around bug in Tk 8.5.18+ (issue #24570)

    def position_window(self):
        """(re)-set the tooltip's screen position"""
        x, y = self.get_position()
        root_x = self.anchor_widget.winfo_rootx() + x
        root_y = self.anchor_widget.winfo_rooty() + y
        self.tipwindow.wm_geometry("+%d+%d" % (root_x, root_y))

    def get_position(self):
        """choose a screen position for the tooltip"""
        # The tip window must be completely outside the anchor widget;
        # 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 :-(
        #
        # Note: This is a simplistic implementation; sub-classes will likely
        # want to override this.
        return 20, self.anchor_widget.winfo_height() + 1

    def showcontents(self):
        """content display hook for sub-classes"""
        # See ToolTip for an example
        raise NotImplementedError

    def hidetip(self):
        """hide the tooltip"""
        # Note: This is called by __del__, so careful when overriding/extending
        tw = self.tipwindow
        self.tipwindow = None
        if tw:
            try:
                tw.destroy()
            except tk.TclError:  # pragma: no cover
                pass


class OnHoverTooltipBase(TooltipBase):
    """abstract base class for tooltips, with delayed on-hover display"""

    def __init__(self, anchor_widget, hover_delay=1000):
        """Create a tooltip with a mouse hover delay.
        anchor_widget: the widget next to which the tooltip will be shown
        hover_delay: time to delay before showing the tooltip, in milliseconds
        Note that a widget will only be shown when showtip() is called,
        e.g. after hovering over the anchor widget with the mouse for enough
        time.
        """
        super(OnHoverTooltipBase, self).__init__(anchor_widget)
        self.hover_delay = hover_delay

        self._after_id = None
        self._id1 = self.anchor_widget.bind("<Enter>", self._show_event)
        self._id2 = self.anchor_widget.bind("<Leave>", self._hide_event)
        self._id3 = self.anchor_widget.bind("<Button>", self._hide_event)

    def __del__(self):
        try:
            self.anchor_widget.unbind("<Enter>", self._id1)
            self.anchor_widget.unbind("<Leave>", self._id2)  # pragma: no cover
            self.anchor_widget.unbind("<Button>", self._id3)  # pragma: no cover
        except tk.TclError:  # pragma: no cover
            pass
        super(OnHoverTooltipBase, self).__del__()

    def _show_event(self, event=None):
        """event handler to display the tooltip"""
        if self.hover_delay:
            self.schedule()
        else:
            self.showtip()

    def _hide_event(self, event=None):
        """event handler to hide the tooltip"""
        self.hidetip()

    def schedule(self):
        """schedule the future display of the tooltip"""
        self.unschedule()
        self._after_id = self.anchor_widget.after(self.hover_delay,
                                                  self.showtip)

    def unschedule(self):
        """cancel the future display of the tooltip"""
        after_id = self._after_id
        self._after_id = None
        if after_id:
            self.anchor_widget.after_cancel(after_id)

    def hidetip(self):
        """hide the tooltip"""
        try:
            self.unschedule()
        except tk.TclError:  # pragma: no cover
            pass
        super(OnHoverTooltipBase, self).hidetip()

    def showcontents(self):
        """content display hook for sub-classes"""
        # See ToolTip for an example
        raise NotImplementedError


class Hovertip(OnHoverTooltipBase):
    """A tooltip that pops up when a mouse hovers over an anchor widget."""
    def __init__(self, anchor_widget, text, hover_delay=1000):
        """Create a text tooltip with a mouse hover delay.
        anchor_widget: the widget next to which the tooltip will be shown
        hover_delay: time to delay before showing the tooltip, in milliseconds
        Note that a widget will only be shown when showtip() is called,
        e.g. after hovering over the anchor widget with the mouse for enough
        time.
        """
        super(Hovertip, self).__init__(anchor_widget, hover_delay=hover_delay)
        self.text = text

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


现在一些代码说明了我遇到的问题:


class PopupWindow:
    def __init__(self, parent):
        self.parent = parent
        self.gui = tk.Toplevel(self.parent)
        self.gui.geometry("100x30")
        self.gui.wait_visibility()
        self.ok_button = tk.Button(self.gui, text="OK", command=self.on_ok_button)
        self.ok_button.pack()
        Hovertip(self.ok_button, text="OK button", hover_delay=500)

    def on_ok_button(self):
        self.gui.destroy()

    def show(self):
        self.gui.grab_set()
        # Hovertip(self.ok_button, text="OK button", hover_delay=500)
        self.gui.wait_window()
        return 0


class App(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        button = tk.Button(parent, text="Button -- no hover delay", command=self.button)
        button.pack()
        Hovertip(button, "This is tooltip\ntext for button.", hover_delay=0)

    def button(self):
        window = PopupWindow(self.parent)
        window.show()


if __name__ == '__main__':
    root = tk.Tk()
    App(root)
    root.mainloop()

您会注意到模态窗口中“确定”按钮的工具提示出现在窗口后面(我正在更改窗口的几何形状,否则它会太小以至于我们实际上不会注意到这一点)。 当然,这在具有多个小部件的真实窗口中会成为一个问题,其中一些小部件的提示将根本看不到。

显然有两种方法可以解决这个问题:一种是删除PopupWindow类的__init__中的self.gui.wait_visibility()行; 另一种是删除show()方法中的self.gui.grab_set()
使用这些中的任何一个,窗口不再是模态的(如果我的意思正确:我的意思是我希望窗口保持在顶部并防止在父窗口存在时对其进行更改)。

show 方法中的注释行是我尝试通过在 grab_set 之后定义工具提示来解决它,以便它可能会出现在顶部,但它也不起作用。

我想必须有一种方法可以正确地使用此类作为工具提示。
我该怎么做?

谢谢。

【问题讨论】:

  • 您使用的是 Windows 还是 Linux?
  • @TheLizzard 这是 Mac。顺便说一句,我刚刚尝试过 Pmw,我也遇到了同样的问题。
  • 在 Windows 10 上运行它时找不到任何问题。如果我正确理解您的问题,您可能需要致电 &lt;pop up window&gt;.attributes("-topmost", True),以便它始终位于其他窗口的顶部。
  • @TheLizzard 但是弹出窗口确实与上面的代码保持一致。问题是该窗口上的小部件的工具提示出现在窗口后面,而它们应该出现在窗口顶部
  • 所以你是说Hovertip窗口在主窗口后面?

标签: python tkinter modal-dialog tooltip


【解决方案1】:

我找到了解决办法。
显然,有人遇到了同样的问题,尽管工具提示的类不同。
我指的是this question and answer

解决方案是在TooltipBase 类的showtip 方法中的某处添加行tw.wm_attributes("-topmost", 1)
我把它作为最后一行,但我不确定它在其他地方是否有效;我知道如果紧跟在tw.wm_overrideredirect(1) 之后不会。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-31
    • 2020-03-28
    • 2023-03-18
    • 2017-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多