web-dev-qa-db-fra.com

Basculer entre deux images dans tkinter

J'ai construit mes premiers scripts avec une petite interface graphique de Nice, comme les tutoriels me l'ont montré, mais aucun d'entre eux n'indique quoi faire pour un programme plus complexe.

Si vous avez quelque chose avec un "menu de démarrage", pour votre écran d’ouverture, et lors de la sélection de l’utilisateur, vous passez à une autre section du programme et redessinez l’écran de manière appropriée, quelle est la manière élégante de procéder?

Ne fait-on que .destroy() le cadre du menu Démarrer, puis en créer un nouveau contenant les widgets d'une autre partie? Et inverser ce processus quand ils appuient sur le bouton retour?

77
Max Tilley

L’une des méthodes consiste à empiler les images les unes sur les autres, vous pouvez simplement les élever les unes sur les autres dans l’ordre d’empilement. Celui sur le dessus sera celui qui est visible. Cela fonctionne mieux si tous les cadres ont la même taille, mais avec un peu de travail, vous pouvez le faire fonctionner avec des cadres de toutes tailles.

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 un parent (ou un maître, selon la terminologie que vous préférez).

Voici un exemple artificiel 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 __== "__main__":
    app = SampleApp()
    app.mainloop()

start pagepage 1page 2

Si vous trouvez le concept de création d'instance dans une classe 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, 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 voudrez peut-être lire les réponses à ces questions:

130
Bryan Oakley

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

Une façon de changer de cadre dans tkinter consiste à détruire l'ancien cadre, puis à le remplacer par votre nouveau cadre.

J'ai modifié Bryan Oakley répondre pour détruire l'ancien cadre avant de le remplacer. En prime, ceci élimine le besoin d'un objet container et vous permet d'utiliser n'importe quelle classe générique Frame.

# 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 __== "__main__":
    app = SampleApp()
    app.mainloop()

Start pagePage onePage two

Explication

switch_frame() fonctionne en acceptant tout objet de classe implémentant 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.
  • Les autres cadres ajoutés avec .pack(), tels que les barres de menus, ne seront pas affectés.
  • Peut être utilisé avec n'importe quelle classe qui implémente tkinter.Frame.
  • La fenêtre est automatiquement redimensionnée 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.
22
Stevoisiak