not a complete answer, but have you considered creating your own scrollbar lookalike:
import tkinter as tk
class MyScrollbar(tk.Canvas):
def __init__(self, master, *args, **kwargs):
if 'width' not in kwargs:
kwargs['width'] = 10
if 'bd' not in kwargs:
kwargs['bd'] = 0
if 'highlightthickness' not in kwargs:
kwargs['highlightthickness'] = 0
self.command = kwargs.pop('command')
tk.Canvas.__init__(self, master, *args, **kwargs)
self.elements = { 'button-1':None,
'button-2':None,
'trough':None,
'thumb':None}
self._oldwidth = 0
self._oldheight = 0
self._sb_start = 0
self._sb_end = 1
self.bind('<Configure>', self._resize)
self.tag_bind('button-1', '<Button-1>', self._button_1)
self.tag_bind('button-2', '<Button-1>', self._button_2)
self.tag_bind('trough', '<Button-1>', self._trough)
self._track = False
self.tag_bind('thumb', '<ButtonPress-1>', self._thumb_press)
self.tag_bind('thumb', '<ButtonRelease-1>', self._thumb_release)
self.tag_bind('thumb', '<Leave>', self._thumb_release)
self.tag_bind('thumb', '<Motion>', self._thumb_track)
def _sort_kwargs(self, kwargs):
for key in kwargs:
if key in ['buttontype', 'buttoncolor', 'troughcolor', 'thumbcolor', 'thumbtype']:
self._scroll_kwargs[key] = kwargs.pop(key) # add to custom dict and remove from canvas dict
return kwargs
def _resize(self, event):
width = self.winfo_width()
height = self.winfo_height()
# print("canvas: (%s, %s)" % (width, height))
if self.elements['button-1']: # exists
if self._oldwidth != width:
self.delete(self.elements['button-1'])
self.elements['button-1'] = None
else:
pass
if not self.elements['button-1']: # create
self.elements['button-1'] = self.create_oval((0,0,width, width), fill='#006cd9', outline='#006cd9', tag='button-1')
if self.elements['button-2']: # exists
coords = self.coords(self.elements['button-2'])
if self._oldwidth != width:
self.delete(self.elements['button-2'])
self.elements['button-2'] = None
elif self._oldheight != height:
self.move(self.elements['button-2'], 0, height-coords[3])
else:
pass
if not self.elements['button-2']: # create
self.elements['button-2'] = self.create_oval((0,height-width,width, height), fill='#006cd9', outline='#006cd9', tag='button-2')
if self.elements['trough']: # exists
coords = self.coords(self.elements['trough'])
if (self._oldwidth != width) or (self._oldheight != height):
self.delete(self.elements['trough'])
self.elements['trough'] = None
else:
pass
if not self.elements['trough']: # create
self.elements['trough'] = self.create_rectangle((0,int(width/2),width, height-int(width/2)), fill='#00468c', outline='#00468c', tag='trough')
self.set(self._sb_start, self._sb_end) # hacky way to redraw thumb
self.tag_raise('thumb') # ensure thumb always on top of trough
self._oldwidth = width
self._oldheight = height
def _button_1(self, event):
self.command('scroll', -1, 'pages')
return 'break'
def _button_2(self, event):
self.command('scroll', 1, 'pages')
return 'break'
def _trough(self, event):
width = self.winfo_width()
height = self.winfo_height()
size = (self._sb_end - self._sb_start) / 1
thumbrange = height - width
thumbsize = int(thumbrange * size)
thumboffset = int(thumbrange * self._sb_start) + int(width/2)
thumbpos = int(thumbrange * size / 2) + thumboffset
if event.y < thumbpos:
self.command('scroll', -1, 'pages')
elif event.y > thumbpos:
self.command('scroll', 1, 'pages')
return 'break'
def _thumb_press(self, event):
print("thumb press: (%s, %s)" % (event.x, event.y))
self._track = True
def _thumb_release(self, event):
print("thumb release: (%s, %s)" % (event.x, event.y))
self._track = False
def _thumb_track(self, event):
if self._track:
# print("*"*30)
print("thumb: (%s, %s)" % (event.x, event.y))
width = self.winfo_width()
height = self.winfo_height()
# print("window size: (%s, %s)" % (width, height))
size = (self._sb_end - self._sb_start) / 1
# print('size: %s' % size)
thumbrange = height - width
# print('thumbrange: %s' % thumbrange)
thumbsize = int(thumbrange * size)
# print('thumbsize: %s' % thumbsize)
clickrange = thumbrange - thumbsize
# print('clickrange: %s' % clickrange)
thumboffset = int(thumbrange * self._sb_start) + int(width/2)
# print('thumboffset: %s' % thumboffset)
thumbpos = int(thumbrange * size / 2) + thumboffset
# print("mouse point: %s" % event.y)
# print("thumbpos: %s" % thumbpos)
point = (event.y - (width/2) - (thumbsize/2)) / clickrange
# point = (event.y - (width / 2)) / (thumbrange - thumbsize)
# print(event.y - (width/2))
# print(point)
if point < 0:
point = 0
elif point > 1:
point = 1
# print(point)
self.command('moveto', point)
return 'break'
def set(self, *args):
oldsize = (self._sb_end - self._sb_start) / 1
self._sb_start = float(args[0])
self._sb_end = float(args[1])
size = (self._sb_end - self._sb_start) / 1
width = self.winfo_width()
height = self.winfo_height()
if oldsize != size:
self.delete(self.elements['thumb'])
self.elements['thumb'] = None
thumbrange = height - width
thumbsize = int(thumbrange * size)
thumboffset = int(thumbrange * self._sb_start) + int(width/2)
if not self.elements['thumb']: # create
self.elements['thumb'] = self.create_rectangle((0, thumboffset,width, thumbsize+thumboffset), fill='#4ca6ff', outline='#4ca6ff', tag='thumb')
else: # move
coords = self.coords(self.elements['thumb'])
if (thumboffset != coords[1]):
self.move(self.elements['thumb'], 0, thumboffset-coords[1])
return 'break'
if __name__ == '__main__':
root = tk.Tk()
lb = tk.Listbox(root)
lb.pack(side='left', fill='both', expand=True)
for num in range(0,100):
lb.insert('end', str(num))
sb = MyScrollbar(root, width=50, command=lb.yview)
sb.pack(side='right', fill='both', expand=True)
lb.configure(yscrollcommand=sb.set)
root.mainloop()
I've left my comments in, and for the life of me i can't seem to get click and dragging the thumb to work correctly, but its a simple scrollbar with the following features:
- up and down buttons that can be coloured
- thumb and trough that can be individually coloured
- tracks movement in scrollable widget
- thumb resizes with size of scroll area
Edit
I've revised the thumb code to fix the click and drag scrolling:
import tkinter as tk
class MyScrollbar(tk.Canvas):
def __init__(self, master, *args, **kwargs):
self._scroll_kwargs = { 'command':None,
'orient':'vertical',
'buttontype':'round',
'buttoncolor':'#006cd9',
'troughcolor':'#00468c',
'thumbtype':'rectangle',
'thumbcolor':'#4ca6ff',
}
kwargs = self._sort_kwargs(kwargs)
if self._scroll_kwargs['orient'] == 'vertical':
if 'width' not in kwargs:
kwargs['width'] = 10
elif self._scroll_kwargs['orient'] == 'horizontal':
if 'height' not in kwargs:
kwargs['height'] = 10
else:
raise ValueError
if 'bd' not in kwargs:
kwargs['bd'] = 0
if 'highlightthickness' not in kwargs:
kwargs['highlightthickness'] = 0
tk.Canvas.__init__(self, master, *args, **kwargs)
self.elements = { 'button-1':None,
'button-2':None,
'trough':None,
'thumb':None}
self._oldwidth = 0
self._oldheight = 0
self._sb_start = 0
self._sb_end = 1
self.bind('<Configure>', self._resize)
self.tag_bind('button-1', '<Button-1>', self._button_1)
self.tag_bind('button-2', '<Button-1>', self._button_2)
self.tag_bind('trough', '<Button-1>', self._trough)
self._track = False
self.tag_bind('thumb', '<ButtonPress-1>', self._thumb_press)
self.bind('<ButtonRelease-1>', self._thumb_release)
# self.bind('<Leave>', self._thumb_release)
self.bind('<Motion>', self._thumb_track)
def _sort_kwargs(self, kwargs):
to_remove = []
for key in kwargs:
if key in [ 'buttontype', 'buttoncolor', 'buttonoutline',
'troughcolor', 'troughoutline',
'thumbcolor', 'thumbtype', 'thumboutline',
'command', 'orient']:
self._scroll_kwargs[key] = kwargs[key] # add to custom dict
to_remove.append(key)
for key in to_remove:
del kwargs[key]
return kwargs
def _get_colour(self, element):
if element in self._scroll_kwargs: # if element exists in settings
return self._scroll_kwargs[element]
if element.endswith('outline'): # if element is outline and wasn't in settings
return self._scroll_kwargs[element.replace('outline', 'color')] # fetch default for main element
def _width(self):
return self.winfo_width() - 2 # return width minus 2 pixes to ensure fit in canvas
def _height(self):
return self.winfo_height() - 2 # return height minus 2 pixes to ensure fit in canvas
def _resize(self, event):
width = self._width()
height = self._height()
if self.elements['button-1']: # exists
# delete element if vertical scrollbar and width changed
# or if horizontal and height changed, signals button needs to change
if (((self._oldwidth != width) and (self._scroll_kwargs['orient'] == 'vertical')) or