Перемикання між двома кадрами в tkinter


94

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

Якщо у вас є щось із «меню Пуск» для вашого початкового екрану, і після вибору користувача ви переходите в інший розділ програми та перемальовуєте екран належним чином, який елегантний спосіб це зробити?

Чи є лише .destroy()кадр "меню Пуск", а потім створюється новий, наповнений віджетами для іншої частини? І повернути цей процес назад, коли вони натискають кнопку назад?

Відповіді:


177

Одним із способів є укладання рамок одна на одну, тоді ви можете просто підняти одну над іншою в порядку укладання. Той, що зверху, буде той, який видно. Це найкраще працює, якщо всі кадри однакового розміру, але, трохи попрацювавши, ви зможете змусити його працювати з будь-якими кадрами розміру.

Примітка : для того, щоб це працювало, усі віджети сторінки повинні мати цю сторінку (тобто:) 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")

З часом люди задавали інші запитання, використовуючи цей код (або підручник з Інтернету, який скопіював цей код) як вихідну точку. Можливо, ви захочете прочитати відповіді на ці запитання:


3
Ого, дуже дякую. Так само, як академічне запитання, а не практичне, скільки з цих сторінок, прихованих одна на одній, знадобиться вам, перш ніж вона почне відставати і не реагувати?
Max Tilley

4
Не знаю. Можливо, тисячі. Це було б досить просто протестувати.
Брайан Оклі

1
Чи не існує справді простого способу досягти подібного ефекту, набагато менше рядків коду, без необхідності складати кадри один на одного?
Parallax Sugar

2
@StevenVascellaro: так, pack_forgetможна використовувати, якщо ви використовуєте pack.
Bryan Oakley,

2
Попередження : Користувачі можуть вибирати "приховані" віджети на фонових кадрах, натискаючи Tab, а потім активувати їх за допомогою Enter.
Стевойсяк,

31

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

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

28

Попередження : Відповідь нижче може спричинити витік пам'яті , багаторазово руйнуючи та відтворюючи кадри.

Один із способів переключення кадрів 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. Потім функція створює новий кадр для заміни старого.

  • Видаляє старий, _frameякщо він існує, а потім замінює його новим кадром.
  • Інші кадри, додані .pack(), наприклад, панелі меню, не вплинуть.
  • Може використовуватися з будь-яким класом, який реалізує 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.

1
Я отримую цей дивний просочування
quantik

2
@quantik Ефект пропускання відбувається тому, що старі кнопки неправильно руйнуються. Переконайтеся, що ви прикріплюєте кнопки безпосередньо до класу кадру (зазвичай self).
Стевойсяк

1
Оглянувшись назад, назва switch_frame()може дещо ввести в оману. Чи слід перейменовувати його на replace_frame()?
Стевойсяк

11
Я рекомендую видалити частину історії відповідей з цієї відповіді. Це абсолютно марно. Якщо ви хочете переглянути історію, stackoverflow надає механізм для цього. Більшість людей, які прочитали цю відповідь, не будуть цікавитись історією.
Bryan Oakley

2
Дякуємо за історію версій. Приємно знати, що ви з часом оновили та переглянули відповідь.
Василь Васьківський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.