Tkinter'da iki çerçeve arasında geçiş yapın


94

Öğreticilerin bana gösterdiği gibi, ilk birkaç komut dosyamı üzerinde güzel küçük bir GUI ile oluşturdum, ancak hiçbiri daha karmaşık bir program için ne yapılacağını ele almıyor.

Açılış ekranınız için bir 'başlat menüsü' olan bir şeyiniz varsa ve kullanıcı seçimiyle programın farklı bir bölümüne gidip ekranı uygun şekilde yeniden çiziyorsanız, bunu yapmanın zarif yolu nedir?

Biri sadece .destroy()'başlangıç ​​menüsü' çerçevesini mi oluşturuyor ve ardından başka bir bölüm için widget'larla dolu yeni bir tane mi oluşturuyor? Ve geri düğmesine bastıkları zaman bu işlemi tersine çevirmek mi?

Yanıtlar:


177

Bunun bir yolu, çerçeveleri üst üste yığmaktır, daha sonra istifleme sırasında birini diğerinin üzerine kaldırabilirsiniz. Üstteki, görünen kişi olacaktır. Bu, tüm çerçevelerin aynı boyutta olması durumunda en iyi sonucu verir, ancak küçük bir çalışma ile herhangi bir boyuttaki çerçeveyle çalışmasını sağlayabilirsiniz.

Not : Bunun çalışması için, bir sayfanın tüm widget'larının o sayfaya (yani self:) veya bir üst öğe olarak (veya tercih ettiğiniz terminolojiye bağlı olarak ana) bir alt öğeye sahip olması gerekir .

İşte size genel konsepti göstermek için yapmacık bir örnek:

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

başlangıç ​​sayfası Sayfa 1 sayfa 2

Bir sınıfta örnek oluşturma kavramını kafa karıştırıcı bulursanız veya farklı sayfalar oluşturma sırasında farklı argümanlara ihtiyaç duyarsa, her sınıfı ayrı ayrı açıkça çağırabilirsiniz. Döngü, esas olarak her sınıfın aynı olduğu noktasını göstermeye yarar.

Örneğin, sınıfları ayrı ayrı oluşturmak için döngüyü kaldırabilirsiniz ( for F in (StartPage, ...)bununla:

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

Zamanla insanlar bu kodu (veya bu kodu kopyalayan çevrimiçi bir öğreticiyi) başlangıç ​​noktası olarak kullanarak başka sorular sordular. Şu soruların cevaplarını okumak isteyebilirsiniz:


3
Vay canına, çok teşekkür ederim. Tıpkı pratik bir soru yerine akademik bir soru olarak, gecikmeli ve tepkisiz hale gelmeye başlamadan önce bu sayfalardan kaç tanesinin üst üste gizlenmiş olması gerekir?
Max Tilley

4
Bilmiyorum. Muhtemelen binlerce. Test etmek yeterince kolay olurdu.
Bryan Oakley

1
Çerçeveleri üst üste yığmak zorunda kalmadan çok daha az kod satırıyla buna benzer bir etki elde etmenin gerçekten basit bir yolu yok mu?
Paralaks Şeker

2
@StevenVascellaro: evet, pack_forgetkullanıyorsanız kullanılabilir pack.
Bryan Oakley

2
Uyarı : Kullanıcıların arka plan çerçevelerinde "gizli" widget'ları Tab tuşuna basarak seçmesi ve ardından bunları Enter ile etkinleştirmesi mümkündür.
Stevoisiak

31

İşte başka bir basit cevap, ancak sınıfları kullanmadan.

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

Uyarı : Aşağıdaki yanıt, çerçeveleri tekrar tekrar yok edip yeniden oluşturarak bellek sızıntısına neden olabilir .

Çerçeveleri değiştirmenin bir yolu tkinter, eski çerçeveyi yok edip yeni çerçevenizle değiştirmektir.

Ben değiştirdiniz Bryan Oakley değiştirmeden önce eski çerçeveyi yok etmek cevabı. Ek bir bonus olarak, bu bir containernesneye olan ihtiyacı ortadan kaldırır ve herhangi bir genel Framesınıfı kullanmanıza izin verir .

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

Başlangıç ​​sayfası Birinci sayfa Sayfa iki

Açıklama

switch_frame(), uygulayan herhangi bir Class nesnesini kabul ederek çalışır Frame. İşlev daha sonra eskisinin yerini alacak yeni bir çerçeve oluşturur.

  • _frameVarsa eskiyi siler , ardından yeni çerçeveyle değiştirir.
  • .pack()Menü çubukları gibi eklenen diğer çerçeveler etkilenmeyecektir.
  • Uygulayan herhangi bir sınıfla kullanılabilir tkinter.Frame.
  • Pencere, yeni içeriğe uyacak şekilde otomatik olarak yeniden boyutlandırılır

Sürüm Geçmişi

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
Çoğaltılıp çoğaltılamayacağından emin olmadığım bu yöntemi denediğimde bu garip karelerin taşmasını
alıyorum

2
@quantik Taşma etkisi, eski düğmelerin düzgün şekilde yok edilmemesi nedeniyle ortaya çıkar. Doğrudan çerçeve sınıfına (genellikle self) düğme iliştirdiğinizden emin olun .
Stevoisiak

1
Geriye dönüp bakıldığında, isim switch_frame()biraz yanıltıcı olabilir. Olarak yeniden adlandırmalı mıyım replace_frame()?
Stevoisiak

11
Bu cevabın sürüm geçmişi kısmını kaldırmanızı tavsiye ederim. Tamamen işe yaramaz. Geçmişi görmek istiyorsanız, stackoverflow bunu yapmak için bir mekanizma sağlar. Bu cevabı okuyan çoğu insan tarihi umursamayacak.
Bryan Oakley

2
Sürüm geçmişi için teşekkür ederiz. Cevabı zaman içinde güncellediğinizi ve revize ettiğinizi bilmek güzel.
Vasyl Vaskivskyi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.