Подтвердить что ты не робот

Добавление полосы прокрутки в группу виджетов в Tkinter

Я использую Python для анализа записей из файла журнала и отображения содержимого записи с помощью Tkinter, и пока это отлично. Вывод представляет собой сетку виджетов ярлыков, но иногда на экране появляется больше строк, чем может отображаться на экране. Я бы хотел добавить панель прокрутки, которая выглядит очень легко, но я не могу понять.

Документация подразумевает, что только виджеты List, Textbox, Canvas и Entry поддерживают интерфейс полосы прокрутки. Ни один из них не подходит для отображения сетки виджетов. Можно поместить произвольные виджеты в виджет Canvas, но вам, похоже, нужно использовать абсолютные координаты, поэтому я не смог бы использовать диспетчер компоновки сетки?

Я пробовал помещать сетку виджета в фрейм, но это, похоже, не поддерживает интерфейс полосы прокрутки, поэтому это не работает:

mainframe = Frame(root, yscrollcommand=scrollbar.set)

Может кто-нибудь предложить способ обойти это ограничение? Мне бы не хотелось переписывать в PyQt и увеличить размер исполняемого изображения на столько, просто добавив прокрутку!

4b9b3361

Ответ 1

Обзор

Вы можете связывать полосы прокрутки только с несколькими виджетами, а корневой виджет и Frame не входят в эту группу виджетов.

Наиболее распространенным решением является создание виджета холста и сопоставление полос прокрутки с этим виджетами. Затем в этот холст встроен фрейм, содержащий виджеты ярлыков. Определите ширину/высоту кадра и отправьте его в опцию canvas scrollregion, чтобы область прокрутки точно соответствовала размеру кадра.

Рисование текстовых элементов непосредственно на холсте не очень сложно, поэтому вам может потребоваться пересмотреть этот подход, если решение, встроенное в рамку, будет слишком сложным. Поскольку вы создаете сетку, координаты каждого текстового элемента будут очень легко вычисляться, особенно если каждая строка имеет одинаковую высоту (что, вероятно, это если вы используете один шрифт).

Для рисования непосредственно на холсте просто определите высоту строки шрифта, который вы используете (и для этого есть команды). Тогда каждая координата y row*(lineheight+spacing). Координата x будет фиксированным числом, основанным на самом широком элементе в каждом столбце. Если вы укажете все теги для столбца, в котором он находится, вы можете настроить координату x и ширину всех элементов в столбце с помощью одной команды.

Объектно-ориентированное решение

Вот пример решения, встроенного в рамку, с использованием объектно-ориентированного подхода:

import tkinter as tk

class Example(tk.Frame):
    def __init__(self, root):

        tk.Frame.__init__(self, root)
        self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
        self.frame = tk.Frame(self.canvas, background="#ffffff")
        self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.vsb.set)

        self.vsb.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas.create_window((4,4), window=self.frame, anchor="nw", 
                                  tags="self.frame")

        self.frame.bind("<Configure>", self.onFrameConfigure)

        self.populate()

    def populate(self):
        '''Put in some fake data'''
        for row in range(100):
            tk.Label(self.frame, text="%s" % row, width=3, borderwidth="1", 
                     relief="solid").grid(row=row, column=0)
            t="this is the second column for row %s" %row
            tk.Label(self.frame, text=t).grid(row=row, column=1)

    def onFrameConfigure(self, event):
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

if __name__ == "__main__":
    root=tk.Tk()
    Example(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

Процедурное решение

Вот решение, которое не использует объекты:

import tkinter as tk

def populate(frame):
    '''Put in some fake data'''
    for row in range(100):
        tk.Label(frame, text="%s" % row, width=3, borderwidth="1", 
                 relief="solid").grid(row=row, column=0)
        t="this is the second column for row %s" %row
        tk.Label(frame, text=t).grid(row=row, column=1)

def onFrameConfigure(canvas):
    '''Reset the scroll region to encompass the inner frame'''
    canvas.configure(scrollregion=canvas.bbox("all"))

root = tk.Tk()
canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
frame = tk.Frame(canvas, background="#ffffff")
vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)

vsb.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((4,4), window=frame, anchor="nw")

frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))

populate(frame)

root.mainloop()

Примечание:, чтобы сделать эту работу в python 2.x, используйте Tkinter, а не Tkinter в инструкции import

Ответ 2

Сделать прокручиваемым

Используйте этот удобный класс, чтобы сделать прокручиваемый фрейм, содержащий ваши виджеты. Следуй этим шагам:

  1. создать рамку
  2. показать его (упаковка, сетка и т.д.)
  3. сделать его прокручиваемым
  4. добавить виджеты внутри него
  5. вызвать метод update()

import tkinter as tk
from tkinter import ttk

class Scrollable(tk.Frame):
    """
       Make a frame scrollable with scrollbar on the right.
       After adding or removing widgets to the scrollable frame, 
       call the update() method to refresh the scrollable area.
    """

    def __init__(self, frame, width=16):

        scrollbar = tk.Scrollbar(frame, width=width)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)

        self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set)
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar.config(command=self.canvas.yview)

        self.canvas.bind('<Configure>', self.__fill_canvas)

        # base class initialization
        tk.Frame.__init__(self, frame)         

        # assign this obj (the inner frame) to the windows item of the canvas
        self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW)


    def __fill_canvas(self, event):
        "Enlarge the windows item to the canvas width"

        canvas_width = event.width
        self.canvas.itemconfig(self.windows_item, width = canvas_width)        

    def update(self):
        "Update the canvas and the scrollregion"

        self.update_idletasks()
        self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))

Пример использования

root = tk.Tk()

header = ttk.Frame(root)
body = ttk.Frame(root)
footer = ttk.Frame(root)
header.pack()
body.pack()
footer.pack()

ttk.Label(header, text="The header").pack()
ttk.Label(footer, text="The Footer").pack()


scrollable_body = Scrollable(body, width=32)

for i in range(30):
    ttk.Button(scrollable_body, text="I'm a button in the scrollable frame").grid()

scrollable_body.update()

root.mainloop()