tkinter에서 두 프레임 간 전환


94

튜토리얼에서 보여준 것처럼 멋진 GUI를 사용하여 처음 몇 개의 스크립트를 만들었지 만 더 복잡한 프로그램을 위해 무엇을해야하는지 설명하는 스크립트는 없습니다.

시작 화면에 대해 '시작 메뉴'가있는 것이 있고 사용자 선택시 프로그램의 다른 섹션으로 이동하여 화면을 적절하게 다시 그리는 경우이 작업을 수행하는 우아한 방법은 무엇입니까?

하나 .destroy()는 '시작 메뉴'프레임이고 다른 부분의 위젯으로 채워진 새 프레임을 생성합니까? 그리고 뒤로 버튼을 누르면이 과정을 반대로 할 수 있습니까?

답변:


178

한 가지 방법은 프레임을 서로 쌓는 것입니다. 그런 다음 쌓기 순서대로 하나를 다른 프레임 위에 올릴 수 있습니다. 맨 위에있는 것이 보이는 것입니다. 이것은 모든 프레임이 같은 크기 일 때 가장 잘 작동하지만 약간의 작업으로 모든 크기의 프레임에서 작동하도록 할 수 있습니다.

참고 :이 작업을 수행하려면 페이지의 모든 위젯에 해당 페이지 (예 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 2011 년

4
모르겠어요. 아마 수천. 테스트하기는 쉬울 것입니다.
Bryan Oakley

1
프레임을 쌓을 필요없이 훨씬 적은 수의 코드로 이와 유사한 효과를 얻을 수있는 정말 간단한 방법이 없습니까?
Parallax Sugar

2
@StevenVascellaro : 예, pack_forget당신이 사용하는 경우 사용할 수 있습니다 pack.
Bryan Oakley

2
경고 : 사용자가 Tab 키를 눌러 배경 프레임에서 "숨겨진"위젯을 선택한 다음 Enter 키를 눌러 활성화 할 수 있습니다.
Stevoisiak

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은 이전 프레임을 파괴 한 다음 새 프레임으로 교체하는 것입니다.

교체하기 전에 이전 프레임을 파괴 하도록 Bryan Oakley의 답변을 수정 했습니다. 추가 보너스로 이것은 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()

시작 페이지 페이지 1 2 페이지

설명

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.

1
복제 할 수 있는지 확실하지 않은이 방법을 시도 할 때 프레임의 이상한 블리드 오버가 발생합니다. imgur.com/a/njCsa
quantik

2
@quantik 블리드 오버 효과는 이전 버튼이 제대로 파괴되지 않았기 때문에 발생합니다. 프레임 클래스 (일반적으로 self)에 버튼을 직접 연결하고 있는지 확인합니다 .
Stevoisiak

1
돌이켜 보면 이름 switch_frame()이 약간 오해의 소지가있을 수 있습니다. 이름을 변경해야합니까 replace_frame()?
Stevoisiak

11
이 답변의 버전 기록 부분을 제거하는 것이 좋습니다. 완전히 쓸모가 없습니다. 히스토리를 보려면 stackoverflow가이를 수행하는 메커니즘을 제공합니다. 이 답변을 읽는 대부분의 사람들은 역사에 관심이 없습니다.
Bryan Oakley

2
버전 기록에 감사드립니다. 시간이 지남에 따라 답변을 업데이트하고 수정했음을 아는 것이 좋습니다.
Vasyl Vaskivskyi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.