129 votes

Passer d'une image à l'autre dans tkinter ?

J'ai construit mes premiers scripts avec une jolie petite interface graphique, comme me l'ont montré les tutoriels, mais aucun d'entre eux ne traite de ce qu'il faut faire pour un programme plus complexe.

Si vous disposez d'un "menu de démarrage" pour votre écran d'ouverture et que, sur sélection de l'utilisateur, vous passez à une section différente du programme et redessinez l'écran en conséquence, quelle est la manière la plus élégante de procéder ?

Est-ce que l'on se contente de .destroy() le cadre "menu de démarrage" et en créer un nouveau rempli de widgets pour une autre partie ? Et inverser ce processus lorsqu'ils appuient sur le bouton "retour" ?

238voto

Bryan Oakley Points 63365

L'une des solutions consiste à empiler les cadres l'un sur l'autre, puis à les placer l'un au-dessus de l'autre dans l'ordre de l'empilement. Celui qui se trouve au-dessus est celui qui est visible. Cette méthode fonctionne mieux si tous les cadres sont de la même taille, mais avec un peu de travail, vous pouvez l'utiliser avec des cadres de n'importe quelle taille.

Note pour que cela fonctionne, tous les widgets d'une page doivent avoir cette page (c'est-à-dire self ) ou un descendant en tant que parent (ou maître, selon la terminologie que vous préférez).

Voici un exemple un peu artificiel pour vous montrer le concept général :

try:
    import tkinter as tk                # python 3
    from tkinter import font as tkfont  # python 3
except ImportError:
    import Tkinter as tk     # python 2
    import tkFont as tkfont  # python 2

class SampleApp(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")

        # the container is where we'll stack a bunch of frames
        # on top of each other, then the one we want visible
        # will be raised above the others
        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        for F in (StartPage, PageOne, PageTwo):
            page_name = F.__name__
            frame = F(parent=container, controller=self)
            self.frames[page_name] = frame

            # put all of the pages in the same location;
            # the one on the top of the stacking order
            # will be the one that is visible.
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame("StartPage")

    def show_frame(self, page_name):
        '''Show a frame for the given page name'''
        frame = self.frames[page_name]
        frame.tkraise()

class StartPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text="This is the start page", font=controller.title_font)
        label.pack(side="top", fill="x", pady=10)

        button1 = tk.Button(self, text="Go to Page One",
                            command=lambda: controller.show_frame("PageOne"))
        button2 = tk.Button(self, text="Go to Page Two",
                            command=lambda: controller.show_frame("PageTwo"))
        button1.pack()
        button2.pack()

class PageOne(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text="This is page 1", font=controller.title_font)
        label.pack(side="top", fill="x", pady=10)
        button = tk.Button(self, text="Go to the start page",
                           command=lambda: controller.show_frame("StartPage"))
        button.pack()

class PageTwo(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text="This is page 2", font=controller.title_font)
        label.pack(side="top", fill="x", pady=10)
        button = tk.Button(self, text="Go to the start page",
                           command=lambda: controller.show_frame("StartPage"))
        button.pack()

if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()

start page page 1 page 2

Si le concept de création d'une instance dans une classe vous semble confus, ou si des pages différentes ont besoin d'arguments différents lors de la construction, vous pouvez appeler explicitement chaque classe séparément. La boucle sert principalement à illustrer le fait que chaque classe est identique.

Par exemple, pour créer les classes individuellement, vous pouvez supprimer la boucle ( for F in (StartPage, ...) avec ceci :

self.frames["StartPage"] = StartPage(parent=container, controller=self)
self.frames["PageOne"] = PageOne(parent=container, controller=self)
self.frames["PageTwo"] = PageTwo(parent=container, controller=self)

self.frames["StartPage"].grid(row=0, column=0, sticky="nsew")
self.frames["PageOne"].grid(row=0, column=0, sticky="nsew")
self.frames["PageTwo"].grid(row=0, column=0, sticky="nsew")

Au fil du temps, des personnes ont posé d'autres questions en utilisant ce code (ou un tutoriel en ligne qui copiait ce code) comme point de départ. Vous pouvez lire les réponses à ces questions :

44voto

recobayu Points 511

Voici une autre réponse simple, mais sans utiliser de classes.

from tkinter import *

def raise_frame(frame):
    frame.tkraise()

root = Tk()

f1 = Frame(root)
f2 = Frame(root)
f3 = Frame(root)
f4 = Frame(root)

for frame in (f1, f2, f3, f4):
    frame.grid(row=0, column=0, sticky='news')

Button(f1, text='Go to frame 2', command=lambda:raise_frame(f2)).pack()
Label(f1, text='FRAME 1').pack()

Label(f2, text='FRAME 2').pack()
Button(f2, text='Go to frame 3', command=lambda:raise_frame(f3)).pack()

Label(f3, text='FRAME 3').pack(side='left')
Button(f3, text='Go to frame 4', command=lambda:raise_frame(f4)).pack(side='left')

Label(f4, text='FRAME 4').pack()
Button(f4, text='Goto to frame 1', command=lambda:raise_frame(f1)).pack()

raise_frame(f1)
root.mainloop()

39voto

Note : Selon JDN96 la réponse ci-dessous peut provoquer une fuite de mémoire en détruisant et en recréant des cadres de manière répétée. Cependant, je n'ai pas testé cela moi-même.

Une façon de changer de cadre dans tkinter est de détruire l'ancien cadre et de le remplacer par le nouveau.

J'ai modifié Bryan Oakley's répondre pour détruire l'ancien cadre avant de le remplacer. En outre, cela permet d'éviter l'utilisation d'un container et vous permet d'utiliser n'importe quel objet générique Frame classe.

# Multi-frame tkinter application v2.3
import tkinter as tk

class SampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self._frame = None
        self.switch_frame(StartPage)

    def switch_frame(self, frame_class):
        """Destroys current frame and replaces it with a new one."""
        new_frame = frame_class(self)
        if self._frame is not None:
            self._frame.destroy()
        self._frame = new_frame
        self._frame.pack()

class StartPage(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        tk.Label(self, text="This is the start page").pack(side="top", fill="x", pady=10)
        tk.Button(self, text="Open page one",
                  command=lambda: master.switch_frame(PageOne)).pack()
        tk.Button(self, text="Open page two",
                  command=lambda: master.switch_frame(PageTwo)).pack()

class PageOne(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        tk.Label(self, text="This is page one").pack(side="top", fill="x", pady=10)
        tk.Button(self, text="Return to start page",
                  command=lambda: master.switch_frame(StartPage)).pack()

class PageTwo(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        tk.Label(self, text="This is page two").pack(side="top", fill="x", pady=10)
        tk.Button(self, text="Return to start page",
                  command=lambda: master.switch_frame(StartPage)).pack()

if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()

Start page Page one Page two

Explication

switch_frame() fonctionne en acceptant tout objet Class qui implémente la fonction Frame . La fonction crée ensuite un nouveau cadre pour remplacer l'ancien.

  • Supprime l'ancien _frame s'il existe, puis le remplace par le nouveau cadre.
  • Autres cadres ajoutés avec .pack() telles que les barres de menus, ne seront pas affectées.
  • Peut être utilisé avec n'importe quelle classe qui implémente la fonction tkinter.Frame .
  • La fenêtre se redimensionne automatiquement pour s'adapter au nouveau contenu

Historique de la version

v2.3

- Pack buttons and labels as they are initialized

v2.2

- Initialize `_frame` as `None`.
- Check if `_frame` is `None` before calling `.destroy()`.

v2.1.1

- Remove type-hinting for backwards compatibility with Python 3.4.

v2.1

- Add type-hinting for `frame_class`.

v2.0

- Remove extraneous `container` frame.
    - Application now works with any generic `tkinter.frame` instance.
- Remove `controller` argument from frame classes.
    - Frame switching is now done with `master.switch_frame()`.

v1.6

- Check if frame attribute exists before destroying it.
- Use `switch_frame()` to set first frame.

v1.5

  - Revert 'Initialize new `_frame` after old `_frame` is destroyed'.
      - Initializing the frame before calling `.destroy()` results
        in a smoother visual transition.

v1.4

- Pack frames in `switch_frame()`.
- Initialize new `_frame` after old `_frame` is destroyed.
    - Remove `new_frame` variable.

v1.3

- Rename `parent` to `master` for consistency with base `Frame` class.

v1.2

- Remove `main()` function.

v1.1

- Rename `frame` to `_frame`.
    - Naming implies variable should be private.
- Create new frame before destroying old frame.

v1.0

- Initial version.

3voto

Engineer_Chris Points 101

Une solution plus intuitive consisterait peut-être à masquer/désactiver les cadres à l'aide de la fonction pack_forget si vous utilisez la méthode pack gestionnaire de la géométrie.

Voici un exemple simple.

import tkinter as tk

class App:
    def __init__(self, root=None):
        self.root = root
        self.frame = tk.Frame(self.root)
        self.frame.pack()
        tk.Label(self.frame, text='Main page').pack()
        tk.Button(self.frame, text='Go to Page 1',
                  command=self.make_page_1).pack()
        self.page_1 = Page_1(master=self.root, app=self)

    def main_page(self):
        self.frame.pack()

    def make_page_1(self):
        self.frame.pack_forget()
        self.page_1.start_page()

class Page_1:
    def __init__(self, master=None, app=None):
        self.master = master
        self.app = app
        self.frame = tk.Frame(self.master)
        tk.Label(self.frame, text='Page 1').pack()
        tk.Button(self.frame, text='Go back', command=self.go_back).pack()

    def start_page(self):
        self.frame.pack()

    def go_back(self):
        self.frame.pack_forget()
        self.app.main_page()

if __name__ == '__main__':
    root = tk.Tk()
    app = App(root)
    root.mainloop()

enter image description here

enter image description here

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X