Agregar una barra de desplazamiento a un grupo de widgets en Tkinter

Estoy usando Python para analizar las entradas de un archivo de registro, y mostrar el contenido de las entradas con Tkinter y hasta ahora ha sido excelente. La salida es una cuadrícula de widgets de tags, pero a veces hay más filas de las que se pueden mostrar en la pantalla. Me gustaría agregar una barra de desplazamiento, que parece que debería ser muy fácil, pero no puedo entenderlo.

La documentación implica que solo los widgets Lista, Cuadro de texto, Lienzo y Entrada admiten la interfaz de la barra de desplazamiento. Ninguno de estos parece ser adecuado para mostrar una cuadrícula de widgets. Es posible colocar widgets arbitrarios en un widget de Canvas, pero parece que tiene que usar coordenadas absolutas, por lo que no podría usar el administrador de diseño de cuadrícula.

He intentado colocar la cuadrícula de widgets en un marco, pero eso no parece ser compatible con la interfaz de la barra de desplazamiento, por lo que esto no funciona:

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

¿Alguien puede sugerir una manera de evitar esta limitación? ¡Odiaría tener que volver a escribir en PyQt y boost el tamaño de mi imagen ejecutable tanto, solo para agregar una barra de desplazamiento!

Visión general

Solo puede asociar las barras de desplazamiento con algunos widgets, y el widget raíz y el Frame no forman parte de ese grupo de widgets.

La solución más común es crear un widget de canvas y asociar las barras de desplazamiento con ese widget. Luego, en ese canvas, incrusta el marco que contiene los widgets de tus tags. Determine el ancho / alto del marco y aliméntelo en la opción de área de scrollregion canvas para que el área de desplazamiento coincida exactamente con el tamaño del marco.

Dibujar los elementos de texto directamente en el canvas no es muy difícil, por lo que es posible que desee reconsiderar ese enfoque si la solución de marco incrustado en un canvas parece demasiado compleja. Ya que está creando una cuadrícula, las coordenadas de cada elemento de texto serán muy fáciles de calcular, especialmente si cada fila tiene la misma altura (lo que probablemente sea si está usando una sola fuente).

Para dibujar directamente en el canvas, solo determine la altura de la línea de la fuente que está usando (y hay comandos para eso). Luego, cada coordenada y es una row*(lineheight+spacing) . La coordenada x será un número fijo basado en el elemento más ancho de cada columna. Si asigna a todos una etiqueta para la columna en la que se encuentra, puede ajustar la coordenada x y el ancho de todos los elementos en una columna con un solo comando.

Solución orientada a objetos

A continuación, se muestra un ejemplo de la solución frame-embedded-in-canvas, utilizando un enfoque orientado a objetos:

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

Solución procesal

Aquí hay una solución que no usa objetos:

 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("", lambda event, canvas=canvas: onFrameConfigure(canvas)) populate(frame) root.mainloop() 

Nota: para hacer que esto funcione en Python 2.x, use Tkinter lugar de tkinter en la statement de importación

Hazlo desplazable

Usa esta clase práctica para hacer que el marco que contiene tus widgets se pueda desplazar. Sigue estos pasos:

  1. crear el marco
  2. mostrarlo (paquete, rejilla, etc.)
  3. hazlo desplazable
  4. agregar widgets dentro de ella
  5. llamar al método update ()

 import tkinter as tk from tkinter import ttk class Scrollable(ttk.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('', 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)) 

Ejemplo de uso

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