Basculer entre deux cadres en tkinter

j'ai construit mes premiers scripts avec une jolie petite GUI sur eux, comme les tutoriels m'ont montré, mais aucun d'eux n'aborde ce qu'il faut faire pour un programme plus complexe.

si vous avez quelque chose avec un "menu Démarrer", pour votre écran d'ouverture, et après la sélection de l'utilisateur vous passez à une section différente du programme et de redessiner l'écran de manière appropriée, Quelle est la façon élégante de faire cela?

- T-on seulement .destroy() le menu 'démarrer' de cadre et puis créer un nouveau rempli avec les widgets pour une autre partie? Et inverser ce processus quand ils appuieront sur le bouton arrière?

57
demandé sur nbro 2011-09-25 18:14:04

3 réponses

Une façon est d'empiler les images l'une sur l'autre, alors vous pouvez simplement faire de l'un au dessus de l'autre dans l'ordre d'empilement. Celui du dessus sera celui qui est visible. Cela fonctionne mieux si toutes les images sont de la même taille, mais avec un peu de travail, vous pouvez l'obtenir pour fonctionner avec tout type de cadres.

Note : pour que cela fonctionne, tous les widgets d'une page doivent avoir cette page (i.e.: self ) ou un descendant comme parent (ou maître, selon la terminologie que vous préférez).

voici un petit exemple pour vous montrer le concept général:

import tkinter as tk                # python 3
from tkinter import font  as tkfont # python 3
#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 vous trouvez le concept de création d'instance dans une classe déroutante, ou si des pages différentes nécessitent des arguments différents pendant la construction, vous pouvez explicitement appeler chaque classe séparément. La boucle sert surtout à illustrer le point 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, les gens ont posé d'autres questions en utilisant ce code (ou un tutoriel en ligne qui a copié ce code) comme point de départ. Vous pourriez vouloir lire le réponses à ces questions:

104
répondu Bryan Oakley 2018-08-28 02:04:57

Voici une autre réponse simple, mais sans utiliser les 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()
19
répondu recobayu 2015-08-31 02:05:32

pour changer de cadre dans tkinter , détruisez l'ancien cadre et remplacez-le par votre nouveau cadre.

alors que Bryan Oakley's frame stacking est une solution intelligente, elle maintient tous les cadres actifs à la fois. Cela a un effet secondaire inattendu qui permet aux utilisateurs de sélectionner des widgets à partir d'autres cadres en appuyant sur onglet .

J'ai modifié la réponse de Bryan pour détruire le vieux cadre avant de le remplacer. En bonus, cela élimine le besoin d'un objet container et vous permet d'utiliser n'importe quelle classe Frame Générique.

# 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() travaux en acceptant un objet de Classe outils Frame . La fonction crée ensuite une nouvelle image pour remplacer l'ancien.

  • supprime l'ancien _frame s'il existe, le remplace par le nouveau cadre.
  • les autres cadres ajoutés avec .pack() , tels que les menubars, ne seront pas affectés.
  • Peut être utilisé avec n'importe quelle classe qui implémente tkinter.Frame .
  • La fenêtre
  • se redimensionne automatiquement pour s'adapter à la nouvelle fenêtre sommaire

Histoire Des Versions

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.
12
répondu Steven Vascellaro 2018-09-24 13:54:40