基于 tkinter 的程序中彩色滚动条的其他选项?



所以在下班或阅读帖子并查看tkinter的文档后,我发现在Windows机器上,tkinter滚动条的颜色选项将不起作用,因为滚动条直接从Windows获取其主题。我的问题是默认主题的颜色确实与我的程序冲突,我正在尝试找到一种不涉及导入不同 GUI 包(如 PyQt)的解决方案(我在工作中无法访问 pip,所以这是一个问题获取新包)

除了使用单独的软件包之外,任何人都可以指出我有关如何编写自己的侧边栏以滚动文本小部件的一些文档。到目前为止,我所发现的甚至接近我希望能够做的就是这个问题的答案。(使用 ttk 样式更改 tkinter 中滚动条的外观)

从我所看到的,该示例仅更改滚动条的背景,因此我仍然无法使用该示例。我在用于配置样式的其中一行上遇到错误。

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

不确定如何处理此错误,因为我只是遵循用户示例,我不确定为什么它对他们有用,但对我不起作用。

到目前为止,我尝试的是:

我如何创建文本框和与之配套的滚动条。

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

按照此处的文档进行操作,我下面的尝试似乎在Windows机器上没有做任何事情。正如我在其他帖子中读到的那样,这与滚动条从窗口本地获取其主题有关。

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

我想这一切都归结为:

是否可以创建我自己的侧边栏(我可以为每个主题更改颜色),而无需导入其他 python 包?如果是这样,我应该从哪里开始,或者有人可以将我链接到文档,因为我的搜索总是接缝将我带回 Tkinter 滚动条信息。由于这些 config() 选项确实适用于 linux,因此它们不适用于 windows。

不是一个完整的答案,但您是否考虑过创建自己的滚动条:

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()

我已经留下了我的评论,对于我的一生,我似乎无法单击和拖动拇指来正常工作,但它是一个具有以下功能的简单滚动条:

  • 可以着色的上下按钮
  • 拇指和槽可以单独着色
  • 在可滚动小部件中跟踪移动
  • 拇指随滚动区域的大小调整大小

编辑

我已经修改了拇指代码以修复单击和拖动滚动:

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()

所以我修复了计算以计算出新的滚动到位置的位置,并从跟踪和发布事件的拇指标签上的绑定更改为在整个画布上绑定,因此如果用户快速滚动,则在松开鼠标时仍会释放绑定.
我已经注释掉了光标离开画布时的绑定,因此行为更接近于模仿现有滚动bar,但如果您希望它在鼠标离开小部件时停止滚动,可以重新启用.
至于创建两个类,上面的修改代码允许您使用orient关键字,因此您可以删除此类(样式更改)代替默认滚动条,如底部的示例所示。

最新更新