Python moving class to separate file causes nameError - python

I am simply trying to move class ScreenThree to a separate file 9 I will soon have many more)....However for the lambdas I get nameError ...how to fix?
I've tried many arrangements, but allways get some sort of nameError. For this post, I have deleted ScreenTwo, since these basically all look the same.
When moving this class to its own file what needs to be changed? I used import, which seemed to work & screen3 shows. , However the button lambda is where it fails
import tkinter as tk
LARGE_FONT = ("Verdana", 12) # font's family is Verdana, font's size is 12
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Fuzzy System") # set the title of the main window
self.geometry("300x300") # set size of the main window to 300x300 pixels
# this container contains all the screens
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1) # make the cell in grid cover the entire window
container.grid_columnconfigure(0,weight=1) # make the cell in grid cover the entire window
self.frames = {} # these are screens we want to navigate to
for F in (ScreenOne, ScreenTwo,ScreenThree): # for each screen
frame = F(container, self) # create the screen
self.frames[F] = frame # store into frames
frame.grid(row=0, column=0, sticky="nsew") # grid it to container
self.show_frame(ScreenOne) # let the first screen is ScreenOne
def show_frame(self, name):
frame = self.frames[name]
frame.tkraise()
class ScreenOne(tk.Frame):
def __init__(self, parent, controller):
self.controller=controller
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='This is ScreenOne', font=LARGE_FONT)
label.pack(pady=10, padx=10) # center alignment
# when click on this button, call the show_frame method to make screenOne appear
button1 = tk.Button(self, text='Visit screen two', command=lambda : controller.show_frame(ScreenTwo))
button1.pack() # pack it in
self.button2 = tk.Button(self, text='GOTO Screen Three', command=lambda : controller.show_frame(ScreenThree))
self.button2.place(relx=0.5, rely=0.29, height=41, width=144)
self.button2.configure(background="#911218")
class ScreenThree(tk.Frame):
def __init__(self, parent, controller):
self.controller=controller
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='This is screen Three', font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text='GOTO ScreenTwo', command=lambda : controller.show_frame(ScreenTwo))
button1.pack()
button2 = tk.Button(self, text='GOTO Screen One', command=lambda : controller.show_frame(ScreenOne))
button2.pack()
if __name__ == '__main__':
app = MainWindow()
app.mainloop()
I have several classes, all looking similar to the following. They all work fine, no problems. However I want to move them to individual files, since they will soon become lengthy. I moved the following to file `scr3.py`.
I then added the following to my main file:
from scr3 import ScreenThree
Screen one and two work fine and my buttons in `screen3` show up. However when pushing on the screen three buttons I get a `NameError: name 'ScreenOne' is not defined` and similar for `screen2` (see the lambda funcs). These worked fine when all was in one file. `Screen1` and `2` (still in the `main` file) continue to work fine.
Why does it work fine when this same code is in the `main` file , but now fails? It has only been moved. What is the workaround?
import tkinter as tk
LARGE_FONT = ("Verdana", 12) # font's family is Verdana, font's size is 12
class ScreenThree(tk.Frame):
def __init__(self, parent, controller):
self.controller=controller
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='This is screen Three', font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text='GOTO ScreenTwo', command=lambda : controller.show_frame(ScreenTwo))
button1.pack()
button2 = tk.Button(self, text='GOTO Screen One', command=lambda : controller.show_frame(ScreenOne))
button2.pack()

The code of ScreenThree is no longer in the same namespace as e.g. ScreenOne. You might fix this by passing a reference to ScreenOne and ScreenTwo as arguments to the __init__.

Related

In tkinter, how can I insert the value of an inherited class variable into a Label widget?

The issue I am having is specifically with the Label widget. In the linked code, the tests for Text widgets and Button widgets both grab the inherited value and display it correctly.
You can find the Label widget in question in class PageTwo
The variable I am trying to inherit and display within the Label is the "num" variable set in the first class.
The goal is to take that variable, set the value in another class, and then display the newly set value later in a Label widget.
I have tried to set the Label to display the variable directly, as a str value, within an f-string, as well as setting a local variable within PageTwo to take the value of TestClass.num
Example of the code in question is:
import tkinter as tk
class TestClass(tk.Tk):
num = None
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Game")
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 (PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(PageOne)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self,
text="Make a selection",
wraplength=450, justify='center')
label.pack(padx=10, pady=10)
TestClass.num = tk.StringVar()
tk.Radiobutton(self, text="1", variable=TestClass.num, value="1", ).pack()
tk.Radiobutton(self, text="2", variable=TestClass.num, value="2", ).pack()
tk.Radiobutton(self, text="3", variable=TestClass.num, value="3", ).pack()
view_selection = tk.Button(self, text="test selection", command=lambda: print(TestClass.num.get()))
view_selection.pack()
next_page = tk.Button(self, text="Next Page",
command=lambda: controller.show_frame(PageTwo))
next_page.pack(pady=10, padx=10)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# label = tk.Label(self, text=TestClass.num.get()
label = tk.Label(self, text=f"The number should show up here -> {TestClass.num.get()} <- ")
label.pack()
text1 = tk.Text(self)
text1.pack()
see_num = tk.Button(self, text="View Number",
command=lambda: text1.insert('1.0', TestClass.num.get()))
see_num.pack(pady=10, padx=10)
app = TestClass()
app.mainloop()
I have come across such uses of controller for a while now but never really got to know where the idea arose from. Anyway over here I see the problem might actually be in the program flow(when you instantiate with F(container, self) you are executing the __init__ of that class, hence the values are already being set.
When you select each item in the radio button, you want the value of the label to appropriately edit itself. So for that I think static variables are more appropriate(to access the widgets inside another class) and you can use command to fire a callback.
Also you need to fix the tristate issue of your Radiobutton, by giving different initial value to the StringVar to not be equal to the tristatevalue of Radiobutton or giving different tristatevalue to each radiobutton or to use ttk.Radiobutton that does not use this tristatevalue.
So the changes to be made for PageTwo are to create some static variables:
class PageTwo(tk.Frame):
label = ''
text = ''
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
PageTwo.text = "The number should show up here -> {} <- " # {} so we can `format` it to be whatever text we want, later
PageTwo.label = tk.Label(self, text=self.text.format(''))
PageTwo.label.pack()
...
...
And in PageOne, you need to set a command for Radiobutton that will be called each time an option is changed.
class PageOne(tk.Frame):
def __init__(self, parent, controller):
...
...
TestClass.num = tk.StringVar(value=' ')
command = lambda *args: PageTwo.label.config(text=PageTwo.text.format(TestClass.num.get()))
tk.Radiobutton(self, text="1", variable=TestClass.num, value="1",command=command).pack()
tk.Radiobutton(self, text="2", variable=TestClass.num, value="2",command=command).pack()
tk.Radiobutton(self, text="3", variable=TestClass.num, value="3",command=command).pack()
...
...
Another method I think possible is to create a function to create widgets, and load that up later when the page is supposed to be showed.

Accessing a tkinter list-box selection when using frames

I am trying to use frames in a tkinter window to change the layout when I user selects a range of options - in this case "Open".
I want the frame to update but I also need to capture the selection of the listbox. I have tried to access the selection from the method "openMat".
I have simplified the code as much as i can.
i have tried to solve this issue for a while, tried looking online for a solution and have finally resorted clicking the "ask a question" button.
import tkinter as tk
LARGE_FONT = ("Verdana", 12) # font's family is Verdana, font's size is 12
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# text for all windows
label2 = tk.Label(self, text='title', font=LARGE_FONT)
label2.pack(pady=10, padx=10) # center alignment
# this container contains all the pages
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1) # make the cell in grid cover the entire window
container.grid_columnconfigure(0,weight=1) # make the cell in grid cover the entire window
self.frames = {} # these are pages we want to navigate to
for F in (StartPage, Page2): # for each page
frame = F(container, self) # create the page
self.frames[F] = frame # store into frames
frame.grid(row=0, column=0, sticky="nsew") # grid it to container
self.show_frame(StartPage) # let the first page is StartPage
def show_frame(self, name):
frame = self.frames[name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
ltbox = tk.Listbox(self)
label = tk.Label(self, text='Menu', font=LARGE_FONT)
label.grid(row=0, column = 0)
#label.pack(pady=10, padx=10) # center alignment
button1 = tk.Button(self, text='Open', width = 12, # when click on this button, call the show_frame method to make PageOne appear
command=self.openMat)
button1.grid(row=1, column = 0)
#button1.pack() # pack it in
#Insert data in listbox
ltbox.insert( 1, "Option 1")
ltbox.insert( 2, "Option 2")
ltbox.insert( 3, "Option 3")
ltbox.insert( 4, "Option 4")
ltbox.grid(row=1, column = 4, rowspan=100, pady=0, padx=50)
print (ltbox.curselection())
def openMat(self):
#This function prints the option selected and changes the frame
print (ltbox.curselection())
app.show_frame(Page2)
class Page2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='Page Two', font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text='Back to Home', # likewise StartPage
command=lambda : controller.show_frame(StartPage))
button1.pack()
if __name__ == '__main__':
app = MainWindow()
app.mainloop()
This gives the error:
NameError: name 'ltbox' is not defined
thank you for reading my question - any help is much appreciated!
Your issue is of Scope.
ltbox is defined and hence can be used only inside the __init__ function of the class StartPage. If you want it to be accessible to all the functions of a class, you have to make it an instance attribute of the class, which is done by using self. So wherever you have used ltbox, just change it to self.ltbox.

Strange behavior of frames in tkinter

I have a project I am working on and it requires me to update a frame with new page information depending on what button is clicked.
I have 2 frames in the main page. One frame holds the buttons and the other frame is to be updated on button click. However when I click on a button it seams to remove both buttons and set up the new page on the first frame.
I don't see how this is possible as I have check each frame and they should be separate.
When I run the test1.py page I get this window as expected:
However this is what I get when I press one of the buttons:
I should expect to see the 2 buttons still there and the label now to the right of the buttons. I really cant see how this could be happening.
Here are my example pages.
test1.py contains:
import tkinter as tk
import test2
import test3
class TestApp(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.button_frame = tk.Frame(self)
self.button_frame.grid(row=0, column=0)
self.working_frame = tk.Frame(self)
self.working_frame.grid(row=0, column=1)
tk.Button(self.button_frame, text="Test 2", command=lambda: self.update_main_frame("test2")).grid(row=0, column=0)
tk.Button(self.button_frame, text="Test 3", command=lambda: self.update_main_frame("test3")).grid(row=1, column=0)
def update_main_frame(self, window_name):
if window_name == "test2":
self.reset_working_frame()
x = test2.TestFrame2(self.working_frame)
x.grid(row=0, column=0, sticky="nsew")
if window_name == "test3":
self.reset_working_frame()
x = test3.TestFrame3(self.working_frame)
x.grid(row=0, column=0, sticky="nsew")
def reset_working_frame(self):
self.working_frame.destroy()
self.working_frame = tk.Frame(self)
self.working_frame.grid(row=0, column=1)
if __name__ == "__main__":
root = tk.Tk()
TestApp(root).grid(row=0, column=0, sticky="nsew")
tk.mainloop()
test2.py contains:
import tkinter as tk
class TestFrame2(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self)
self.parent = parent
self.internal_frame = tk.Frame(self)
self.internal_frame.grid(row=0, column=0, sticky="nsew")
tk.Label(text="some small label").grid(row=0, column=0)
test3.py contains:
import tkinter as tk
class TestFrame3(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self)
self.parent = parent
self.internal_frame = tk.Frame(self)
self.internal_frame.grid(row=0, column=0, sticky="nsew")
tk.Label(text="some larger label!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!").grid(row=0, column=0)
You didn't give a parent frame to the TestFrame or the Label, so both default to root. Try this:
class TestFrame2(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent) # add parent Frame
self.parent = parent
self.internal_frame = tk.Frame(self)
self.internal_frame.grid(row=0, column=0, sticky="nsew")
lbl = tk.Label(self.internal_frame, text="some small label") # add parent Frame
lbl.grid(row=0, column=0)
Also, putting the initialization and layout on the same line leads to bugs. Use 2 lines.

Frames not stacking with pack()

I am trying to create a program in which the user navigates through the screens with "Next" and "Previous" buttons. Editing the code from the selected answer here I have produced the following code:
import Tkinter as tk
import tkFont as tkfont
def cancel():
root.destroy()
def disable_event():
pass
#Set the parent (main/root) window
root = tk.Tk()
root.title("Some title")
root.geometry('700x500') #Width x Height
root.resizable(0, 0) #Make root unresizable and force the use of "Cancel" button
root.protocol("WM_DELETE_WINDOW", disable_event)
class InstallerApp(tk.Tk):
def __init__(self, *args, **kwargs):
self.title_font = tkfont.Font(family='Helvetica', size=18)
container = tk.Frame(root)
container.pack(side="top", fill="both", expand=True)
self.frames = {}
for F in (Intro, FirstPage):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
self.show_frame("Intro")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class Intro(tk.Frame):
def __init__(self, parent, controller):
Intro = tk.Frame.__init__(self, parent)
self.controller = controller
leftFrame = tk.Frame(Intro, height=500, width=250, bg="#000000")
leftFrame.pack(side="left")
middleFrame = tk.Frame(Intro, height=500, width=5)
middleFrame.pack(side="left")
rightFrame = tk.Frame(Intro, height=500, width=450, bg="#FFFFFF")
buttonFrame = tk.Frame(Intro, height=35, width=450, bg="#FFFFFF")
nextButton = tk.Button(buttonFrame, width=10, text="Next >", command=lambda: controller.show_frame("FirstPage")).grid(row=0, column=0)
div3 = tk.Frame(buttonFrame, bg="#FFFFFF", width=10).grid(row=0, column=1)
cancelButton = tk.Button(buttonFrame, text="Cancel", width=10, command=cancel).grid(row=0,column=2)
buttonFrame.pack_propagate(False)
buttonFrame.pack(side="bottom")
#Other child widgets to rightFrame managed by pack
rightFrame.pack_propagate(False)
rightFrame.pack_forget()
rightFrame.pack(side="right")
class FirstPage(tk.Frame):
#the same code with "previousButton"
However, when I execute the code I notice that even if the show_frame function is called the FirstPage does not appear and that if I resize the main window it gradually appears from behind the Intro. When I run the original code, it works perfectly.
Is the problem because I am using the pack() manager while the original code uses grid() or what? Can somebody provide with a sample code?
P.S.: I have seen other questions but they all use grid(). I am using python 2.7.
You simply cannot use pack to overlay one widget on top of another within the same master. That's just not something that pack can do. pack is explicitly designed to place widgets in unallocated space above, below, or to the side of existing widgets in the same master.
If you want to stack frames on top of each other, you need to use either grid or place.

Change number binding depending on class within Tkinter Frame

I have a Tkinter GUI that is comprised of several classes all contained within one frame. All the classes have the same dimensions and are all loaded simultaneously on top of one another. However, user input determines which class is visible. Each class has the same layout and number of buttons as the others. ie: 3x3 grid of buttons.
I've started implementing .bind() functions that associate each button with the respective key on the number pad. The problem is that the .bind() functions remain static throughout all the classes because of the one frame.
How would I add .bind() functions to the following script to adjust depending on the currently visible class? Effectively changing the commands in order to match up with the corresponding 3x3 button grid.
import subprocess
import Tkinter as tk
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.grid(column=5, row=15)
container.grid_rowconfigure(0)
container.grid_columnconfigure(0)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nswe")
self.show_frame(StartPage)
self.bind('0',(lambda event: self.show_frame(StartPage)))
self.bind('1',(lambda event: self.show_frame(PageOne)))
self.bind('2',(lambda event: self.show_frame(PageTwo)))
def show_frame(self, c):
frame = self.frames[c]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
def randomfunction0():
subprocess.Popen('foobar', shell=True)
def randomfunction1():
subprocess.Popen('foobar', shell=True)
StartPageButton0 = tk.Button(self, compound="top", image=self.StartImage0, text="foobar0", fg="black", command=lambda: subprocess.Popen('foobar0', shell=True))
StartPageButton1 = tk.Button(self, compound="top", image=self.StartImage1, text="foobar1", fg="black", command=lambda: subprocess.Popen('foobar1', shell=True))
StartPageButton2 = tk.Button(self, compound="top", image=self.StartImage2, text="foobar2", fg="black", command=lambda: subprocess.Popen('foobar2', shell=True))
StartPageButton0.grid(row=1, column=1, padx=20, pady=10)
StartPageButton1.grid(row=1, column=2, padx=20, pady=10)
StartPageButton2.grid(row=1, column=3, padx=20, pady=10)
controller.bind('1',(lambda event: subprocess.Popen('foobar0', shell=True)))
controller.bind('2',(lambda event: subprocess.Popen('foobar1', shell=True)))
controller.bind('3',(lambda event: subprocess.Popen('foobar2', shell=True)))
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Page1Button0 = tk.Button(self, compound="top", image=self.Page1Image0, text="foobar0", fg="black", command=lambda: subprocess.Popen('foobar0', shell=True))
Page1Button1 = tk.Button(self, compound="top", image=self.Page1Image1, text="foobar1", fg="black", command=lambda: subprocess.Popen('foobar1', shell=True))
Page1Button2 = tk.Button(self, compound="top", image=self.Page1Image2, text="foobar2", fg="black", command=lambda: subprocess.Popen('foobar2', shell=True))
Page1Button0.grid(row=1, column=1, padx=20, pady=10)
Page1Button1.grid(row=1, column=2, padx=20, pady=10)
Page1Button2.grid(row=1, column=3, padx=20, pady=10)
controller.bind('1',(lambda event: subprocess.Popen('foobar0', shell=True)))
controller.bind('2',(lambda event: subprocess.Popen('foobar1', shell=True)))
controller.bind('3',(lambda event: subprocess.Popen('foobar2', shell=True)))
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# PageOne Repeated with different subprocess.Popen commands.
if __name__ == "__main__":
app = MainApp()
app.title("foobar title")
app.mainloop()
While we are at it, the one Frame setup also doesn't allow me to change the title displayed at the top of the GUI window. Would I be able to change app.title("foobar title") within each class? Opposed to having just one title displayed across all classes.
EDIT: I've tried using controller.bind() and self.bind() however self.bind doesn't change anything. The initial bindings in the MainApp() class are what get executed regardless of page focus.
If you do self.bind rather than controller.bind, the page-specific function should run, since only the page with focus will see the click. This is normally how you handle bindings -- only bind to the widgets that the bindings should apply to. In this case you don't want global bindings, you want bindings for each page.
The other solution is to bind them all to controller.handleKey(1), controller.handleKey(1), etc. handleKey is a generic function whose only function is to figure out which page is current, and call the handleKey of the current frame.

Categories