解决办法来了:
非常重要
有一些奇怪的错误我不知道如何修复(至少)关于性能和东西,这使得这个解决方案非常不可用,我的意思是理论上你可以在这个上面放置更多的小部件并滚动而不是通常的方法但是取决于 CPU,它可能会工作,也可能无法正常工作。我就简单写个分页回答
from tkinter import (Tk, Frame, Label, Scrollbar, Canvas,
DoubleVar, Entry, Button, StringVar, TclError)
from tkinter.ttk import Progressbar
class EndlessScroll(Canvas):
def __init__(self, master, update_mode='passive', scrollbar=None, **kwargs):
"""update_mode:
'passive' (recommended and default): widgets have to be accessed (Text, Entry...)
or using Frame, DON'T use if only single widget per line,
USE Frame with this
'active': recommended only if single widget
per line (best with Label
but can use Button too)"""
Canvas.__init__(self, master, **kwargs)
self.master = master
self._widget_heights = []
self._start_index = 0
self._widgets = []
self.temp_frame = None
self.__got_height = False
self.__initialised_placement = False
self.__height_counter = 0
self.yscrollincrement = None
self._update_mode = update_mode
self.scrollbar = scrollbar
self.__load_progress_tracker = DoubleVar(master=self.master, value=0.0)
self.__percent_tracker = StringVar(master=self.master, value='0.0%')
self._allow_mouse_control = False
self.bind('<Enter>', lambda e: setattr(self, '_allow_mouse_control', True))
self.bind('<Leave>', lambda e: setattr(self, '_allow_mouse_control', False))
self.bind('<MouseWheel>', self.__scroll_with_mouse)
for key, value in kwargs.items():
setattr(self, key, value)
def __scroll_with_mouse(self, event):
if not self._allow_mouse_control:
return
self.yscroll('scroll', (-1 * (event.delta/120)), 'units')
def set_scrollbar(self, scrollbar):
self.scrollbar = scrollbar
def init_after_widgets(self):
self.temp_frame = Frame(self)
self._widgets = self.winfo_children()[:-1]
self.temp_frame.place(x=0, y=0, relwidth=1, relheight=1)
self.update()
Label(self.temp_frame, text='Loading...').pack(expand=True)
Progressbar(self.temp_frame,
orient='horizontal',
variable=self.__load_progress_tracker,
length=self.temp_frame.winfo_width()
- self.temp_frame.winfo_width() // 10).pack(expand=True)
Label(self.temp_frame, textvariable=self.__percent_tracker).pack(expand=True)
self.after(100, self._get_widget_heights, 0)
def _get_widget_heights(self, index):
length = len(self._widgets)
self.after(100, self.__get_widget_heights, index, self._widgets, length)
def __get_widget_heights(self, index, widgets, length):
if index >= length:
self.__got_height = True
self.temp_frame.place_forget()
self._initial_placement(widgets)
self.update_idletasks()
self.__update()
self.yscroll(None, 0)
return
id_ = self.create_window(0, 0, window=widgets[index], anchor='nw')
self.update()
try:
self._widget_heights.append(widgets[index].winfo_height())
self.delete(id_)
except TclError:
pass
percent = (index + 1) * 100 / length
self.__load_progress_tracker.set(value=percent)
self.__percent_tracker.set(value=f'{percent: .2f}%')
self.after(1, self.__get_widget_heights, index + 1, widgets, length)
def yscroll(self, *args):
if not self.__initialised_placement or not self.scrollbar:
return
type_, fraction = args[0], args[1]
parent_height = self.winfo_height()
if type_ == 'scroll':
units = args[2]
k = float(fraction)
if k < -1.0:
k = -1.0
elif k > 1.0:
k = 1.0
top, bottom, *_ = self.scrollbar.get()
length = (bottom - top) if not self.yscrollincrement else self.yscrollincrement
if (top == 0.0 and k < 0) or (bottom == 1.0 and k > 0):
return
if 0 < top < length:
length = top
if 0 < (1 - bottom) < length:
length = 1 - bottom
if units == 'units':
fraction = top + (k * length)
elif units == 'pages':
return
# fraction = top + (k * (0.9 * parent_height) * length)
sum_height = sum(self._widget_heights)
scroll_height = float(fraction) * sum_height
height_counter = 0
for index_, height in enumerate(self._widget_heights):
height_counter += height
if height_counter > scroll_height:
self._start_index = index_
self.__height_counter = -(height - (height_counter - scroll_height))
break
self.scrollbar.set(float(fraction), float(fraction) + parent_height / sum_height)
if self._update_mode == 'passive':
self.__update()
def __update(self, _call_from_self=False):
if _call_from_self and self._update_mode == 'passive':
self.after(10, self.__update, True)
return
parent_height = self.winfo_height()
height_counter = self.__height_counter
self.delete('all')
for height, widget in zip(self._widget_heights[self._start_index:], self._widgets[self._start_index:]):
if height_counter > parent_height:
break
self.create_window(0, height_counter, window=widget, anchor='nw')
height_counter += height
self.after(10, self.__update, True)
def _initial_placement(self, widgets):
parent_height = self.winfo_height()
height_counter = 0
for height, widget in zip(self._widget_heights, widgets):
if height_counter > parent_height:
self.__initialised_placement = True
return
self.create_window(0, height_counter, window=widget, anchor='nw')
height_counter += height
def create_scroller():
endless_scroll = EndlessScroll(root, yscrollincrement=0.1)
endless_scroll.pack(side='left')
for i in range(100):
frame = Frame(endless_scroll)
Label(frame, text=str(i).zfill(4)).pack()
scrollbar = Scrollbar(root, command=endless_scroll.yscroll)
scrollbar.pack(side='right', fill='y')
endless_scroll.set_scrollbar(scrollbar)
endless_scroll.init_after_widgets()
root = Tk()
create_scroller()
root.mainloop()
MAIN_EDIT:
我稍微完善了小部件,其余说明仍然有效,但我修复了以下问题,使用小部件,以便您可以看到添加的最后一个小部件(是索引问题),现在您可以看到所有这些。添加了鼠标滚动和使用滚动条的箭头滚动(仍然无法通过单击滚动条来移动它,也不知道该怎么做(还))。现在还显示百分比,减少了一些等待时间。现在,如果您在加载过程中关闭窗口,它也会捕获引发的错误。
基本上,您只需转到 create_scroller() 函数定义并根据需要调整循环。
重要(建议)
我强烈建议在导入某些内容时不要使用通配符 (*),您应该导入您需要的内容,例如from module import Class1, func_1, var_2 等等或导入整个模块:import module 然后你也可以使用别名:import module as md 或类似的东西,关键是不要导入所有东西,除非你真的知道你在做什么;名称冲突是问题所在。
这里的主要说明是,在将小部件添加到 EndlessScroll 小部件后,您必须放置 .init_after_widgets() 方法(不要打包或添加任何东西(如示例中所示)) . 请注意特殊的小部件,它也会尝试修复它以使其正常工作,而且非常重要的是它目前仅适用于拖动滚动条的滑块(也会修复它(至少尝试)。罢工>
问题是它现在可以工作了,理论上它可以显示任意数量的小部件(尝试使用 50000 但这非常滞后,我不得不关闭它但使用 3000 它就像一个魅力),还有一件很棒的事情是窗口在“加载”小部件时是响应式的(加载对于获取小部件的高度至关重要)所以是的,您可以尝试自己改进它,但我也会自己尝试这样做,但稍后会。
编辑1:
例如,如果您想将更多小部件放在一起,您可以使用框架:
for i in range(100):
frame = Frame(endless_scroll)
Label(frame, text=f'Entry {i}:').pack(side='left')
Entry(frame).pack(side='right')
在一定程度上解决了这个问题。请参阅下面的编辑。
由于.__update() 循环,此特定示例的条目存在轻微问题,否则静态小部件应该完全正常工作(按钮问题较少,但可能更难按下它们,Entry 和 Text 小部件可能无法使用)
EDIT2:
添加了更改更新模式的选项以处理上述问题(阅读EndlessScroll 类中的文档字符串)。问题在于使用 Frame,它基本上打破了现在的“主动”模式循环(可能是因为数据量),因此您必须使用“被动”模式,但这意味着更新不太流畅(如果你不使用框架)。所以你也可以使用“活动”模式,但只有当你每行有一个小部件时(希望@TheLizzard通过制作某种自定义.grid()方法或某事来解决这个问题,在cmets中)然后它更新非常顺利。因此,目前除非确实需要在一列中有很多小部件并将它们扩展到其他列,否则我建议使用添加滚动的常用方法。当然,除非您可以在拖动滑块时稍微闪烁一下,否则其他方法不会产生该问题。
如果你有任何问题,问他们!