0

I'm adding tooltips to my program, they should appear if mouse is on the widget more than 1 seconds. But there is a problem that tooltip always appears when mouse simply passes over the widget. I mean they should appear if mouse remains on the widget more than 1 seconds but they also appear when mouse doesn't remains on the widget.

So I decided to add some lines to code that delays 1 seconds when mouse enters the widget and checks if mouse is still on the widget. If mouse is still there, then makes tooltip appear. But I don't know how to check if mouse is on a widget. I've tried widget.focus_get() but it simply gives a dot for output. Which is also gives a dot if mouse wasn't there. So this is useless for me. I've done lot's of research but I wasn't able to find something about this.

I am using Python 3.7.9 and tkinter.ttk

I was copied the code from here and modified a little bit to fade-in and fade-out animations. Here is my code:

label = Label(root, text="Label")

class ToolTip(object):
    def __init__(self, widget):
        self.widget = widget
        self.tipwindow = None
        self.id = None
        self.x = self.y = 0
    def showtip(self, text, widget):
        global tw
        self.text = text
        if self.tipwindow or not self.text:
            return
        x, y, cx, cy = self.widget.bbox("insert")
        x = x + self.widget.winfo_rootx() + 20
        y = y + cy + self.widget.winfo_rooty() +20
        self.tipwindow = tw = Toplevel(self.widget)
        tw.wm_overrideredirect(1)
        tw.wm_geometry("+%d+%d" % (x, y))
        tw.attributes("-alpha", 0)
        label = Label(tw, text=self.text, justify=LEFT, relief=SOLID, borderwidth=1)
        label.pack(ipadx=1)
        def fade_in():
            alpha = tw.attributes("-alpha")
            if alpha < root.attributes("-alpha"):
                alpha += .1
                tw.attributes("-alpha", alpha)
                tw.after(10, fade_in)
            elif alpha == root.attributes("-alpha"):
                tw.attributes("-alpha", root.attributes("-alpha"))
                try:
                    tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, "help", "noActivates")
                except TclError:
                    pass
        fade_in()
    def hidetip(self, widget):
        tw = self.tipwindow
        self.tipwindow = None
        def fade_away():
            alpha = tw.attributes("-alpha")
            if alpha > 0:
                alpha -= .1
                tw.attributes("-alpha", alpha)
                tw.after(10, fade_away)
            else:
                tw.destroy()
        fade_away()

def createToolTip(widget, text):
    toolTip = ToolTip(widget)
    def enter(event):
        time.sleep(1000) #Wait 1 second
        if widget.focus_get() == True: #Check if mouse is still on the widget. But not working.
            toolTip.showtip(text, widget)
    def leave(event):
        ToolTipActive = False
        toolTip.hidetip(widget)
    widget.bind('<Enter>', enter)
    widget.bind('<Leave>', leave)

createToolTip(label, "This is a tooltip")
Yılmaz Alpaslan
  • 327
  • 3
  • 16
  • 1
    Why do you have `time.sleep(1)` inside a function that will be called by tkinter? Also you need to change `root.after(1, toolTip.showtip(text, widget))` to `root.after(1, toolTip.showtip, text, widget)` – TheLizzard May 26 '21 at 09:24
  • @TheLizzard as there, it should delay 1 second and then should check if mouse is still on widget. If it is, it should call `root.after(1, toolTip.showtip, text, widget)`. But it isn't complete as I don't know how to check if mouse is on the widget. – Yılmaz Alpaslan May 26 '21 at 09:27
  • Also you know that the time delay is in milliseconds so if you want 1 second you should actually use 1000 – TheLizzard May 26 '21 at 09:28
  • @TheLizzard yes you're right. I'm editing now. – Yılmaz Alpaslan May 26 '21 at 09:31
  • When I was talking about the time delay, I meant the time delay in your `.after` call. Not your` time.sleep` call. So that part of your code should be something like: `root.after(1000, toolTip.showtip, text, widget)`, which means: after 1 second call `toolTip.showtip()` with `text` and `widget` as arguments. – TheLizzard May 26 '21 at 09:33
  • @TheLizzard but this schedules the function to 1 seconds later without checking if mouse is still on the widget. So that makes the tooltip appear even if mouse wasn't remained 1 seconds on the widget. That makes the tooltip glitch if mouse passes over the widget. I hope I'm able to explain what I want. – Yılmaz Alpaslan May 26 '21 at 09:37
  • 1
    Use `.after()` to show the tooltip one second later, but you need to save the ID returned by `.after()`. Then you need to cancel the after task using `.after_cancel(id)` when the mouse leaves the label. – acw1668 May 26 '21 at 09:52
  • @acw1668 Thank you, this one solved my problem without checking if mouse is on widget. Have a nice day. – Yılmaz Alpaslan May 26 '21 at 10:13

1 Answers1

0

I don't know your goal. You can make a tooltip/hovertip by yourself. if you want to make it for yourself, you need to use bind when hovering.

Using hovertip:

from idlelib.tooltip import Hovertip
mytip = Hovertip(self, "Info")

As an example in :

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

Stolen from: How do I display tooltips in Tkinter?

Using bind when hovering: (there are different ways)

from tkinter import *

root = Tk()
root.geometry("400x400")


def hover_this_enter(_):  # on enter
    hover_lbl.place_configure(x=100, y=170)
    hover_lbl.after(1)  # after method (you can change)


def hover_this_leave(_):  # if you want to destroy, make sure you get this item back, or you get attribute errors
    hover_lbl.place_configure(x=-200)


btn = Button(root, text="give me tooltips")
btn.pack(side="left")
btn.bind("<Enter>", hover_this_enter)
btn.bind("<Leave>", hover_this_leave)
hover_lbl = Label(root, text="I was hovered", bg="black", fg="white")

if __name__ == '__main__':
    root.mainloop()
pippo1980
  • 2,181
  • 3
  • 14
  • 30
bangKok
  • 332
  • 2
  • 13
  • Wow, it looks like `idlelib.tooltip` was a good alternative to tooltips. Without any glitches. But first it doesn't have fade-in fade-out animations that makes the program a little bit old-style and second I already solved my problem. By the way thanks for your answer. – Yılmaz Alpaslan May 26 '21 at 10:22
  • yes but if you want make your own `hovers` you can use `after`method. so it's not the right answer for your problem, only an alternative. you need to `bind` your widgets, if you want to do custom – bangKok May 26 '21 at 10:42
  • `bind` command already exists in `createToolTip` function in my code. – Yılmaz Alpaslan May 26 '21 at 10:48
  • yes, in a function. never seen before this way. you need to make for each widget another custom tooltip. or do you want always the same text? so for example = `btn.bind("", func)` would be maybe better – bangKok May 26 '21 at 10:59
  • No, not. When I call `createToolTip(widget, text)` for a widget, I am calling it as `createToolTip(label, "ToolTip Text")` and with that I'm giving information about the widget I want to show tooltip about and the tooltip text. And in `createToolTip` function, it binds the called widget by `widget.bind` so it binds the widget that was entered when calling `createToolTip` function. I mean `widget` variable becames the widget that we wanted to show tooltip about's variable. – Yılmaz Alpaslan May 26 '21 at 12:36
  • And when mouse enters the widget, the `createToolTip` function calls `toolTip.showtip(text, widget)` in `ToolTip` class with entered text variable and widget. So function will always use entered text and widget to show tooltips with `createToolTip(label, "ToolTip Text")` function (`label` is a widget). – Yılmaz Alpaslan May 26 '21 at 12:36
  • there are different ways to handle it. there isn`t "these one way" . I explained myself maybe bad, if you can handle other ways better, work with it. that was only a suggestion – bangKok May 26 '21 at 12:46
  • I think I understand you. Thanks for suggestion. Have a nice day! – Yılmaz Alpaslan May 26 '21 at 12:48
  • @bangKok `hover_lbl.after(1000)` is the same as `time.sleep(1)`. If you read a bit about `tkinter`, you will find that it's a bad idea to use `time.sleep` when using `tkinter`. The same reason applies to `.after(...)` if you don't specify a function as the second parameter. – TheLizzard Sep 04 '21 at 20:13