Ajout d'une barre de défilement à un groupe de widgets dans Tkinter

J'utilise Python pour analyser les entrées d'un fichier journal, et afficher le contenu des entrées en utilisant Tkinter et jusqu'à présent c'est excellent. La sortie est une grille de widgets d'étiquette, mais parfois il y a plus de lignes qui peuvent être affichées sur l'écran. J'aimerais ajouter une barre de défilement, ce qui devrait être très facile, mais je n'arrive pas à le comprendre.

la documentation implique que seuls les widgets List, Textbox, Canvas et Entry supportent l'interface scrollbar. Aucun des ceux-ci semblent convenir pour afficher une grille de widgets. Il est possible de mettre des widgets arbitraires dans un widget Canvas, mais vous semblez avoir à utiliser des coordonnées absolues, donc je ne serais pas en mesure d'utiliser le gestionnaire de mise en page grid?

j'ai essayé de mettre le widget grid dans un cadre, mais cela ne semble pas prendre en charge l'interface scrollbar, donc cela ne fonctionne pas:

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

quelqu'un peut-il suggérer un moyen de contourner cette limitation? Je déteste avoir à réécrire dans PyQt et augmenter la taille de mon image exécutable de tellement, juste pour ajouter une barre de défilement!

48
demandé sur Bryan Oakley 2010-06-21 18:50:14

2 réponses

vue d'ensemble

vous ne pouvez associer que des barres de défilement avec quelques widgets, et le widget racine et Frame ne font pas partie de ce groupe de widgets.

la solution la plus courante est de créer un widget canvas et d'associer les barres de défilement à ce widget. Puis, dans cette toile intégrer le cadre qui contient vos widgets d'étiquette. Déterminez la largeur / hauteur du cadre et insérez-le dans l'option canvas scrollregion pour que le scrollregion correspond exactement à la taille de l'image.

dessiner les éléments de texte directement sur la toile n'est pas très difficile, donc vous pourriez vouloir reconsidérer cette approche si le cadre-embedded-in-a-canvas solution semble trop complexe. Puisque vous créez une grille, Les coordonnées de chaque élément de texte vont être très faciles à calculer, surtout si chaque ligne est de la même hauteur (ce qui est probablement le cas si vous utilisez une seule police).

pour dessiner directement sur la toile, déterminez la hauteur de la ligne de la police que vous utilisez (et il y a des commandes pour cet). Ensuite, chaque coordonnée y est row*(lineheight+spacing) . La coordonnée x sera un nombre fixe basé sur le plus grand élément dans chaque colonne. Si vous donner tout un tag pour la colonne, il est, vous pouvez régler la coordonnée x et la largeur de tous les éléments dans une colonne avec une seule commande.

solution orientée objet

voici un exemple de la solution cadre-embedded-in-canvas, en utilisant une approche orientée objet:

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

solution procédurale

Voici une solution qui n'utilise pas d'objets:

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

Note: pour faire ce travail en python 2.x, utilisez Tkinter plutôt que tkinter dans la déclaration d'importation

85
répondu Bryan Oakley 2017-03-17 00:29:02

scrollable

utilisez cette classe pratique pour faire scrollable le cadre contenant vos widgets . Suivez ces étapes:

  1. créer le cadre
  2. afficher (pack, grille, etc)
  3. scrollable
  4. ajouter des widgets à l'intérieur de lui
  5. appel de la méthode 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('<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))

exemple d'Utilisation

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()
0
répondu Tarqez 2018-03-26 17:51:09