'How does one get tooltips working for notebook tabs in python tkinter?

I'm working with python 3.8 and tkinter to create a GUI that uses a ttk notebook (tabbed window). I want to have tooltips displayed for the individual tabs but I can't see how to add the tooltip for the tab. I have a class defined that takes an object and text and adds the tooltip and it works for buttons and other objects.

I have example code here:

'''
At no time were Katz, Dawgs, Mice or Squirrels harmed in any way during the development of this application. But...
Well just lets say that skunk next door had it coming!
'''

import tkinter as tk
from tkinter import ttk


class CreateToolTip(object):
    """
    create a tooltip for a given widget
    """
    def __init__(self, widget, text='widget info'):
        self.waittime = 250     #miliseconds
        self.wraplength = 180   #pixels
        self.widget = widget
        self.text = text
        self.widget.bind("<Enter>", self.enter)
        self.widget.bind("<Leave>", self.leave)
        self.widget.bind("<ButtonPress>", self.leave)
        self.id = None
        self.tw = None

    def enter(self, event=None):
        self.schedule()

    def leave(self, event=None):
        self.unschedule()
        self.hidetip()

    def schedule(self):
        self.unschedule()
        self.id = self.widget.after(self.waittime, self.showtip)

    def unschedule(self):
        id = self.id
        self.id = None
        if id:
            self.widget.after_cancel(id)

    def showtip(self, event=None):
        x = y = 0
        x, y, cx, cy = self.widget.bbox("insert")
        x += self.widget.winfo_rootx() + 25
        y += self.widget.winfo_rooty() + 20
        # creates a toplevel window
        self.tw = tk.Toplevel(self.widget)
        # Leaves only the label and removes the app window
        self.tw.wm_overrideredirect(True)
        self.tw.wm_geometry("+%d+%d" % (x, y))
        label = tk.Label(self.tw, text=self.text, justify='left',
                       background="cornsilk", relief='solid', borderwidth=1,
                       wraplength = self.wraplength)
        label.pack(ipadx=1)

    def hidetip(self):
        tw = self.tw
        self.tw= None
        if tw:
            tw.destroy()


class MyCanvas(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.tabControl = ttk.Notebook(self)
        self.geometry('600x300')
        self.title('An Indepth study of the Differences Between Katz and Dawgs!')
        self.tab = {}
        self.tab['dawg tab'] = ttk.Frame(self.tabControl)
        self.tabControl.add(self.tab['dawg tab'], text='dawgs', state='normal')
        self.tab['katz tab'] = ttk.Frame(self.tabControl)
        self.tabControl.add(self.tab['katz tab'], text='katz', state='normal')
        self.tabControl.enable_traversal()
        btn1 = tk.Button(self.tab['dawg tab'], text='Squirrel')
        btn1.grid(row=1, column=0)
        CreateToolTip(btn1, 'Despised by dawgs!!')
        btn2 = tk.Button(self.tab['katz tab'], text='Mice')
        btn2.grid(row=1, sticky='EW')
        CreateToolTip(btn2, 'Katz find them a tasty treat.')
        self.tabControl.grid(row=0, column=0, sticky="W")


# testing ...
if __name__ == '__main__':
    root = MyCanvas()
    root.mainloop()

Any advice or constructive comments would be greatly appreciated. And yes, the tooltip class was totally plagiarized from someone else here on stack overflow.

Cheers!!

So after a few days and lots of help from a friend here is what I have that actually works:

'''
At no time were Katz, Dawgs, Mice or Squirrels harmed in any way during the development of this application. But...
Well lets just say that skunk next door had it coming!
'''
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.scrolledtext as tkst


class MyCanvas(ttk.Notebook):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.tooltips = dict()
        self.tab_list = dict()
        self.current_tab = None
        self.waittime = 250     #miliseconds
        self.wraplength = 180   #pixels
        self.id = None
        self.tw = None
        self.bind('<Motion>', self.motion)
        self.bind('<ButtonPress>', self.leave)
        self.line_number = 0

    def leave(self, event=None):
        self.unschedule()
        self.hide_tip()

    def schedule(self):
        self.unschedule()
        self.id = self.after(self.waittime, self.show_tip)

    def unschedule(self):
        id = self.id
        self.id = None
        if id:
            self.after_cancel(id)

    def show_tip(self):
        if self.tw:
            return
        if self.current_tab in self.tooltips.keys():
            x = y = 0
            x, y, cx, cy = self.bbox('insert')
            x += self.winfo_rootx() + 25
            y += self.winfo_rooty() - 20

            # creates a toplevel window
            self.tw = tk.Toplevel(self)

            # Leaves only the label and removes the app window
            self.tw.wm_overrideredirect(True)
            self.tw.wm_geometry(f'+{x}+{y}')
            self.tw.wm_attributes('-topmost', True)
            label = ttk.Label(self.tw, text=self.tooltips[self.current_tab], background='cornsilk')
            label.pack()

    def add(self, *args, **kwargs):
        if 'tooltip' in kwargs.keys():
            tooltip = kwargs['tooltip']
            del kwargs['tooltip']
        else:
            tooltip = None
        self.tab_list[kwargs.get('text')] = args[0]
        super().add(*args, **kwargs)
        if tooltip and ('text' in kwargs.keys()):
            self.set_tooltip(kwargs['text'], tooltip)

    def set_tooltip(self, tab_text, tooltip):
        for idx in range(self.index('end')):
            if self.tab(idx, 'text') == tab_text:
                self.tooltips[tab_text] = tooltip
                return True
        return False

    def motion(self, event):
        if self.identify(event.x, event.y) == 'label':
            index = self.index(f'@{event.x},{event.y}')
            if self.current_tab != self.tab(index, 'text'):
                self.current_tab = self.tab(index, 'text')
                self.show_tip()
        else:
            self.hide_tip()
            self.current_tab = None

    def hide_tip(self):
        tw = self.tw
        self.tw = None
        if tw:
            tw.destroy()

    def insert_text_to_tab(self, **kwargs):
        string_var = tk.StringVar()

        if 'tab' in kwargs.keys():
            the_tab = kwargs.get('tab')
            string_var.set(kwargs.get('data'))
            the_label = tk.Label(self.tab_list[the_tab], textvariable=string_var, background='lightblue')
            the_label.pack()


def get_scrolled_text_widget(parent):
    st_widget = tkst.ScrolledText(parent)
    return st_widget


if __name__ == '__main__':
    root = tk.Tk()
    root.title('An Indepth study of the Differences Between Katz and Dawgs!')
    canvas = MyCanvas(root)
    canvas.pack(fill='both', expand=1)
    canvas.add(get_scrolled_text_widget(canvas), text='Dawgz', tooltip='A quadraped that doesn\'t look like a kat')
    canvas.add(get_scrolled_text_widget(canvas), text='Katz', tooltip='A quadraped that doesn\'t look like a dawg')
    canvas.insert_text_to_tab(data='Dawgz hate squirrels!!', tab='Dawgz')
    canvas.insert_text_to_tab(data='Katz find mice a tasty treat.', tab='Katz')

    root.mainloop()

So it would appear that I was thinking about it a bit wrong and I had only to think of each tabbed entry as an object instead of an attribute (I'm sure the python peanut gallery are going to be all over me for saying that) the solution is quit straight forward.

Cheers!!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source