Моя цель - автоматически прокрутить кадр tkinter как пользователи / в виджеты за пределами видимой области. У меня это работает, используя общее количество строк и строку сфокусированного виджета для вычисления позиции прокрутки, но это работает только для строк одинаковой высоты. (В моей программе непредсказуемое количество строк разной высоты).
Я использую фрейм для поддержки менеджера сетки, так как без него мне пришлось бы использовать абсолютное размещение виджетов в окне. Это было бы ужасно быстро с динамически создаваемыми строками. Недостатком является отсутствие доступа к координатам холста для виджетов (насколько я могу судить).
Пример кода показывает рабочую простую версию с равными высотными строками. Любые предложения о том, как обрабатывать непредсказуемое количество строк разной высоты? Я делаю неправильные предположения или полностью пропускаю другой подход?
(Фактическая область прокрутки программы имеет разделы заголовка, много столбцов и до 200 строк виджетов / ярлыков ввода с текстовыми / пустыми метками-разделителями).
import tkinter as tk
class ScrollFrame(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, bd = 4)
self.parent = parent
self.page_start_row = 0
self.page_rows = 5
self.vsb = tk.Scrollbar(self)
self.canvas = tk.Canvas(self, yscrollcommand = self.vsb.set)
self.vsb.config(command = self.canvas.yview)
self.vsb.grid(row = 0, column = 1, sticky = 'ns')
self.canvas.grid(row = 0, column = 0, sticky = 'nesw')
self.grid_rowconfigure(0, weight = 1)
self.grid_columnconfigure(0, weight = 1)
self.interior = tk.Frame(self.canvas)
self.interior.grid_rowconfigure(1, weight = 1)
self.interior.grid_columnconfigure(1, weight = 1)
self.canvas.create_window(0, 0, window =
self.interior, anchor = 'nw')
self.canvas.config(scrollregion = self.canvas.bbox('all'))
def moveto(self, event):
# I'm inflexible and not elegant :(
if event.widget.row_index < self.page_start_row:
self.page_start_row = event.widget.row_index
elif event.widget.row_index > self.page_start_row + self.page_rows:
self.page_start_row = (event.widget.row_index - self.page_rows)
self.canvas.yview_moveto(self.page_start_row/self.parent.row_count)
print(event.y) #Test - I just return "??" (without quotes)
class CustomEntry(tk.Entry):
def __init__(self, parent_frame, row_index, *args, **kwargs):
tk.Entry.__init__(self, parent_frame, *args, **kwargs)
self.parent_frame = parent_frame
self.row_index = row_index
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry('200x200')
self.resizable(False, False)
self.scroll_frame = ScrollFrame(self)
self.scroll_frame.grid(row = 0, column = 0, sticky = 'nesw')
self.grid_rowconfigure(0, weight = 1)
self.grid_columnconfigure(0, weight = 1)
control_frame = tk.Frame(self)
add_rows_btn = tk.Button(control_frame, text = 'Add More Rows',
command = self.add_rows)
add_rows_btn.grid(row = 0, column = 0)
control_frame.grid(row = 1, column = 0, sticky = 'esw')
self.row_count = 0
self.add_rows()
def add_rows(self):
for row_i in range(self.row_count, self.row_count + 10):
entry = CustomEntry(self.scroll_frame.interior, row_i)
entry.grid(row = row_i, column = 0)
entry.bind('<FocusIn>',
lambda event: self.scroll_frame.moveto(event))
self.row_count += 10
self.scroll_frame.interior.update_idletasks()
self.scroll_frame.canvas.config(scrollregion =
self.scroll_frame.canvas.bbox('all'))
if __name__ == '__main__':
app = SampleApp()
app.mainloop()
Примечание: высота, установленная в self.geometry('200x200')
, соответствует высоте 5 строк в self.page_rows = 5
в OS X, но, возможно, потребуется настроить для других платформ. Если выключен, он будет прокручиваться в неподходящее время. Это еще один пример, когда основание его на строках не так гибко, как использование геометрии / координат.