7

So after hours or reading post and looking at the documentation for tkinter I have found that on windows machines the color options for tkinter scrollbar will not work due to the scrollbar getting its theme from windows directly. My problem is the color of the default theme really clashes with my program and I am trying to find a solution that does not involve importing a different GUI package such as PyQt (I don't have access to pip at work so this is a problem to get new packages)

Aside from using a separate package can anyone point me towards some documentation on how to write my own sidebar for scrolling through the text widget. All I have found so far that is even close to what I want to be able to do is an answer on this question. (Changing the apperance of a scrollbar in tkinter using ttk styles)

From what I can see the example is only changing the background of the scrollbar and with that I was still unable to use the example. I got an error on one of the lines used to configure the style.

    style.configure("My.Horizontal.TScrollbar", *style.configure("Horizontal.TScrollbar"))
TypeError: configure() argument after * must be an iterable, not NoneType

Not sure what to do with this error because I was just following the users example and I am not sure as to why it worked for them but not for me.

What I have tried so far is:

How I create my text box and the scrollbars to go with it.

root.text = Text(root, undo = True)
root.text.grid(row = 0, column = 1, columnspan = 1, rowspan = 1, padx =(5,5), pady =(5,5), sticky = W+E+N+S)
root.text.config(bg = pyFrameColor, fg = "white", font=('times', 16))
root.text.config(wrap=NONE)
vScrollBar = tkinter.Scrollbar(root, command=root.text.yview)
hScrollBar = tkinter.Scrollbar(root, orient = HORIZONTAL, command=root.text.xview)
vScrollBar.grid(row = 0, column = 2, columnspan = 1, rowspan = 1, padx =1, pady =1, sticky = E+N+S)
hScrollBar.grid(row = 1 , column = 1, columnspan = 1, rowspan = 1, padx =1, pady =1, sticky = S+W+E)
root.text['yscrollcommand'] = vScrollBar.set
root.text['xscrollcommand'] = hScrollBar.set

Following the documentation here My attempt below does not appear to do anything on windows machine. As I have read on other post this has to do with the scrollbar getting its theme natively from windows.

vScrollBar.config(bg = mainBGcolor)
vScrollBar['activebackground'] = mainBGcolor
hScrollBar.config(bg = mainBGcolor)
hScrollBar['activebackground'] = mainBGcolor

I guess it all boils down to:

Is it possible to create my own sidebar (with colors I can change per theme) without the need to import other python packages? If so, where should I start or can someone please link me to the documentation as my searches always seam to lead me back to Tkinter scrollbar Information. As these config() options do work for linux they do not work for windows.

Community
  • 1
  • 1
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • @ Christian Dean, It is not. I have already linked that question in my question as a reference to what I have already tried. – Mike - SMT May 07 '17 at 22:05
  • Sorry, I didn't see that link when I was reading over your question. Weird though, it worked for me on my windows machine. Are you using an older version of windows perhaps? – Christian Dean May 07 '17 at 22:11
  • @Christian Dean: I did try however I got a error that I show in my question and I have not been able to resolve it. Also From what I was reading it was only editing the background of the scroll bar. I need to be able to change all aspects of the scroll bar. The arrows, the background, and the scrolling bar itself. I don't mind doing the work to write my own scroll bar but I have been unable to find a starting point to do so. – Mike - SMT May 07 '17 at 22:14

1 Answers1

10

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
                ((self._oldheight != height) and (self._scroll_kwargs['orient'] == 'horizontal'))):
                self.delete(self.elements['button-1'])
                self.elements['button-1'] = None
        if not self.elements['button-1']: # create
            size = width if (self._scroll_kwargs['orient'] == 'vertical') else height
            rect = (0,0,size, size)
            fill = self._get_colour('buttoncolor')
            outline = self._get_colour('buttonoutline')
            if (self._scroll_kwargs['buttontype'] == 'round'):
                self.elements['button-1'] = self.create_oval(rect, fill=fill, outline=outline, tag='button-1')
            elif (self._scroll_kwargs['buttontype'] == 'square'):
                self.elements['button-1'] = self.create_rectangle(rect, fill=fill, outline=outline, tag='button-1')
            
        if self.elements['button-2']: # exists
            coords = self.coords(self.elements['button-2'])
            # 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
                ((self._oldheight != height) and (self._scroll_kwargs['orient'] == 'horizontal'))):
                self.delete(self.elements['button-2'])
                self.elements['button-2'] = None
            # if vertical scrollbar and height changed button needs to move
            elif ((self._oldheight != height) and (self._scroll_kwargs['orient'] == 'vertical')):
                self.move(self.elements['button-2'], 0, height-coords[3])
            # if horizontal scrollbar and width changed button needs to move
            elif ((self._oldwidth != width) and (self._scroll_kwargs['orient'] == 'horizontal')):
                self.move(self.elements['button-2'], width-coords[2], 0)
        if not self.elements['button-2']: # create
            if (self._scroll_kwargs['orient'] == 'vertical'):
                rect = (0,height-width,width, height)
            elif (self._scroll_kwargs['orient'] == 'horizontal'):
                rect = (width-height,0,width, height)
            fill = self._get_colour('buttoncolor')
            outline = self._get_colour('buttonoutline')
            if (self._scroll_kwargs['buttontype'] == 'round'):
                self.elements['button-2'] = self.create_oval(rect, fill=fill, outline=outline, tag='button-2')
            elif (self._scroll_kwargs['buttontype'] == 'square'):
                self.elements['button-2'] = self.create_rectangle(rect, fill=fill, outline=outline, tag='button-2')
        
        if self.elements['trough']: # exists
            coords = self.coords(self.elements['trough'])
            # delete element whenever width or height changes
            if (self._oldwidth != width) or (self._oldheight != height):
                self.delete(self.elements['trough'])
                self.elements['trough'] = None
        if not self.elements['trough']: # create
            if (self._scroll_kwargs['orient'] == 'vertical'):
                rect = (0, int(width/2), width, height-int(width/2))
            elif (self._scroll_kwargs['orient'] == 'horizontal'):
                rect = (int(height/2), 0, width-int(height/2), height)
            fill = self._get_colour('troughcolor')
            outline = self._get_colour('troughoutline')
            self.elements['trough'] = self.create_rectangle(rect, fill=fill, outline=outline, tag='trough')

        self.set(self._sb_start, self._sb_end) # hacky way to redraw thumb without moving it
        self.tag_raise('thumb') # ensure thumb always on top of trough
            
        self._oldwidth = width
        self._oldheight = height
        
    def _button_1(self, event):
        command = self._scroll_kwargs['command']
        if command:
            command('scroll', -1, 'pages')
        return 'break'
    
    def _button_2(self, event):
        command = self._scroll_kwargs['command']
        if command:
            command('scroll', 1, 'pages')
        return 'break'
        
    def _trough(self, event):
#       print('trough: (%s, %s)' % (event.x, event.y))
        width = self._width()
        height = self._height()
        
        coords = self.coords(self.elements['trough'])
        
        if (self._scroll_kwargs['orient'] == 'vertical'):
            trough_size = coords[3] - coords[1]
        elif (self._scroll_kwargs['orient'] == 'horizontal'):
            trough_size = coords[2] - coords[0]
#       print('trough size: %s' % trough_size)
        
        size = (self._sb_end - self._sb_start) / 1
        if (self._scroll_kwargs['orient'] == 'vertical'):
            thumbrange = height - width
        elif (self._scroll_kwargs['orient'] == 'horizontal'):
            thumbrange = width - height
        thumbsize = int(thumbrange * size)
        
        if (self._scroll_kwargs['orient'] == 'vertical'):
            thumboffset = int(thumbrange * self._sb_start) + int(width/2)
        elif (self._scroll_kwargs['orient'] == 'horizontal'):
            thumboffset = int(thumbrange * self._sb_start) + int(height/2)
        thumbpos = int(thumbrange * size / 2) + thumboffset
        
        command = self._scroll_kwargs['command']
        if command:
            if (((self._scroll_kwargs['orient'] == 'vertical') and (event.y < thumbpos)) or
                ((self._scroll_kwargs['orient'] == 'horizontal') and (event.x < thumbpos))):
                command('scroll', -1, 'pages')
            elif (((self._scroll_kwargs['orient'] == 'vertical') and (event.y > thumbpos)) or
                ((self._scroll_kwargs['orient'] == 'horizontal') and (event.x > thumbpos))):
                command('scroll', 1, 'pages')
        return 'break'
    
    def _thumb_press(self, event):
        self._track = True
        
    def _thumb_release(self, event):
        self._track = False
            
    def _thumb_track(self, event):
#       print('track')
        if self._track:
            width = self._width()
            height = self._height()
#           print("window size: (%s, %s)" % (width, height))
            
            size = (self._sb_end - self._sb_start) / 1
            
            coords = self.coords(self.elements['trough'])
#           print('trough coords: %s' % coords)
            
            if (self._scroll_kwargs['orient'] == 'vertical'):
                trough_size = coords[3] - coords[1]
                thumbrange = height - width
            elif (self._scroll_kwargs['orient'] == 'horizontal'):
                trough_size = coords[2] - coords[0]
                thumbrange = width - height
#           print('trough size: %s' % trough_size)
                
            thumbsize = int(thumbrange * size)
            
            if (self._scroll_kwargs['orient'] == 'vertical'):
                pos = max(min(trough_size, event.y - coords[1] - (thumbsize/2)), 0)
            elif (self._scroll_kwargs['orient'] == 'horizontal'):
                pos = max(min(trough_size, event.x - coords[0] - (thumbsize/2)), 0)
            
#           print('pos: %s' % pos)
            
            point = pos / trough_size
#           print('point: %s' % point)
            
            command = self._scroll_kwargs['command']
            if command:
                command('moveto', point)
            return 'break'
        
    def set(self, *args):
#       print('set: %s' % str(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._width()
        height = self._height()
        
        if oldsize != size:
            self.delete(self.elements['thumb'])
            self.elements['thumb'] = None
        
        if (self._scroll_kwargs['orient'] == 'vertical'):
            thumbrange = height - width
            thumboffset = int(thumbrange * self._sb_start) + int(width/2)
        elif (self._scroll_kwargs['orient'] == 'horizontal'):
            thumbrange = width - height
            thumboffset = int(thumbrange * self._sb_start) + int(height/2)
        thumbsize = int(thumbrange * size)
        
        if not self.elements['thumb']: # create
            if (self._scroll_kwargs['orient'] == 'vertical'):
                rect = (0, thumboffset,width, thumbsize+thumboffset)
            elif (self._scroll_kwargs['orient'] == 'horizontal'):
                rect = (thumboffset, 0, thumbsize+thumboffset, height)
            fill = self._get_colour('thumbcolor')
            outline = self._get_colour('thumboutline')
            if (self._scroll_kwargs['thumbtype'] == 'round'):
                self.elements['thumb'] = self.create_oval(rect, fill=fill, outline=outline, tag='thumb')
            elif (self._scroll_kwargs['thumbtype'] == 'rectangle'):
                self.elements['thumb'] = self.create_rectangle(rect, fill=fill, outline=outline, tag='thumb')
        else: # move
            coords = self.coords(self.elements['thumb'])
            if (self._scroll_kwargs['orient'] == 'vertical'):
                if (thumboffset != coords[1]):
                    self.move(self.elements['thumb'], 0, thumboffset-coords[1])
            elif (self._scroll_kwargs['orient'] == 'horizontal'):
                if (thumboffset != coords[1]):
                    self.move(self.elements['thumb'], thumboffset-coords[0], 0)
        return 'break'
        
if __name__ == '__main__':
    root = tk.Tk()
    root.grid_rowconfigure(1, weight=1)
    root.grid_columnconfigure(1, weight=1)
    
    root.grid_rowconfigure(3, weight=1)
    root.grid_columnconfigure(3, weight=1)
    
    lb = tk.Listbox(root)
    lb.grid(column=1, row=1, sticky="nesw")
    for num in range(0,100):
        lb.insert('end', str(num)*100)
        
    sby1 = MyScrollbar(root, width=50, command=lb.yview)
    sby1.grid(column=2, row=1, sticky="nesw")
    
    sby2 = MyScrollbar(root, width=50, command=lb.yview, buttontype='square', thumbtype='round')
    sby2.grid(column=4, row=1, sticky="nesw")
    
    sbx1 = MyScrollbar(root, height=50, command=lb.xview, orient='horizontal', buttoncolor='red', thumbcolor='orange', troughcolor='green')
    sbx1.grid(column=1, row=2, sticky="nesw")
    
    sbx2 = MyScrollbar(root, height=50, command=lb.xview, orient='horizontal', thumbtype='round')
    sbx2.grid(column=1, row=4, sticky="nesw")
    
    def x_set(*args):
        sbx1.set(*args)
        sbx2.set(*args)
        
    def y_set(*args):
        sby1.set(*args)
        sby2.set(*args)
    
    lb.configure(yscrollcommand=y_set, xscrollcommand=x_set)
    root.mainloop()

so I've fixed the calculation to work out where the new scroll to position will be, and changed from binding on the thumb tag for the track and release events to binding on the whole canvas, so if the user scrolls quickly the binding will still release when the mouse is let go.
I've commented out the binding for when the cursor leaves the canvas so the behavior more closely mimics the existing scroll bar, but can be re enabled if you want it to stop scrolling if the mouse leaves the widget.
As for making two classes, the amended code above lets you use the orient keyword so you can just drop this class (with styling changes) in place of the default scrollbar, as shown in the example at the bottom.

Community
  • 1
  • 1
James Kent
  • 5,763
  • 26
  • 50
  • Its defiantly a start. The scroll bar kinda works. Needs to be able to click&drag the bar. Could you post some of your resources that you used to write this scroll bar. As the code you posted kinda helps I would like to know where I can go to actually learn how to do this myself. – Mike - SMT May 11 '17 at 17:40
  • yeah i know, i considered stripping that bit out cause it doesn't currently work, but left it in as a rough example, going to have another look tomorrow with a fresh mind, but i used the docs on effbot: http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm http://effbot.org/tkinterbook/listbox.htm http://effbot.org/tkinterbook/scrollbar.htm http://effbot.org/tkinterbook/canvas.htm – James Kent May 11 '17 at 17:46
  • and should you wish for a bit of a more sophisticated look the top and bottom buttons could be replaced with tkinter/ttk buttons, or images – James Kent May 11 '17 at 17:47
  • I was able to use your class and make 2 scrollbars. One for `yview` and one for `xview`. The one for x slides vertically instead of horizontally but it does scroll the text horizontally as it should. I think with this I can work on the design part of it and correct the orientation of the `xview` scrollbar. Thank you for the links as I will be reading through them all to attempt to make my own scroll bar similar to the one you gave as an example. If you would add your links to the answer so others see it that would be great. Thanks for the answer. I have accepted it as the answer for my bounty. – Mike - SMT May 11 '17 at 18:11
  • I guess I have to wait 2 more hours before I can award the bounty. I will do that then. – Mike - SMT May 11 '17 at 18:12
  • just to let you know i've made some edits, fixed the click and drag scrolling and added support for a horizontal scrollbar – James Kent May 12 '17 at 09:01
  • Thanks for the update. That did fix the scrolling problem. And the it does orientate properly. :D. I will play with it and see if I can make some visual improvements. I think it would have taken me at least a week or town to come up with something remotely as functional as what you have provided. Thanks again! – Mike - SMT May 12 '17 at 14:35
  • not a problem, as noted in answer, i'd been looking at it too long and was doing stupid things in the maths, it's not fully functional as it can't be reconfigured after creation, but for a scrollbar how often is that really needed... now that I've done it i'll probably look at refactoring it to provide a draw call for each element for easy modification – James Kent May 12 '17 at 14:41
  • Sounds a bit more complicated than I am currently able to work with. I will be spending some time reading those links you posted just to understand what you have done so far. – Mike - SMT May 12 '17 at 14:44
  • So I was able to "reconfigure" so to speak by having a function that first `try:`'s to `.destroy()` the scrollbar's if no error then it recreates the scrollbars with the new variables sent to the function. In the even of an exception. For example on first load then it just creates the scroll bars instead of trying to `.destroy()` first. – Mike - SMT May 12 '17 at 19:47