Переключение между двумя кадрами в tkinter
Я построил свои первые несколько сценариев с симпатичным небольшим графическим интерфейсом, как показали учебники, но ни один из них не описывает, что делать для более сложной программы.
Если у вас есть что-то с "меню Пуск", для вашего начального экрана, и после выбора пользователя вы переходите в другой раздел программы и перерисовываете экран соответствующим образом, каков элегантный способ сделать это?
Один только .destroy()
кадр "меню пуск", а затем создать новый, заполненный виджетами для другой части? И отменить этот процесс, когда они нажимают кнопку назад?
4 ответа
Один из способов - сложить кадры друг над другом, затем вы можете просто поднять один над другим в порядке наложения. Один сверху будет тот, который виден. Это работает лучше всего, если все кадры имеют одинаковый размер, но с небольшой работой вы можете заставить его работать с кадрами любого размера.
Примечание: чтобы это работало, все виджеты для страницы должны иметь эту страницу (то есть: self
) или потомок в качестве родителя (или хозяина, в зависимости от терминологии, которую вы предпочитаете).
Вот немного надуманного примера, чтобы показать вам общую концепцию:
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()
Если вы находите концепцию создания экземпляра в классе запутанной или если разные страницы требуют разных аргументов во время конструирования, вы можете явно вызывать каждый класс отдельно. Цикл служит главным образом для иллюстрации того, что каждый класс идентичен.
Например, чтобы создать классы индивидуально, вы можете удалить цикл (for F in (StartPage, ...)
с этим:
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")
Со временем люди задавали другие вопросы, используя этот код (или интерактивное руководство, которое скопировало этот код) в качестве отправной точки. Возможно, вы захотите прочитать ответы на эти вопросы:
- Понимание родителя и контроллера в Tkinter __init__
- Tkinter! Понимание того, как переключать кадры
- Как получить переменные данные из класса
- Вызов функций из фрейма Tkinter в другой
- Как получить доступ к переменным из разных классов в tkinter?
- Как бы я сделал метод, который запускается каждый раз, когда кадр отображается в tkinter
- Tkinter Frame Resize
- Ткинтер есть код для страниц в отдельных файлах
- Обновление рамки tkinter при нажатии кнопки
Вот еще один простой ответ, но без использования классов.
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()
Для переключения кадров вtkinter
, уничтожьте старую рамку и замените ее новой.
Хотя укладка кадров Брайана Окли является разумным решением, он поддерживает активную работу каждого кадра. Это имеет непреднамеренный побочный эффект, который позволяет пользователям выбирать виджеты из других фреймов, нажимая клавишуTab.
Я изменил ответ Брайана, чтобы уничтожить старый кадр, прежде чем заменить его. В качестве дополнительного бонуса это устраняет необходимостьcontainer
объект и позволяет использовать любой общий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 __name__ == "__main__":
app = SampleApp()
app.mainloop()
объяснение
switch_frame()
работает, принимая любой объект класса, который реализуетFrame
, Затем функция создает новый фрейм для замены старого.
- Удаляет старый
_frame
если он существует, то заменяет его новым кадром. - Другие кадры добавлены с
.pack()
, такие как menubars, не будут затронуты. - Может использоваться с любым классом, который реализует
tkinter.Frame
, - Окно автоматически изменяет размеры, чтобы соответствовать новому контенту
История версий
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.
Возможно, более интуитивным решением было бы скрыть / показать фреймы с помощью
pack_forget
метод, если вы используете
pack
менеджер по геометрии.
Вот простой пример.
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()