-1

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)
Conor Egan
  • 518
  • 1
  • 3
  • 21
  • Maybe you forgot `from Page1 import *` Or you import wrong Page. – furas Oct 20 '16 at 12:29
  • Its better to write all page related classes in one file say `pages.py`, instead of so many different/ You might not have imported `Page1` in the file `Page4.py`. mind showing the content of `Page4`. – lycuid Oct 20 '16 at 12:29
  • If all pages are almost identical then you could create one class `BasePage` and use it to define other classes - `class Page1(BasePage)` – furas Oct 20 '16 at 12:33
  • Ill add the other three pages in to make it clearer what i mean. – Conor Egan Oct 20 '16 at 12:51
  • All of the pages on the actual program are different. The only reason I have them set out like this is to make the problem much clearer for everyone to see. I can go from Page1 to Page2, Page2 to Page3, Page3 to Page4 but cannot go from Page4 to Page1 - this is when all of the python classes are on separate files. If all of the classes are on the same file, I can go from Page4 to Page1 and continuously loop forever. I hope this makes the problem clearer for everyone to understand. – Conor Egan Oct 20 '16 at 12:55
  • @SSj.Luffy I added the other pages for you to see. – Conor Egan Oct 20 '16 at 13:02

2 Answers2

1

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: https://stackoverflow.com/a/7557028/7432

Community
  • 1
  • 1
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
1

Imported module is only run once, every connected file that imports it shares the same copy of the module. So in case of the cycle importing (import A in B, import B in C, import C in A), it will be messed up and wont be recognized the second time around. For e.g: You have file A.py:

from B import b
from C import c

print (b, c) # this is fine

and the content of B.py would be:

from C import c # not proper initialization as it has already been in A.py already
print (c)

That'll return an error:

global name 'c' is not defined

The thing is the imported file will be the same copy all over, so you can not actually import the same module in different connected files, what you rather can do is:

pages = { 
          Page1: Page2,
          Page2: Page3,
          Page3: Page4,
          Page4: Page1
      }
for F, goto_page in pages.items():
  frame = F(container, self, goto_page)
  self.frames[F] = frame
  frame.grid(column=0, row=0, sticky="nsew")
lycuid
  • 2,555
  • 1
  • 18
  • 28