Вопрос-Ответ

Switch between two frames in tkinter?

Переключение между двумя фреймами в tkinter?

Я создал свои первые несколько скриптов с симпатичным графическим интерфейсом, как показали мне руководства, но ни в одном из них не говорится, что делать для более сложной программы.

Если у вас есть что-то с "меню пуск" для вашего открывающегося экрана, и по выбору пользователя вы переходите в другой раздел программы и соответствующим образом перерисовываете экран, какой элегантный способ сделать это?

Можно ли просто .destroy() создать фрейм "меню пуск", а затем создать новый, заполненный виджетами для другой части? И обратить этот процесс вспять, когда они нажимают кнопку "Назад"?

Переведено автоматически
Ответ 1

Один из способов - наложить фреймы друг на друга, затем вы можете просто расположить один над другим в порядке наложения. Тот, что сверху, будет виден. Это работает лучше всего, если все фреймы одинакового размера, но, немного поработав, вы можете заставить его работать с фреймами любого размера.

Примечание: чтобы это сработало, все виджеты для страницы должны иметь эту страницу (т.е.: self) или потомка в качестве родительского элемента (или ведущего, в зависимости от терминологии, которую вы предпочитаете).

Вот немного надуманный пример, чтобы показать вам общую концепцию:

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

начальная страница страница 1 страница 2

Если вы находите концепцию создания экземпляра в классе запутанной, или если разным страницам нужны разные аргументы при создании, вы можете явно вызвать каждый класс отдельно. Цикл служит главным образом для иллюстрации того, что каждый класс идентичен.

Например, чтобы создавать классы по отдельности, вы можете удалить цикл (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")

Со временем люди задавали другие вопросы, используя этот код (или онлайн-учебник, скопировавший этот код) в качестве отправной точки. Возможно, вы захотите прочитать ответы на эти вопросы.:

Ответ 2

Вот еще один простой ответ, но без использования классов.

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()
Ответ 3

Примечание: Согласно JDN96, приведенный ниже ответ может вызвать утечку памяти из-за многократного уничтожения и воссоздания фреймов. Однако я сам не тестировал, чтобы убедиться в этом.


Один из способов переключения фреймов в tkinter - уничтожить старый фрейм, а затем заменить его вашим новым фреймом.

Я изменил ответ Брайана Оукли, чтобы уничтожить старый фрейм перед его заменой. В качестве дополнительного бонуса это устраняет необходимость в 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. Затем функция создает новый фрейм для замены старого.


  • Deletes old _frame if it exists, then replaces it with the new frame.

  • Other frames added with .pack(), such as menubars, will be unaffected.

  • Can be used with any class that implements tkinter.Frame.

  • Window automatically resizes to fit new content

Version History

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.
Ответ 4

Возможно, более интуитивным решением было бы скрыть / не показывать фреймы с помощью pack_forget метода, если вы используете pack geometry manager.

Вот простой пример.

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

введите описание изображения здесь

введите описание изображения здесь

2023-07-05 00:23 python python-3.x tkinter