Python >> Tutoriel Python >  >> Python GUI >> Tkinter GUI

Barre de défilement Tkinter pour le cadre

Veuillez noter que le code proposé n'est valable qu'avec Python 2

Voici un exemple :

from Tkinter import *   # from x import * is bad practice
from ttk import *

# http://tkinter.unpythonic.net/wiki/VerticalScrolledFrame

class VerticalScrolledFrame(Frame):
    """A pure Tkinter scrollable frame that actually works!
    * Use the 'interior' attribute to place widgets inside the scrollable frame
    * Construct and pack/place/grid normally
    * This frame only allows vertical scrolling

    """
    def __init__(self, parent, *args, **kw):
        Frame.__init__(self, parent, *args, **kw)            

        # create a canvas object and a vertical scrollbar for scrolling it
        vscrollbar = Scrollbar(self, orient=VERTICAL)
        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
        canvas = Canvas(self, bd=0, highlightthickness=0,
                        yscrollcommand=vscrollbar.set)
        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
        vscrollbar.config(command=canvas.yview)

        # reset the view
        canvas.xview_moveto(0)
        canvas.yview_moveto(0)

        # create a frame inside the canvas which will be scrolled with it
        self.interior = interior = Frame(canvas)
        interior_id = canvas.create_window(0, 0, window=interior,
                                           anchor=NW)

        # track changes to the canvas and frame width and sync them,
        # also updating the scrollbar
        def _configure_interior(event):
            # update the scrollbars to match the size of the inner frame
            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
            canvas.config(scrollregion="0 0 %s %s" % size)
            if interior.winfo_reqwidth() != canvas.winfo_width():
                # update the canvas's width to fit the inner frame
                canvas.config(width=interior.winfo_reqwidth())
        interior.bind('<Configure>', _configure_interior)

        def _configure_canvas(event):
            if interior.winfo_reqwidth() != canvas.winfo_width():
                # update the inner frame's width to fill the canvas
                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
        canvas.bind('<Configure>', _configure_canvas)


if __name__ == "__main__":

    class SampleApp(Tk):
        def __init__(self, *args, **kwargs):
            root = Tk.__init__(self, *args, **kwargs)


            self.frame = VerticalScrolledFrame(root)
            self.frame.pack()
            self.label = Label(text="Shrink the window to activate the scrollbar.")
            self.label.pack()
            buttons = []
            for i in range(10):
                buttons.append(Button(self.frame.interior, text="Button " + str(i)))
                buttons[-1].pack()

    app = SampleApp()
    app.mainloop()

Il n'a pas encore la molette de la souris liée à la barre de défilement mais c'est possible. Cependant, le défilement avec la molette peut devenir un peu cahoteux.

modifier :

à 1)
Le défilement des cadres à mon humble avis est quelque peu délicat dans Tkinter et ne semble pas être fait beaucoup. Il semble qu'il n'y ait pas de manière élégante de le faire.
Un problème avec votre code est que vous devez définir manuellement la taille du canevas - c'est ce que l'exemple de code que j'ai publié résout.

à 2)
Vous parlez de la fonction data ? Le lieu fonctionne pour moi aussi. (En général je préfère la grille).

à 3)
Eh bien, il positionne la fenêtre sur la toile.

Une chose que j'ai remarquée est que votre exemple gère le défilement de la molette de la souris par défaut alors que celui que j'ai posté ne le fait pas. Il faudra que je regarde ça un jour.


"Est-ce que je le fais bien ? Existe-t-il un moyen meilleur/plus intelligent d'obtenir le résultat que ce code m'a donné ?"

De manière générale, oui, vous le faites bien. Tkinter n'a pas de conteneur déroulant natif autre que le canevas. Comme vous pouvez le voir, ce n'est vraiment pas si difficile à mettre en place. Comme le montre votre exemple, il ne faut que 5 ou 6 lignes de code pour que cela fonctionne, selon la façon dont vous comptez les lignes.

"Pourquoi dois-je utiliser la méthode de la grille ? (J'ai essayé la méthode du lieu, mais aucune des étiquettes n'apparaît sur la toile ?)"

Vous demandez pourquoi vous devez utiliser la grille. Il n'est pas nécessaire d'utiliser la grille. Le lieu, la grille et le pack peuvent tous être utilisés. C'est simplement que certains sont plus naturellement adaptés à des types particuliers de problèmes. Dans ce cas, il semble que vous créiez une grille réelle -- des lignes et des colonnes d'étiquettes -- donc la grille est le choix naturel.

"Qu'y a-t-il de si spécial dans l'utilisation de anchor='nw' lors de la création d'une fenêtre sur un canevas ?"

L'ancre vous indique quelle partie de la fenêtre est positionnée aux coordonnées que vous donnez. Par défaut, le centre de la fenêtre sera placé à la coordonnée. Dans le cas de votre code ci-dessus, vous voulez que le coin supérieur gauche ("nord-ouest") soit aux coordonnées.


S'il vous plaît voir ma classe qui est un cadre déroulant. Sa barre de défilement verticale est liée à <Mousewheel> événement également. Donc, tout ce que vous avez à faire est de créer un cadre, de le remplir de widgets comme vous le souhaitez, puis de faire de ce cadre un enfant de mon ScrolledWindow.scrollwindow . N'hésitez pas à demander si quelque chose n'est pas clair.

Beaucoup utilisé de @ Brayan Oakley répond à près de ces questions

class ScrolledWindow(tk.Frame):
    """
    1. Master widget gets scrollbars and a canvas. Scrollbars are connected 
    to canvas scrollregion.

    2. self.scrollwindow is created and inserted into canvas

    Usage Guideline:
    Assign any widgets as children of <ScrolledWindow instance>.scrollwindow
    to get them inserted into canvas

    __init__(self, parent, canv_w = 400, canv_h = 400, *args, **kwargs)
    docstring:
    Parent = master of scrolled window
    canv_w - width of canvas
    canv_h - height of canvas

    """


    def __init__(self, parent, canv_w = 400, canv_h = 400, *args, **kwargs):
        """Parent = master of scrolled window
        canv_w - width of canvas
        canv_h - height of canvas

       """
        super().__init__(parent, *args, **kwargs)

        self.parent = parent

        # creating a scrollbars
        self.xscrlbr = ttk.Scrollbar(self.parent, orient = 'horizontal')
        self.xscrlbr.grid(column = 0, row = 1, sticky = 'ew', columnspan = 2)         
        self.yscrlbr = ttk.Scrollbar(self.parent)
        self.yscrlbr.grid(column = 1, row = 0, sticky = 'ns')         
        # creating a canvas
        self.canv = tk.Canvas(self.parent)
        self.canv.config(relief = 'flat',
                         width = 10,
                         heigh = 10, bd = 2)
        # placing a canvas into frame
        self.canv.grid(column = 0, row = 0, sticky = 'nsew')
        # accociating scrollbar comands to canvas scroling
        self.xscrlbr.config(command = self.canv.xview)
        self.yscrlbr.config(command = self.canv.yview)

        # creating a frame to inserto to canvas
        self.scrollwindow = ttk.Frame(self.parent)

        self.canv.create_window(0, 0, window = self.scrollwindow, anchor = 'nw')

        self.canv.config(xscrollcommand = self.xscrlbr.set,
                         yscrollcommand = self.yscrlbr.set,
                         scrollregion = (0, 0, 100, 100))

        self.yscrlbr.lift(self.scrollwindow)        
        self.xscrlbr.lift(self.scrollwindow)
        self.scrollwindow.bind('<Configure>', self._configure_window)  
        self.scrollwindow.bind('<Enter>', self._bound_to_mousewheel)
        self.scrollwindow.bind('<Leave>', self._unbound_to_mousewheel)

        return

    def _bound_to_mousewheel(self, event):
        self.canv.bind_all("<MouseWheel>", self._on_mousewheel)   

    def _unbound_to_mousewheel(self, event):
        self.canv.unbind_all("<MouseWheel>") 

    def _on_mousewheel(self, event):
        self.canv.yview_scroll(int(-1*(event.delta/120)), "units")  

    def _configure_window(self, event):
        # update the scrollbars to match the size of the inner frame
        size = (self.scrollwindow.winfo_reqwidth(), self.scrollwindow.winfo_reqheight())
        self.canv.config(scrollregion='0 0 %s %s' % size)
        if self.scrollwindow.winfo_reqwidth() != self.canv.winfo_width():
            # update the canvas's width to fit the inner frame
            self.canv.config(width = self.scrollwindow.winfo_reqwidth())
        if self.scrollwindow.winfo_reqheight() != self.canv.winfo_height():
            # update the canvas's width to fit the inner frame
            self.canv.config(height = self.scrollwindow.winfo_reqheight())