今日も見に来てくださって、ありがとうございます。石川さんです。
今回は、tkinterでツールチップを出力する方法を調べてみました。いい感じにできたのでまとめてみます。もともと、ホバーイベントって、どうやるのかなぁ、というのが発端でした。ホバーとは、マウスを同じところでじっとしていると発生するイベントです。tkinterのイベントの中にホバーは見当たらなかったので、たぶん、そんなイベントは存在しないのだと思います。
出来上がりイメージ

ソースコード
import tkinter as tk
class ToolTip():
def __init__(self, widget, text="default tooltip"):
self.widget = widget
self.text = text
self.widget.bind("<Enter>", self.enter)
self.widget.bind("<Motion>", self.motion)
self.widget.bind("<Leave>", self.leave)
self.id = None
self.tw = None
def enter(self, event):
self.schedule()
def motion(self, event):
self.unschedule()
self.schedule()
def leave(self, event):
self.unschedule()
self.id = self.widget.after(500, self.hideTooltip)
def schedule(self):
if self.tw:
return
self.unschedule()
self.id = self.widget.after(500, self.showTooltip)
def unschedule(self):
id = self.id
self.id = None
if id:
self.widget.after_cancel(id)
def showTooltip(self):
id = self.id
self.id = None
if id:
self.widget.after_cancel(id)
x, y = self.widget.winfo_pointerxy()
self.tw = tk.Toplevel(self.widget)
self.tw.wm_overrideredirect(True)
self.tw.geometry(f"+{x+10}+{y+10}")
label = tk.Label(self.tw, text=self.text, background="lightyellow",
relief="solid", borderwidth=1, justify="left")
label.pack(ipadx=10)
def hideTooltip(self):
tw = self.tw
self.tw = None
if tw:
tw.destroy()
if __name__ == "__main__":
root = tk.Tk()
root.title("Tooltip test")
root.geometry("400x100")
button = tk.Button(root,text="test button")
button.pack()
tooltip = ToolTip(button)
button2 = tk.Button(root, text="next button")
button2.pack()
tooltip_text = "ツールチップをセットすることができます。\n改行コードを入れることで複数行になります。"
tooltip2 = ToolTip(button2, tooltip_text)
root.mainloop()
詳細説明
今回は、汎用的に利用できる、ウィジェットではないToolTipクラスを作成してみました。利用方法は、61行目と65行目の通りです。それぞれ、ボタンのツールチップを設定しています。インスタンス作成時にウィジェットとツールチップに表示する文字列を渡します。
ポイントは、ウィジェットにマウスポインタが入った時の<Enter>イベントと出て行った時の<Leave>イベント、それとマウスポインタが入っている間に発生する<Motion>イベントでそれぞれ、ツールチップの表示をスケジュールする、ツールチップの消去をスケジュールする、動いている間はスケジュールをやり直す、というところでしょうか。
<Enter>イベントが発生したときにスケジュールするのに、28行目の「self.id = self.widget.after(500, self.showTooltip)」のafter()メソッドを使用しています。これで500ミリ秒後に、self.showTooltip()を実行してね、とスケジュールしています。これにより、イベント発生後に何もしなければ、ツールチップが表示されるようになります。
<Leave>イベントが発生したときは、既に表示されているはずのツールチップを消去する必要がありますので、ここでも500ミリ秒後に消去するメソッドを呼び出すようスケジュールしています。これが28行目の「self.id = self.widget.after(500, self.hideTooltip)」になります。
ツールチップ自体は、Toplevelを使って表示しています。43行目の「self.tw.wm_overrideredirect(True)」を呼び出すことによって、ウィンドウマネージャがこのウィジェットを無視するようにします。要するに、ウィンドウタイトルや最小化、最大化、ウィンドウを閉じるボタンなどをセットしないようになります。
表示位置を決めるために、41行目の「self.widget.winfo_pointerxy()」を利用しています。ポインタの位置にウィンドウを表示すると、表示したウィンドウにポイントすることになるので<Leave>イベントが発生し、これによりウィンドウが消去される、という循環が発生してしまうので、位置を10ピクセルずつ少しずらして表示するよう工夫しました。
まとめ
ホバーというイベントはなくても、ツールチップを出すことができました。これでマウスがじっとしているときに何かするプログラムが作れるようになりましたね。

