Conor Conor - 1 month ago 12
Python Question

Python tkinter - can't display same frame more than once?

So with the code below, I can switch pages from Page1 to Page2, Page2 to Page3, Page3 to Page4 but cannot go from Page4 to Page1.

It displays the error message:

Traceback (most recent call last):
File "C:\Python33\lib\tkinter\__init__.py", line 1489, in __call__
return self.func(*args)
File "F:\CCTV\test\Page4.py", line 29, in buttonLoginClicked
self.controller.show_frame(Page1)
NameError: global name 'Page1' is not defined


I was wondering if this is because I can't switch to the same frame more that once or am I just doing something wrong. if I put all of the classes onto the same page, it seems to fix the issue however I would like to have all of the classes on separate pages. Maybe this has something to do with importing the pages? This is a simplified version of my code to help show the issue:

CCTV:

import tkinter as tk
from tkinter import ttk, messagebox
from Page1 import *
from Page2 import *
from Page3 import *
from Page4 import *

class CCTV(tk.Tk):

def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)

container = tk.Frame(self)
container.pack()
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)

self.frames = {}

for F in (Page1, Page2, Page3, Page4):
frame = F(container, self)
self.frames[F] = frame
frame.grid(column=0, row=0, sticky="nsew")

self.openPage()

def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()

def openPage(self):
self.show_frame(Page1)

app = CCTV()
app.geometry("800x600")
app.mainloop()


Page1:

import tkinter as tk
from tkinter import ttk, messagebox
from Page2 import *

class Page1(tk.Frame):

def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.createView()

def createView(self):
inner_frame = tk.Frame(self)
inner_frame.pack(side="top", fill="none")

self.labelTitle = ttk.Label(inner_frame, text="Page 1")
self.buttonLogin = ttk.Button(inner_frame, text="Page 2", command=self.buttonLoginClicked)

self.labelTitle.grid(row=1, columnspan=4, pady=10)
self.buttonLogin.grid(row=2, columnspan=4, pady=10)

self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(3, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(3, weight=1)


def buttonLoginClicked(self):
self.controller.show_frame(Page2)


Page2:

import tkinter as tk
from tkinter import ttk, messagebox
from Page3 import *

class Page2(tk.Frame):

def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.createView()

def createView(self):
inner_frame = tk.Frame(self)
inner_frame.pack(side="top", fill="none")

self.labelTitle = ttk.Label(inner_frame, text="Page 2")
self.buttonLogin = ttk.Button(inner_frame, text="Page 3", command=self.buttonLoginClicked)

self.labelTitle.grid(row=1, columnspan=4, pady=10)
self.buttonLogin.grid(row=2, columnspan=4, pady=10)

self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(3, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(3, weight=1)


def buttonLoginClicked(self):
self.controller.show_frame(Page3)


Page3:

import tkinter as tk
from tkinter import ttk, messagebox
from Page4 import *

class Page3(tk.Frame):

def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.createView()

def createView(self):
inner_frame = tk.Frame(self)
inner_frame.pack(side="top", fill="none")

self.labelTitle = ttk.Label(inner_frame, text="Page 3")
self.buttonLogin = ttk.Button(inner_frame, text="Page 4", command=self.buttonLoginClicked)

self.labelTitle.grid(row=1, columnspan=4, pady=10)
self.buttonLogin.grid(row=2, columnspan=4, pady=10)

self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(3, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(3, weight=1)


def buttonLoginClicked(self):
self.controller.show_frame(Page4)


Page4:

import tkinter as tk
from tkinter import ttk, messagebox
from Page1 import *

class Page4(tk.Frame):

def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.createView()

def createView(self):
inner_frame = tk.Frame(self)
inner_frame.pack(side="top", fill="none")

self.labelTitle = ttk.Label(inner_frame, text="Page 4")
self.buttonLogin = ttk.Button(inner_frame, text="Page 1", command=self.buttonLoginClicked)

self.labelTitle.grid(row=1, columnspan=4, pady=10)
self.buttonLogin.grid(row=2, columnspan=4, pady=10)

self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(3, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(3, weight=1)


def buttonLoginClicked(self):
self.controller.show_frame(Page1)

Answer

There's no good way to fix your code as written -- you have circular imports which I'm frankly surprised works at all. When you import Page1 in the main program, it causes Page2 to be imported because Page1 imports it. This causes Page3 to be imported because Page2 imports it. This causes Page4 to be imported because Page3 imports it. Then your main program explicitly imports Page2, which causes Page3 to be imported again, which causes Page4 to be imported again, and so on.

The root of your problem is that you are needing to import a page in order to switch to it. Don't do that. Instead, redefine show_frame to take the name of a page, so that you don't have to import a page in order to switch to it (except in the main program, of course).

The short version is here. Notice that the code gets the name of the page and uses it as the dictionary key:

class CCTV(tk.Tk):
    def __init__(...):
        ...
        for F in (Page1, Page2, Page3, Page4):
            page_name = F.__name__
            frame = F(container, self)
            self.frames[page_name] = frame
            ...

    def show_frame(self, page_name):
        '''Show a frame for the given page name'''
        frame = self.frames[page_name]
        frame.tkraise()

With that, you can switch to a frame without importing it, by simply doing this:

self.controller.show_frame("Page1")

A complete working example is here: http://stackoverflow.com/a/7557028/7432

Comments