Search code examples
pythontkintertooltiptk-toolkitcustomtkinter

How to add a tooltip to a customtkinter image (CTkImage)?


I'm using tkinter-tooltip and when I try to add a tooltip to a button consisting of only an image, it says "NoneType' object has no attribute 'bind'". It works if I do text=" " but I don't wanna make the text a space or anything, because its shape gets distorted. I just want to add a tooltip to image, is there any way to override this?

import customtkinter
from PIL import Image
from tktooltip import ToolTip

root = customtkinter.CTk()

icon = customtkinter.CTkImage(dark_image=Image.open("icon.png"))
button = customtkinter.CTkButton(master=root, width=10, height=10, text="", image=icon)
button.pack(pady=20, padx=20)

ToolTip(button, msg="A message", delay=0, follow=True,
        parent_kwargs={"bg": "black", "padx": 3, "pady": 3},
        fg="white", bg="orange", padx=7, pady=7)

root.mainloop()
Traceback (most recent call last):
  File "d:\Python Projects\main.py", line 10, in <module>
    ToolTip(button, msg="A message", delay=0, follow=True,
  File "D:\Python311\Lib\site-packages\tktooltip\tooltip.py", line 83, in __init__ 
    self.widget.bind("<Enter>", self.on_enter, add="+")
  File "D:\Python311\Lib\site-packages\customtkinter\windows\widgets\ctk_button.py", line 544, in bind
    self._text_label.bind(sequence, command, add=True)
    ^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'bind'

Solution

  • As Bryan said, it is a bug in customtkinter that when text option is an empty string, the internal label self._text_label will not be created and so it is None. You can overcome this by setting text=" " instead.

    Also it is better not to set delay=0 in ToolTip(...) as it will make the tooltip not showing sometimes. Just give delay a very small value like 0.01 for example.

    import customtkinter
    from PIL import Image
    from tktooltip import ToolTip
    
    root = customtkinter.CTk()
    
    icon = customtkinter.CTkImage(dark_image=Image.open("icon.png"))
    # set text=" "
    button = customtkinter.CTkButton(master=root, width=10, height=10, text=" ", image=icon)
    button.pack(pady=20, padx=20)
    
    # set delay=0.01
    ToolTip(button, msg="A message", delay=0.01, follow=True,
            parent_kwargs={"bg": "black", "padx": 3, "pady": 3},
            fg="white", bg="orange", padx=7, pady=7)
    
    root.mainloop()
    

    Another option is to create a custom CTkButton class and override the bind() function:

    class MyButton(customtkinter.CTkButton):
        def bind(self, sequence=None, command=None, add=True):
            if not (add == "+" or add is True):
                raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
            self._canvas.bind(sequence, command, add=True)
            if self._text_label:
                self._text_label.bind(sequence, command, add=True)
            if self._image_label:
                self._image_label.bind(sequence, command, add=True)
    ...
    button = MyButton(master=root, width=20, height=20, text="", image=icon)
    ...