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?
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()
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:
- la Compréhension du parent et de contrôleur dans Tkinter __init__
- Tkinter! Comprendre comment changer de cadre
- comment obtenir des données variables à partir d'une classe
- fonctions D'appel à partir D'un cadre Tkinter vers un autre
- Comment accéder aux variables des différentes classes de tkinter?
- Comment ferais-je une méthode qui est exécutée chaque fois qu'un cadre est montré dans tkinter
- Tkinter Image Redimensionner
- Tkinter avoir le code pour les pages dans des fichiers séparés
- rafraîchir un cadre tkinter sur Bouton presser
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()
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()
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.