python 模态上的 tkinter 工具提示 window
python tkinter tooltip on modal window
我搜索了为应用程序实现工具提示的方法,不久前我在该站点的一些评论或答案中找到了 link to this page.
从那时起我就一直在使用这个 class,我对结果很满意。
但最近我注意到工具提示出现在模态 windows 后面,当它们引用该模态 window.
上的小部件时
在从 GitHub link 下载的代码下方,我刚刚将 from tkinter import *
替换为 import tkinter as tk
,并使用前缀 [=16] =] 相应地在整个代码中。
"""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()
您会注意到模态 window 中“确定”按钮的工具提示出现在 window 后面(我正在更改 window 的几何形状,否则它会太小了,我们实际上不会注意到这一点)。
当然,这在具有多个小部件的真实 window 中会成为一个问题,其中一些小部件的提示根本不会被看到。
显然有两种方法可以解决这个问题:一种是删除 PopupWindow
class 的 __init__
中的行 self.gui.wait_visibility()
;
另一种是删除show()
方法中的self.gui.grab_set()
。
对于这些中的任何一个, window 不再是模态的(如果我的意思正确:我的意思是我希望 window 保持在顶部并防止父 window 在它存在时发生变化).
show
方法中的注释行是我尝试通过在 grab_set
之后定义工具提示来解决它,以便它可能出现在顶部,但它也不起作用。
我想一定有一种方法可以正确地使用这个 class 作为工具提示。
我该怎么做?
谢谢。
我找到了解决方案。
显然,有人遇到了同样的问题,尽管工具提示的 class 不同。
我指的是 this question and answer.
解决方案是在 TooltipBase
class.
的 showtip
方法中的某处添加行 tw.wm_attributes("-topmost", 1)
我把它作为最后一行来做,但我不确定它在其他地方是否行得通;我知道如果紧跟在 tw.wm_overrideredirect(1)
.
之后就不会了
我搜索了为应用程序实现工具提示的方法,不久前我在该站点的一些评论或答案中找到了 link to this page.
从那时起我就一直在使用这个 class,我对结果很满意。
但最近我注意到工具提示出现在模态 windows 后面,当它们引用该模态 window.
在从 GitHub link 下载的代码下方,我刚刚将 from tkinter import *
替换为 import tkinter as tk
,并使用前缀 [=16] =] 相应地在整个代码中。
"""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()
您会注意到模态 window 中“确定”按钮的工具提示出现在 window 后面(我正在更改 window 的几何形状,否则它会太小了,我们实际上不会注意到这一点)。
当然,这在具有多个小部件的真实 window 中会成为一个问题,其中一些小部件的提示根本不会被看到。
显然有两种方法可以解决这个问题:一种是删除 PopupWindow
class 的 __init__
中的行 self.gui.wait_visibility()
;
另一种是删除show()
方法中的self.gui.grab_set()
。
对于这些中的任何一个, window 不再是模态的(如果我的意思正确:我的意思是我希望 window 保持在顶部并防止父 window 在它存在时发生变化).
show
方法中的注释行是我尝试通过在 grab_set
之后定义工具提示来解决它,以便它可能出现在顶部,但它也不起作用。
我想一定有一种方法可以正确地使用这个 class 作为工具提示。
我该怎么做?
谢谢。
我找到了解决方案。
显然,有人遇到了同样的问题,尽管工具提示的 class 不同。
我指的是 this question and answer.
解决方案是在 TooltipBase
class.
的 showtip
方法中的某处添加行 tw.wm_attributes("-topmost", 1)
我把它作为最后一行来做,但我不确定它在其他地方是否行得通;我知道如果紧跟在 tw.wm_overrideredirect(1)
.