It all started from the fact that a normal tk.Scale
was not feared correctly (I'm using a custom theme).
I then switched to ttk.Scale
but it was not showing the value above the slider when I changed it.
I then discovered ttkwidget, which seems to work but with graphical glitches. Anyone have any ideas to fix?
Code: https://pastebin.com/bxgvsjSF
Screenshot: https://ibb.co/tLdjLrx
P.S. Furthermore, the the widgets that have problems are very slow to load widgets
EDIT: solution is in the comment of the best answer
I have found a way to get rid of the glitches in ttk.LabeledScale
. I think the issue is the redrawing of the whole Label
widget so I used a Canvas
instead. With a Canvas
, when the text is moved, there is no need to redraw the background so the animation is smoother.
The code below is based on the source code of the ttk.LabeledScale
(from Python 3.9.1, you can find it in tkinter/ttk.py). But the widget is now based on a Canvas
in which the scale and text are added with create_window()
and ćreate_text()
. I modified the __init__()
and _adjust()
methods and I added a _on_theme_change()
method which is called when the theme changes to update the styling of the canvas and adjust the positions of the elements.
import tkinter as tk
from tkinter import ttk
class LabeledScale(tk.Canvas):
def __init__(self, master=None, variable=None, from_=0, to=10, **kw):
self._label_top = kw.pop('compound', 'top') == 'top'
tk.Canvas.__init__(self, master, **kw)
self._variable = variable or tk.IntVar(master)
self._variable.set(from_)
self._last_valid = from_
# use style to set the Canvas background color
self._style = ttk.Style(self)
self.configure(bg=self._style.lookup('Horizontal.TScale', 'background'))
# create the scale
self.scale = ttk.Scale(self, variable=self._variable, from_=from_, to=to)
self.scale.bind('<<RangeChanged>>', self._adjust)
# put scale in canvas
self._scale = self.create_window(0, 0, window=self.scale, anchor='nw')
# put label in canvas (the position will be updated later)
self._label = self.create_text(0, 0, text=self._variable.get(),
fill=self._style.lookup('TLabel', 'foreground'),
anchor='s' if self._label_top else 'n')
# adjust canvas height to fit the whole content
bbox = self.bbox(self._label)
self.configure(width=self.scale.winfo_reqwidth(),
height=self.scale.winfo_reqheight() + bbox[3] - bbox[1])
# bindings and trace to update the label
self.__tracecb = self._variable.trace_variable('w', self._adjust)
self.bind('<Configure>', self._adjust)
self.bind('<Map>', self._adjust)
# update sizes, positions and appearances on theme change
self.bind('<<ThemeChanged>>', self._on_theme_change)
def destroy(self):
"""Destroy this widget and possibly its associated variable."""
try:
self._variable.trace_vdelete('w', self.__tracecb)
except AttributeError:
pass
else:
del self._variable
super().destroy()
self.label = None
self.scale = None
def _on_theme_change(self, *args):
"""Update position and appearance on theme change."""
def adjust_height():
bbox = self.bbox(self._label)
self.configure(height=self.scale.winfo_reqheight() + bbox[3] - bbox[1])
self.configure(bg=self._style.lookup('Horizontal.TScale', 'background'))
self.itemconfigure(self._label, fill=self._style.lookup('TLabel', 'foreground'))
self._adjust()
self.after_idle(adjust_height)
def _adjust(self, *args):
"""Adjust the label position according to the scale."""
def adjust_label():
self.update_idletasks() # "force" scale redraw
x, y = self.scale.coords()
if self._label_top:
y = 0
else:
y = self.scale.winfo_reqheight()
# avoid that the label goes off the canvas
bbox = self.bbox(self._label)
x = min(max(x, 0), self.winfo_width() - (bbox[2] - bbox[0])/2)
self.coords(self._label, x, y) # move label
self.configure(scrollregion=self.bbox('all'))
self.yview_moveto(0) # make sure everything is visible
self.itemconfigure(self._scale, width=self.winfo_width())
from_ = ttk._to_number(self.scale['from'])
to = ttk._to_number(self.scale['to'])
if to < from_:
from_, to = to, from_
newval = self._variable.get()
if not from_ <= newval <= to:
# value outside range, set value back to the last valid one
self.value = self._last_valid
return
self._last_valid = newval
self.itemconfigure(self._label, text=newval)
self.after_idle(adjust_label)
@property
def value(self):
"""Return current scale value."""
return self._variable.get()
@value.setter
def value(self, val):
"""Set new scale value."""
self._variable.set(val)
root = tk.Tk()
style = ttk.Style(root)
style.theme_use('alt')
scale = LabeledScale(root, from_=0, to=100, compound='bottom')
scale.pack(expand=True, fill='x')
root.mainloop()