Strange behavior of frames in tkinter - python

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.

Related

How to swap between pages / windows in Tkinter

I have tried googling and everyone seems to have structured their code completely differently. I understand the base level tkinter, however I do not understand how people are using classes and def's to swap pages. How can I swap from my main window to my second one? (and not open the second one after the main is closed)
import tkinter as tk
main = tk.Tk()
main.title("Main Program")
firstlabel = tk.Label(main, text="This is a program!")
firstlabel.pack()
main.mainloop()
second = tk.Tk()
second.title("Second Program")
firstlabel = tk.Label(second, text="This is another program!")
firstlabel.pack()
second.mainloop()
EDIT: (solution)
import tkinter as tk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
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):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = tk.Label(self, text="Start Page", font=("Consolas", 30))
label.pack(pady=10,padx=10)
button = tk.Button(self, text="Visit Page 1", command=lambda: controller.show_frame(PageOne))
button.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Page One!!!", font=("Consolas", 30))
label.pack(pady=10,padx=10)
button1 = tk.Button(self, text="Back to Home", command=lambda: controller.show_frame(StartPage))
button1.pack()
app = Application()
app.mainloop()
One way do this is by clearing everything (every widget), with this function:
def clear(app):
# Delete everything else in app
widget_list = app.winfo_children()
for item in widget_list:
if item.winfo_children():
widget_list.extend(item.winfo_children())
for item in widget_list:
item.pack_forget()
And then puting in the new window you want to swap to (every window should hav its own frame to make it simpler).
Try this::::
tk.Toplevel(main)

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.

Tkinter: Code stops and window doesn't show up

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

Two windows appear on my tkinter app

I am quite new to python and tkinter and I was trying to open iamges to use them as icons on my program, so I found I have to use tk.Toplevel instead of tk.Tk. However, when I do this, the application opens two windows,the one where everything is programmed and a blank one.
Here is the full code in case anyone could help:
import tkinter as tk
from tkinter import ttk
from tkinter import PhotoImage
from PIL import Image, ImageTk
Large_Font = ("Times", 20)
class RobProject(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
tk.Tk.wm_title(self, "Com-Bot Control")
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 (MenuPage, PageOne, PageTwo, PageThree):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="NEWS")
self.show_frame(MenuPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class MenuPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = tk.Label(self, text = "Menú", font=Large_Font)
label.pack(pady=10, padx=10)
RCB = ttk.Button(self, text="Control Remoto",
command= lambda: controller.show_frame(PageOne))
RCB.pack()
EOB = ttk.Button(self, text="EvitaObstáculos",
command= lambda: controller.show_frame(PageTwo))
EOB.pack()
SLB = ttk.Button(self, text="Siguelíneas",
command= lambda: controller.show_frame(PageThree))
SLB.pack()
img = Image.open("play-button.png")
photo = ImageTk.PhotoImage(img)
Show_Im = tk.Button(self, image=photo)
Show_Im.image = photo
Show_Im.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text = "Control Remoto", font=Large_Font)
label.pack(pady=10, padx=10)
Back_B1 = ttk.Button(self, text="Atrás",
command= lambda: controller.show_frame(MenuPage))
Back_B1.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text = "Evitaobstáculos", font=Large_Font)
label.pack(pady=10, padx=10)
Back_B2 = ttk.Button(self, text="Atrás",
command= lambda: controller.show_frame(MenuPage))
Back_B2.pack()
class PageThree(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text = "Siguelíneas", font=Large_Font)
label.pack(pady=10, padx=10)
Back_B3 = ttk.Button(self, text="Atrás",
command= lambda: controller.show_frame(MenuPage))
Back_B3.pack()
app = RobProject()
app.mainloop()
Thank you in advance!
You do not need Topelevel() to use images.
Not sure where you got that info but it is not accurate.
Toplevel() is specifically to create new windows outside of the root window in tkinter.
What is happening is when you call tk.Toplevel() tkinter is creating an instance of Tk() in order to run Toplevel(). This is where the blank screen is coming from. The blank screen is actually the root window without anything in it.
To fix your problem Change:
class RobProject(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
To:
class RobProject(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)

Python tkinter callback

I have a container with a set of frames, that provide me with two pages (main page and an info page.) Buttons are used to navigate the pages.
All is working.
I have defined a function to remove folders on the OS. (shutil.rmtree) This function should be called by my button 2 on the main page, but it falls over and is not working.
import tkinter as tk
import shutil
from tkinter import *
TITLE_FONT = ("Helvetica", 18, "bold")
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# 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 (MainPage, InfoPage,):
frame = F(container, self)
self.frames[F] = 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(MainPage)
def show_frame(self, c):
'''Show a frame for the given class'''
frame = self.frames[c]
frame.tkraise()
class MainPage(tk.Frame):
def callback(self):
shutil.rmtree('C:\Test', ignore_errors=False)
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
photo = PhotoImage(file='C:\Logo.gif')
label = Label(self, image=photo)
label.Image = photo
label.pack(side=TOP)
button1 = tk.Button(self, text="Info!",
command=lambda: controller.show_frame(InfoPage))
button1.pack(side=LEFT, padx=5, pady=5)
button2 = tk.Button(self, text="Clean!", command=callback)
button2.pack(side=RIGHT, padx=5, pady=5)
class InfoPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Info Page", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Back",
command=lambda: controller.show_frame(MainPage))
button.pack()
if __name__ == "__main__":
app = App()
app.mainloop()

Categories