[python] tkinter에서 두 프레임 간 전환

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

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

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



답변

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

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

시간이 지남에 따라 사람들은이 코드 (또는이 코드를 복사 한 온라인 자습서)를 시작점으로 사용하여 다른 질문을했습니다. 다음 질문에 대한 답변을 읽을 수 있습니다.


답변

여기에 또 다른 간단한 대답이 있지만 클래스를 사용하지 않습니다.

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

교체하기 전에 이전 프레임을 파괴 하도록 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.


답변