├── .gitignore ├── LICENSE ├── README.md ├── ToolTips.py └── demo.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Pedro Henriques 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tkinter ToolTips in Python 2 | 3 | A standalone python class that will handle showing and hiding tooltips for single or multiple tkinter widgets. 4 | 5 | The code will show the tooltip when a widget is hovered over and will dynamically adjust the tooltip's position and size to make sure it fits the window. 6 | 7 | ## Instructions 8 | 9 | ### Setup 10 | 11 | In order to use this class you need to: 12 | 13 | 1. Place a copy of **ToolTips.py** in your program's folder. 14 | 2. On the file(s) you want to use the tooltips import this class with `import ToolTips`. 15 | 3. Instantiate the class providing a List of widgets, a List of tooltip text strings and an optional Tkinter Font object, in the form `tooltip_obj = ToolTips.ToolTips(widgets, tooltip_text, font_obj)` 16 | 17 | There are several class variables that can be used to customize the tooltip's styles and position. 18 | 19 | ### Parameters 20 | 21 | The class constructor receives **3 parameters**, 2 mandatory and 1 optional. 22 | 23 | 1. A list of widget references. These widgets are the ones you want tooltips on. 24 | 2. A list of strings with the tooltip text for each of the widgets on the list 25 | 3. [OPTIONAL] A tkinter font object to be used for the tooltip text. 26 | 27 | If a font isn't provided, the tooltips will use the default font family and font size set on the respective class variables. 28 | By default the font family is "Helvetica" and the font size is 12. 29 | 30 | Regarding the List of widgets and the List of strings, they should have **matching indexes**, i.e., the first string on the list should be the tooltip of the first widget on the other list, and so on. 31 | 32 | ### Demo 33 | 34 | The file `demo.py` contains a simple tkinter GUI integrating the tooltip code, which can be used as a reference on how to use this class, if needed. 35 | 36 | ## Technical Information 37 | 38 | ### Binds and Events 39 | 40 | The constructor will set 3 binds for each of the supplied widgets. 41 | 42 | - **<Enter>** --> When the mouse enters the widget area the tooltip will be displayed. 43 | - **<Leave>** --> When the mouse leaves the widget area the tooltip will be removed. 44 | - **<Button-1>** --> When the widget is clicked with the LMB the tooltip will be removed. 45 | 46 | You can set your own binds for showing and removing a widget's tooltips, by calling the `showToolTips()` method to show the tooltip and calling the `hideToolTips()` method to remove the tooltip. 47 | 48 | **NOTE:** 49 | If your code is adding binds for the same widgets and events, make sure to add them to the bind list, rather than replacing the tooltip's binds. 50 | This can be done using the following syntax `widget.bind("event", callback, add="+")` 51 | 52 | ### Placement of the ToolTip 53 | 54 | By default the tooltip will be placed below the respective widget and aligned with it's west border. 55 | The code will also add line breaks to the tooltip string in order to avoid overflowing the window. 56 | 57 | However, if there isn't enough space on the window for the default placement, the code will try moving the tooltip around the widget to check if it fits on the window. 58 | If no position can be found, then the tooltip will try using the entire window width, i.e., it will no longer be aligned with the widget's west border. 59 | If it still doesn't fit the window, then the code will start reducing the font size of the tooltip text until it either fits or the font size reaches 1. 60 | 61 | ### Other Information 62 | 63 | The code will try using as much of the window's width to show the tooltip, but there will be some unused space as a safeguard. 64 | This is needed due to the dynamics of fonts, specifically the width of it's characters and how many characters can be inserted in each of the tooltip's lines. 65 | In order to avoid using a more complex algorithm, that would slow down your program, a simpler solution was used with the downside of requiring a safety margin. 66 | -------------------------------------------------------------------------------- /ToolTips.py: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # # 3 | # Python ToolTips for Tkinter v1.0.0 # 4 | # # 5 | # Copyright 2016, PedroHenriques # 6 | # http://www.pedrojhenriques.com # 7 | # https://github.com/PedroHenriques # 8 | # # 9 | # Free to use under the MIT license. # 10 | # http://www.opensource.org/licenses/mit-license.php # 11 | # # 12 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 13 | 14 | import math, tkinter as tk, tkinter.ttk as ttk, tkinter.font as tkFont 15 | 16 | class ToolTips : 17 | """This class will display a tooltip around a widget that is being hovered over. 18 | The constructor receives a list of widgets, a list of strings and an optional Tkinter font object. 19 | The lists should match their indexes so that the tooltip string for the first widget is in index zero, and so on. 20 | 21 | There are several class variables that can be used to customize the styling of the tooltips. 22 | """ 23 | 24 | # class variables with the fallback font information, to be used in the tooltip text 25 | tt_fallback_font_family_ = "Helvetica" 26 | tt_fallback_font_size_ = 12 27 | 28 | # class variables with the background and foreground colors 29 | bg_color_ = "#ffffe0" 30 | fg_color_ = "#000000" 31 | 32 | # class variables used to control the vertical space between the tooltip and the event widget 33 | vertical_margin_ = 0 34 | 35 | def __init__(self, widgets, tooltip_text, font=None) : 36 | # check if the 2 lists have the same number of items, and if not raise an exception 37 | if (len(widgets) != len(tooltip_text)) : 38 | raise ValueError("The number of widgets supplied does not match the number of tooltip strings provided.") 39 | 40 | # instance variable pointing to a list of widgets to be managed by this instance 41 | self.widgets = widgets 42 | 43 | # instance variable pointing to a list of strings to be used by the supplied widgets 44 | self.tooltip_text = tooltip_text 45 | 46 | # instance variable to flag whether a font was supplied or not 47 | if (font == None) : 48 | self.font_supplied = False 49 | else : 50 | self.font_supplied = True 51 | 52 | # instance variable pointing to a font object, to be used on the tooltip text 53 | self.font = font 54 | 55 | # instance variable with the font object for the tooltip text 56 | # starts with the fallback font 57 | self.tt_font = tkFont.Font(family=self.tt_fallback_font_family_, size=self.tt_fallback_font_size_) 58 | 59 | # loop through each widget and set the necessary binds 60 | for widget in self.widgets : 61 | # set the binds 62 | self.setWidgetBinds(widget) 63 | 64 | # instance variable where the tooltip widget will be stored 65 | self.tt_widget = None 66 | # instance variable where the tooltip's text will be stored 67 | self.tt_text = "" 68 | 69 | # this method will set the , and binds to the widgets related to this instance 70 | def setWidgetBinds(self, widget) : 71 | widget.bind("", self.showToolTips, add="+") 72 | widget.bind("", self.hideToolTips, add="+") 73 | widget.bind("", self.hideToolTips, add="+") 74 | 75 | # this method will be called when widgets with tooltips are hovered over 76 | def showToolTips(self, event) : 77 | # get a reference to the event widget 78 | widget_ref = event.widget 79 | 80 | # check if we were able to grab a pointer to a widget and that we have that widget in 81 | # widget list supplied to the constructor 82 | if (widget_ref == None or widget_ref not in self.widgets) : 83 | # either we couldn't grab a pointer to the event widget 84 | # or that widget is not in the list of widgets provided 85 | # to the constructor, so bail out 86 | print("The hovered widget is not in the list of widgets provided to this instance.") 87 | return(False) 88 | 89 | # grab this widget's tooltip text from the list 90 | try : 91 | self.tt_text = self.tooltip_text[self.widgets.index(widget_ref)] 92 | except (IndexError, ValueError) : 93 | # either widget_ref couldn't be found in self.widgets 94 | # or the tooltip text couldn't be found for the widget's index, so bail out 95 | print("An error occured while trying to find the tooltip text for the hovered widget.") 96 | return(False) 97 | 98 | # grab the event widget's top master (will be used for both measuring the position of the tooltip 99 | # and will be the direct master for the tooltip) 100 | top_master = widget_ref.winfo_toplevel() 101 | 102 | # grab the event widget's current width and height 103 | widget_width = widget_ref.winfo_width() 104 | widget_height = widget_ref.winfo_height() 105 | 106 | # local variables used to position the tooltip 107 | # starting at the NW corner of the event widget 108 | x = widget_ref.winfo_x() 109 | y = widget_ref.winfo_y() 110 | 111 | # loop through all the masters of the event widget, until we reach top_master 112 | # for each master, add it's x and y relative to it's master 113 | # by the end, we'll have the x and y coords of the event widget in relation to top_master 114 | w_master = top_master.nametowidget(widget_ref.winfo_parent()) # event widget's master 115 | while w_master != top_master : 116 | # update the x and y coords of the 117 | x += w_master.winfo_x() 118 | y += w_master.winfo_y() 119 | 120 | # grab next master in the hierarchy 121 | w_master = top_master.nametowidget(w_master.winfo_parent()) 122 | 123 | # local variables used to store the final position of the tooltip 124 | # we'll start with the values of x and y, but might be changed in the loop below 125 | final_x = x 126 | final_y = y 127 | 128 | # create the tooltip font based on the event widget's font 129 | self.setFont(widget_ref) 130 | 131 | # create the tooltip label widget and initial width + height 132 | self.handleTooltipWidget(top_master) 133 | 134 | # grab top master's width (index 2) and height (index 3), through it's bbox data 135 | tm_bbox = top_master.bbox() 136 | 137 | # local control variables 138 | update_x = False 139 | found_tt_place = False 140 | 141 | # calculate the maximum x coordinate for the tooltip, which is the lowest between 142 | # the window width and the east border of the event widget 143 | # this is relevant, for example, in the case where the event widget is partially outside the window 144 | # (by using a horizontal scrollbar), meaning that the event widget's east border is outisde the window 145 | # and thus we can't anchor the tooltip to it, but rather to the widnow's east border 146 | max_x = min(x + widget_width, tm_bbox[2]) 147 | 148 | # calculate the minimum x coordinate for the tooltip, which starts on the event widget's W border 149 | min_x = x 150 | 151 | while (not found_tt_place and self.tt_font_size > 1) : 152 | # by default we assume we'll find a position for the tooltip on this iteration 153 | found_tt_place = True 154 | 155 | # at this point, the tooltip's NW corner matches the event widget's NW corner 156 | # check if there's enough width in top_master to fit the tooltip in the default horizontal position 157 | # i.e., growing to the right 158 | if (min_x + self.tt_width > tm_bbox[2]) : 159 | # there isn't enough width to fit the tooltip 160 | # check if we can place the tooltip to the left of the event widget 161 | # i.e., make the E border of the tooltip match the E border of the event widget 162 | if (max_x - self.tt_width >= 0) : 163 | # it fits to the left 164 | # make the tooltip's E border match the event widget's E border 165 | final_x = max_x - self.tt_width 166 | else : 167 | # we need to add line breaks to the tooltip's text 168 | # start by checking which side has more width to work with 169 | if (min_x/tm_bbox[2] <= 0.5) : 170 | # the tooltip is in the left half of the top_master, so there is more width to the right of the event widget 171 | # set the tooltip to go as far left as the window's E border 172 | max_x = tm_bbox[2] 173 | else : 174 | # the tooltip is in the right half of the top_master, so there is more width to the left of the event widget 175 | # set the tooltip to go as far right as the window's W border 176 | min_x = 0 177 | # flag as needing to update the tooltip's X position after all the text changes 178 | update_x = True 179 | 180 | # calculate the tooltip's maximum width 181 | tt_max_width = max_x - min_x 182 | 183 | # calculate the len of the tooltip text 184 | tt_text_len = len(self.tt_text) 185 | # calculate the average width of each character, for the font in use by this widget 186 | tt_char_width = self.tt_width / tt_text_len 187 | # calculate the index increment for the loop below, rounded down ==> #characters per line 188 | # NOTE: by using math.ceil(tt_char_width) it greatly reduces the chance of the tooltip overflowing the window, but it also leads to some space not being used by the tooltip (a safety margin). 189 | # use just tt_char_width to maximize space usage...but in some cases it will lead to overflows. 190 | # This is due to the calculations using an average pixels per character, which in variable width fonts is not 100% accurate. In fixed width fonts this should not be an issue. 191 | increment = math.floor(tt_max_width / math.ceil(tt_char_width)) 192 | # first slice will be from index 0 to the 1st \n index 193 | start_index = 0 194 | end_index = increment 195 | # variable with the new tooltip text 196 | new_tt_text = "" 197 | 198 | # loop through the tooltip text and add the \n when needed 199 | while end_index < tt_text_len : 200 | # grab this iteration's slice of the tooltip text 201 | tt_text_slice = self.tt_text[start_index:end_index] 202 | 203 | # check if there are any \n added by the developer in this slice 204 | if ("\n" in tt_text_slice) : 205 | # split the slice into sub slices, based on \n 206 | tt_sub_slices = tt_text_slice.split("\n") 207 | 208 | # if there are more than 1 sub slices and the last one is not empty 209 | # i.e., the slice doesn't end with a \n, then don't run the last sub slice 210 | # now, since it doesn't end with a manual \n and one will be added if we run it now 211 | if (len(tt_sub_slices) > 1 and tt_sub_slices[-1] != "") : 212 | tt_sub_slices = tt_sub_slices[:-1] 213 | 214 | # reset the end index value, since we might not reach the end of this slice 215 | end_index = start_index 216 | 217 | # loop through each sub slice and if it doesn't exceed the max character number 218 | # for the available width, add it to the tooltip text as is 219 | # ends when all sub slices are dealt with OR we find one that overflows the available width 220 | # in which case we'll process it on the next iteration of the parent loop 221 | for tt_sub_slice in tt_sub_slices : 222 | # check if it overflows and if it does BREAK 223 | if (len(tt_sub_slice) > increment) : 224 | break 225 | 226 | # at this point this sub slice doesn't overflow 227 | # add it to the tooltip text, plus the \n 228 | new_tt_text += tt_sub_slice + "\n" 229 | 230 | # advance the end index value by the sub slice's len + 1 for the \n 231 | end_index += len(tt_sub_slice) + 1 232 | else : 233 | # there are no manual \n to deal with in this slice 234 | new_tt_text += tt_text_slice 235 | 236 | # if the next character in the source string is a \n, then don't add this iteration's 237 | # automatic \n, else add it. This avoids ending up with 2 \n in a row, when only 1 was intended 238 | if ("\n" not in self.tt_text[end_index:end_index+1]) : 239 | # add this iteration's \n 240 | new_tt_text += "\n" 241 | 242 | # advance to the index values 243 | start_index = end_index 244 | end_index += increment 245 | 246 | # if there is any remainder text, add it to the tooltip text 247 | # else, the tooltip text will end on a \n which needs to be removed 248 | if (start_index != tt_text_len - 1) : 249 | # the last index added to the tooltip's text was not the final index, so 250 | # add the final slice of the tooltip text 251 | new_tt_text += self.tt_text[start_index:tt_text_len] 252 | else : 253 | # there is no more text to add, so remove the ending \n 254 | new_tt_text = new_tt_text[:-1] 255 | 256 | # update the label's text 257 | self.tt_widget.configure(text=new_tt_text) 258 | # update the tooltip's width and height 259 | self.tt_width = self.tt_widget.winfo_reqwidth() 260 | self.tt_height = self.tt_widget.winfo_reqheight() 261 | else : 262 | final_x = min_x 263 | 264 | # at this point if the tooltip has more than 1 line, it is using as much width as possible, 265 | # taking into account the safety margin for variable width fonts 266 | 267 | # check if there's enough height in top_master to fit the tooltip in the default vertical position 268 | # i.e., below the event widget 269 | if (y + widget_height + self.vertical_margin_ + self.tt_height > tm_bbox[3]) : 270 | # there isn't enough height to fit the tooltip 271 | # check if we can place the tooltip to the top of the event widget 272 | if (y - self.tt_height - self.vertical_margin_ >= 0) : 273 | # it fits to the top 274 | # move the tooltip up 275 | final_y = y - self.tt_height - self.vertical_margin_ 276 | else : 277 | # it doesn't fit below or above the event widget 278 | 279 | # start by removing the default behaviour of having the tooltip anchored to the event widget's 280 | # east border, making the tooltip go from the window's W to the E border 281 | # if that isn't enough start reducing the font size used by the tooltip text by 1 point per loop iteration 282 | # the loop will end when we reach a situation where the tooltip fits on the screen OR the font 283 | # size goes all the way down to 1 point, at which point we conclude that there is no way to 284 | # fit the tooltip on the screen given the text in it, the event widget's position on the window 285 | # and the window's size 286 | 287 | # if we're already using the entire window's width, then work the font size 288 | if (max_x == tm_bbox[2] and min_x == 0) : 289 | # update the tooltip's font to reduce the font size by 1 point 290 | self.tt_font_size -= 1 291 | self.tt_font.configure(size = max(self.tt_font_size, 1)) 292 | 293 | # recalculate the requested width and height with the new font size 294 | self.handleTooltipWidget(top_master) 295 | else : 296 | # update the max_x to go all the way to the window's E border 297 | # instead of the event widget's E border 298 | max_x = tm_bbox[2] 299 | min_x = 0 300 | 301 | # signal the loop that we didn't find a position for the tooltip yet 302 | found_tt_place = False 303 | else : 304 | # there is enough height below the event widget to fit the tooltip 305 | # so move the tooltip down 306 | final_y = y + widget_height + self.vertical_margin_ 307 | 308 | # if the tooltip text was changed such that the width changed, then recalculate the 309 | # tooltip's X position 310 | if (update_x) : 311 | # move the tooltip to have it's E border matching the event widget's E border 312 | # or the window's E border, depending on the case 313 | final_x = max_x - self.tt_width 314 | 315 | # draw the tooltip 316 | self.tt_widget.place(relx=final_x/tm_bbox[2], rely=final_y/tm_bbox[3]) 317 | 318 | # this method will be called when widgets with tooltips sto being hovered over 319 | # or "entered" by any other means (ex: tab selecting) 320 | def hideToolTips(self, event) : 321 | # if there are no active tooltips bail out 322 | if (self.tt_widget == None) : 323 | return 324 | 325 | # remove the tooltip from the screen and any other tkinter internal references 326 | self.tt_widget.destroy() 327 | 328 | # free the variables 329 | self.tt_widget = None 330 | self.tt_text = "" 331 | 332 | # this method will create the tooltip widget, if one doesn't exist already, and will calculate 333 | # the required width and height of the tooltip text without any line breaks, which is used to later calculate 334 | # the average character width for the given font and the given tooltip text 335 | def handleTooltipWidget(self, top_master) : 336 | # if the tooltip widget doesn't exist create it, otherwise update the dimensions 337 | if (self.tt_widget == None) : 338 | # create the tooltip label widget 339 | # NOTE: we start with an altered version of the intended text, because of the need to calculate the required width 340 | # of the tooltip widget without any line breaks. If they are left in that calculation it will skew the 341 | # the calculation (later below) of the average #characters in each line that fit the window size. 342 | # The correct text will be added shortly below. 343 | self.tt_widget = ttk.Label(top_master, text=self.tt_text.replace("\n", " "), background=self.bg_color_, foreground=self.fg_color_, font=self.tt_font) 344 | else : 345 | # update the tooltip's text to recalculate the requested dimensions below 346 | self.tt_widget.configure(text=self.tt_text.replace("\n", " ")) 347 | 348 | # move the tooltip from being on top of the event widget to a reasonable relative location 349 | # by default the tooltip's NW corner will be close to the event widget's SW corner 350 | # however this might need to be adjusted, if it would make the tooltip overflow the program's window 351 | # calculate the width and height of the tooltip (using the required dimension since it hasn't been drawn to screen yet, meaning there are no actual dimensions yet) 352 | self.tt_width = self.tt_widget.winfo_reqwidth() 353 | self.tt_height = self.tt_widget.winfo_reqheight() 354 | 355 | # now that we have calculated the initial required dimensions, we can update the widget's text to the intended one 356 | self.tt_widget.configure(text=self.tt_text) 357 | 358 | # this method creates the font based on the event widget's font family and size 359 | def setFont(self, event_widget) : 360 | # grab the event widget's font info 361 | try : 362 | if (self.font_supplied) : 363 | # a font was supplied to the constructor, so create a copy of it's current values 364 | self.tt_font["family"] = self.font["family"] 365 | self.tt_font["size"] = self.font["size"] 366 | else : 367 | # a font wasn't supplied to the constructor, try getting the event widget's font 368 | ew_font_info = event_widget["font"].actual() 369 | self.tt_font["family"] = ew_font_info["family"] 370 | self.tt_font["size"] = ew_font_info["size"] 371 | except Exception : 372 | # we couldn't create a tkFont object 373 | # most likely the event widget is using a custom tkFont object which can't be accessed and edited from here 374 | # or the event widget doesn't have a "font" attribute 375 | # use the fallback font 376 | self.tt_font["family"] = self.tt_fallback_font_family_ 377 | self.tt_font["size"] = self.tt_fallback_font_size_ 378 | 379 | # grab the tt_font's font size 380 | self.tt_font_size = self.tt_font["size"] 381 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | import traceback, tkinter as tk, tkinter.ttk as ttk, tkinter.font as tkFont 2 | import ToolTips 3 | 4 | # demo GUI 5 | try : 6 | # create the application window 7 | app_window = tk.Tk() 8 | app_window.title("Tooltip Demo") 9 | app_window.rowconfigure(0, weight=1) 10 | app_window.rowconfigure(1, weight=2) 11 | app_window.rowconfigure(3, weight=2) 12 | app_window.columnconfigure(0, weight=1) 13 | app_window.columnconfigure(2, weight=1) 14 | 15 | # local variables used to store pointers to the widgets we want to have tooltips on 16 | # and the tooltip text for those widgets 17 | # the index on both variables should match so that, for example, the first widget on the list 18 | # should have it's tooltip text as the first item on the other list 19 | widgets = [] 20 | tooltip_text = [] 21 | 22 | # create a custom font, used by the demo widgets and the tooltips 23 | font_obj = tkFont.Font(family="Courier", size=12) 24 | 25 | # add widgets to the window to demonstrate the tooltips at work 26 | # add a reference to each widget and it's tooltip text in the corresponding lists 27 | # making sure to match the keys of both lists 28 | 29 | # add a label with more information 30 | ttk.Label(app_window, text="Try resizing the window to see the changes to the tooltip!", font=font_obj).grid(row=0, column=0, columnspan=3, sticky="N,W") 31 | 32 | # NW corner 33 | widget = ttk.Label(app_window, text="Hover me!", font=font_obj) 34 | widget.grid(row=1, column=0, sticky="N,W") 35 | widgets.append(widget) 36 | tooltip_text.append("This is a tooltip that is very long and should not fit on the screen.\nAs such it will be split into several lines, with each line using as much width as possible!") 37 | # NE corner 38 | widget = ttk.Label(app_window, text="Hover me!", font=font_obj) 39 | widget.grid(row=1, column=2, sticky="N,E") 40 | widgets.append(widget) 41 | tooltip_text.append("This is a tooltip!") 42 | # SW corner 43 | widget = ttk.Label(app_window, text="Hover me!", font=font_obj) 44 | widget.grid(row=3, column=0, sticky="S,W") 45 | widgets.append(widget) 46 | tooltip_text.append("This is a tooltip!") 47 | # SE corner 48 | widget = ttk.Label(app_window, text="Hover me!", font=font_obj) 49 | widget.grid(row=3, column=2, sticky="S,E") 50 | widgets.append(widget) 51 | tooltip_text.append("This is a tooltip that is very long and should not fit on the screen. As such it will be split into several lines, with each line using as much width as possible!") 52 | 53 | # center 54 | widget = ttk.Label(app_window, text="Hover me!", font=font_obj) 55 | widget.grid(row=2, column=1, sticky="S,E") 56 | widgets.append(widget) 57 | tooltip_text.append("This is a really big tooltip text that will not fit inside the window at the default position and thus will need to be broken into several lines and will need to breack the anchor restriction as well as reducing the font size") 58 | 59 | # instantiate the ToolTips class, providing the list of widgets, tooltip strings and font (optional parameter) 60 | tooltip_obj = ToolTips.ToolTips(widgets, tooltip_text, font_obj) 61 | 62 | # start the GUI event listener loop 63 | app_window.mainloop() 64 | except Exception as e : 65 | traceback.print_exc() 66 | print("\n") 67 | --------------------------------------------------------------------------------