I've used Tkinter to create a GUI with different menu options (a similar example is produced below). Each menu has different commands, which when clicked create a new frame. Now what is happening is if I switch to a different command, the new frame stacks below the current frame instead of replacing the old one.
I want to know what is the best way to move forward.
import Tkinter as tkinter
root = tkinter.Tk()
root.minsize(400,300)
welcome = tkinter.Frame(root).grid()
label = tkinter.Label(welcome, text="Welcome to my program").grid(row=0, column=3)
button = tkinter.Button(welcome,text="Exit",command=root.destroy).grid(row=3, column=1)
def newFrame():
newFrame = tkinter.Frame(root).grid()
newFrame_name = tkinter.Label(newFrame, text="This is another frame").grid()
menu = tkinter.Menu(root)
root.config(menu=menu)
main_menu = tkinter.Menu(menu)
menu.add_cascade(label="Main Menu", menu= main_menu)
main_menu.add_command(label="New Frame", command=newFrame)
main_menu.add_command(label="Another Frame", command=newFrame)
#menu.add_command(label="Exit", command=root.destroy, menu= filemenu)
root.mainloop()
Now if I switch between New Frame and Another Frame, the windows stack up, but I want one window to replace the other.
Any ideas? Thanks.
Here is a minimal example of one method I used recently; the key is in PythonGUI.show_frame, which moves the appropriate frame to the front for display.
import Tkinter as tk
class BaseFrame(tk.Frame):
"""An abstract base class for the frames that sit inside PythonGUI.
Args:
master (tk.Frame): The parent widget.
controller (PythonGUI): The controlling Tk object.
Attributes:
controller (PythonGUI): The controlling Tk object.
"""
def __init__(self, master, controller):
tk.Frame.__init__(self, master)
self.controller = controller
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create the widgets for the frame."""
raise NotImplementedError
class ExecuteFrame(BaseFrame):
"""The application home page.
Attributes:
new_button (tk.Button): The button to switch to HomeFrame.
"""
def create_widgets(self):
"""Create the base widgets for the frame."""
self.new_button = tk.Button(self,
anchor=tk.W,
command=lambda: self.controller.show_frame(HomeFrame),
padx=5,
pady=5,
text="Home")
self.new_button.grid(padx=5, pady=5, sticky=tk.W+tk.E)
class HomeFrame(BaseFrame):
"""The application home page.
Attributes:
new_button (tk.Button): The button to switch to ExecuteFrame.
"""
def create_widgets(self):
"""Create the base widgets for the frame."""
self.new_button = tk.Button(self,
anchor=tk.W,
command=lambda: self.controller.show_frame(ExecuteFrame),
padx=5,
pady=5,
text="Execute")
self.new_button.grid(padx=5, pady=5, sticky=tk.W+tk.E)
class PythonGUI(tk.Tk):
"""The main window of the GUI.
Attributes:
container (tk.Frame): The frame container for the sub-frames.
frames (dict of tk.Frame): The available sub-frames.
"""
def __init__(self):
tk.Tk.__init__(self)
self.title("Python GUI")
self.create_widgets()
self.resizable(0, 0)
def create_widgets(self):
"""Create the widgets for the frame."""
# Frame Container
self.container = tk.Frame(self)
self.container.grid(row=0, column=0, sticky=tk.W+tk.E)
# Frames
self.frames = {}
for f in (HomeFrame, ExecuteFrame): # defined subclasses of BaseFrame
frame = f(self.container, self)
frame.grid(row=2, column=2, sticky=tk.NW+tk.SE)
self.frames[f] = frame
self.show_frame(HomeFrame)
def show_frame(self, cls):
"""Show the specified frame.
Args:
cls (tk.Frame): The class of the frame to show.
"""
self.frames[cls].tkraise()
if __name__ == "__main__":
app = PythonGUI()
app.mainloop()
exit()
There are two basic ways to solve the problem:
stack all of the frames on top of each other (eg: put in the same grid cell, or use place with the same options) and then raise the one that should be visible to the top of the stack (using frame.lift()). An example of the technique is in this answer: Switch between two frames in tkinter
Whenever you show a new frame, destroy (with .destroy() or hide (with pack_forget or grid_forget) the old frame.
I have a simple example. I'm sorry, because I don't know how to do this code by class. I just using simple code. But it's works!
:D
Here it is:
from Tkinter import *
def gantihal(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='goto frame 2', command=lambda:gantihal(f2)).pack()
Label(f1, text='this is in frame 1').pack()
Label(f2, text='this is in frame two').pack()
Button(f2, text='goto frame 3', command=lambda:gantihal(f3)).pack()
Label(f3, text='this is in frame 3').pack(side='left')
Button(f3, text='next frame :)', command=lambda:gantihal(f4)).pack(side='left')
Label(f4, text='fourth frame').pack()
Button(f4, text='goto 1st frame', command=lambda:gantihal(f1)).pack()
gantihal(f1)
root.mainloop()
Related
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.
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__.
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.
I am working on a very basic interface on Python with Tkinter, that displays two input boxes and a button to login. I try to do it by creating different frames and change the frame when the user is logged. It was working nearly fine but then the code started to execute itself not entirely sometimes and entirely but without the Tkinter window. I looked into it and saw nothing shocking but I am not an expert so I am looking for help.
This is the code to run my class that implement Tkinter window:
print 1
app = Skeleton("HomePage")
print 2
app.mainloop()
print 3
The skeleton Class that implement the Tkinter window:
class Skeleton(Tk):
def __init__(self, f,*args, **kwags):
Tk.__init__(self,*args, **kwags)
self.title(f)
container = Frame(self, width=512, height=512)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
frameName = {"home","upload","retrieve","deconnected"}
self.frames["HomePage"] = HomePage(parent= container, controller=self)
self.frames["HomePage"].grid(row=0, column=0, sticky="nsew")
print 321
self.show_frame("HomePage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
print "Je vais te montrer mon frame"
frame = self.frames[page_name]
frame.tkraise()
And the code of the Home Page frame:
class HomePage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.parent = parent
self.controller = controller
#print ("Construction de la page dáccueil")
#LABEL
self.username = Label(self, text="Username:")
self.username.grid(row =0,column =0)
self.username.pack()
#ENTRY
self.username_txb = Entry( self)
self.username_txb.focus_set()
self.username_txb.grid(row =0,column =1)
self.username_txb.pack(side=LEFT)
#LABEL
self.pass_lbl = Label(self, text="Password:")
self.pass_lbl.grid(row =0,column =2)
#ENTRY
self.password_txb = Entry( self, text="Password", show = "*")
self.password_txb.grid(row =0,column =3)
self.password_txb.pack(side=LEFT)
#LOGIN BUTTON
self.login_btn = Button(self, text="Login", command=lambda: controller.show_frame("UploadPage"))
self.login_btn.grid(row =0,column =4)
self.login_btn.pack(side=LEFT)
self.info_pane = PanedWindow()
self.info_pane.grid(row =1,column =0)
self.info_pane.pack(fill="none", expand=True, side=BOTTOM)
self.info_lbl = Label(self, text="More information about access:", fg="blue", cursor="hand2")
self.contact_lbl = Label(self, text="Contact us", fg="blue", cursor="hand2")
self.contact_lbl.grid(row =2,column =0)
self.contact_lbl.pack()
self.contact_lbl.bind("<Button-1>", self.callback)
print ("123Construction de la page dáccueil")
#self.parent.update()
def callback(self, event):
pass
def connect(self,controller ):
login = self.username_txb.get()
pwd = self.password_txb.get()
if(login == "a" and pwd == "a"):
print "Valid account"
self.controller.show_frame("UploadPage")
#UploadPage frame is implemented
The output everytime I execute the code is as following:
1
123Construction de la page dáccueil
Thank you in advance for the help. Hope this will help other people.
First lets address your use of pack() and grid().
Due to how tkinter is set up you cannot use both pack() and grid() on the same widget in a frame or window at one time.
You may use for example pack() to pack the main frame and grid() on the widgets inside that frame but you cannot use both in side the frame.
If one of your issues is where each widget is located and if it is expanding with the window you can manage all that inside of grid() so we can just use grid() here as its what I prefer when writing up a GUI.
Next we need to look at your call to show_frame as you are attempting to show a frame that does not exist in self.frames in the code you have presented us.
I have created a new class so your program can be tested with this line of code:
self.controller.show_frame("UploadPage")
The new class just makes a basic frame with a label in it showing that the frame does rise properly with tkrise().
I did some general clean up as your show_frame method was taking unnecessary steps to raise the frame, your method of importing tkinter is not the best option and some other quality corrections.
Instead of using:
frame = self.frames[page_name]
frame.tkraise()
We can simplify this method with just one line like this:
self.frames[page_name].tkraise()
I have also changed how you are importing tkinter as importing with * can sometimes cause problems if you inadvertently override build in methods. The best option is to import tkinter like this:
import tkinter as tk
Take a look at the below code and let me know if you have any questions. It should provide the info you need to allow the HomePage frame and UploadPage frame to work as intended.
import tkinter as tk
class Skeleton(tk.Tk):
def __init__(self, f,*args, **kwags):
tk.Tk.__init__(self,*args, **kwags)
self.title(f)
self.container = tk.Frame(self, width=512, height=512)
self.container.grid(row=0, column=0, sticky="nsew")
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frames["HomePage"] = HomePage(parent=self.container, controller=self)
self.frames["HomePage"].grid(row=0, column=0, sticky="nsew")
self.frames["UploadPage"] = UploadPage(parent=self.container)
self.frames["UploadPage"].grid(row=0, column=0, sticky="nsew")
self.show_frame("HomePage")
def show_frame(self, page_name):
self.frames[page_name].tkraise()
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.parent = parent
self.controller = controller
self.username = tk.Label(self, text="Username:")
self.username.grid(row =0,column =0)
self.username_txb = tk.Entry(self)
self.username_txb.focus_set()
self.username_txb.grid(row=0, column=1)
self.pass_lbl = tk.Label(self, text="Password:")
self.pass_lbl.grid(row =0,column =2)
self.password_txb = tk.Entry(self, text="Password", show="*")
self.password_txb.grid(row =0,column =3)
self.login_btn = tk.Button(self, text="Login", command=self.connect)
self.login_btn.grid(row=0, column=4)
self.info_pane = tk.PanedWindow()
self.info_pane.grid(row=1, column=0)
self.info_lbl = tk.Label(self, text="More information about access:", fg="blue", cursor="hand2")
self.contact_lbl = tk.Label(self, text="Contact us", fg="blue", cursor="hand2")
self.contact_lbl.grid(row=2, column=0)
self.contact_lbl.bind("<Button-1>", self.callback)
def callback(self, event):
pass
# webbrowser.open_new("https://www.tno.nl/nl/")
# I do not have the import for this webbrowser so I disabled it for testing.
def connect(self):
login = self.username_txb.get()
pwd = self.password_txb.get()
if(login == "a" and pwd == "a"):
self.controller.show_frame("UploadPage")
class UploadPage(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
tk.Label(self, text="This upload frame is a test to see if your code is working").grid(row=0, column=0)
if __name__ == "__main__":
app = Skeleton("HomePage")
app.mainloop()
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.