Переключение между двумя кадрами в tkinter - PullRequest
75 голосов
/ 25 сентября 2011

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

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

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

Ответы [ 3 ]

125 голосов
/ 26 сентября 2011

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

Примечание : для этого всевиджеты для страницы должны иметь эту страницу (то есть: 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()

start page page 1 page 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")

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

24 голосов
/ 24 июля 2015

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

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()
21 голосов
/ 16 марта 2018

Один из способов переключения кадров в 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()

Start page Page one Page two

Объяснение

switch_frame() работает, принимая любой объект Class, который реализует 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.
...