How to create a sub frames with a specific layout? - python-3.x

I'm aiming to make a login program but the only part that confuses me is how to make the frames.I need 3 different frames but I neither know how to make a frame other the then like this:
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
and I can only make labels and widgets using that single mainframe. As far as making another one, it is beyond me. I need to know exactly place widets inside of each frame and even after creating frames I don't know how to place stuff on the grid. Would I go for the overall grid, or does something change after making the grid. I'm using the following layout for making the frame. Basically i'm hoping for a crash course in frames. Any information i've gathered doesn't make sense to me, even after I tried to put it into code.
I've got the coding part down just not the frame part.
#Import tkinter to make gui
from tkinter import *
from tkinter import ttk
import codecs
def login(*args
):
file = open("rot13.txt", "r")
lines = file.readlines()
uname = user.get()
pword = pw.get()
for i in lines:
x = i.split()
if codecs.encode(uname,'rot13') == x[0] and codecs.encode(pword,'rot13') == x[1]:
result.set("Successful")
break;
else:
result.set("Access Denied")
root = Tk()
root.title("Login")
#Configures column and row settings and sets padding
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
user = StringVar()
pw = StringVar()
result = StringVar()
user_entry = ttk.Entry(mainframe, width=20, textvariable=user)
user_entry.grid(column=2, row=1, sticky=(W, E))
pw_entry = ttk.Entry(mainframe, width=20, textvariable=pw)
pw_entry.grid(column=2, row=2, sticky=(W, E))
ttk.Label(mainframe, text="Username ").grid(column=1, row=1, sticky=W)
ttk.Label(mainframe, text="Password ").grid(column=1, row=2, sticky=W)
ttk.Label(mainframe, text="").grid(column=1, row=3, sticky=W)
ttk.Label(mainframe, text="Result").grid(column=1, row=4, sticky=W)
ttk.Label(mainframe, text="").grid(column=1, row=5, sticky=W)
ttk.Button(mainframe, text="Login", command=login).grid(column=3, row=6, sticky=W)
#Makes a spot to put in result
ttk.Label(mainframe, textvariable=result).grid(column=2, row=4, sticky=(W, E))
#Opens up with item selected and allows you to enter username without having to click it
user_entry.focus()
#Runs calculate if click enter
root.bind('<Return>', login)
root.mainloop()

I believe the key point that you are missing is that subframes of mainframe use mainframe as the parent and that widgets within subframes use the subframe as parent. Furthermore, you can then place the subframe within the mainframe and the subframe widgets within the subframe. You do not have to pass parents to .grid because each widget knows its parent. A simplified example:
from tkinter import *
root = Tk()
mainframe = Frame(root)
login = Frame(mainframe)
label = Label(login, text='label')
entry = Entry(login)
display = Frame(mainframe)
result = Label(display, text='display result')
mainframe.grid() # within root
login.grid(row=0, column=0) # within mainframe
label.grid(row=0, column=0) # within login
entry.grid(row=0, column=1) # within login
display.grid() # within mainfram
result.grid(row=2, column=0) # within display

Related

How to define specific commands for each button included in each pane of a PanedWindow? [duplicate]

This question already has an answer here:
Tkinter assign button command in a for loop with lambda [duplicate]
(1 answer)
Closed last year.
I want to create a PanedWindow with variable number of panes which every one of these panes includes a label and a button. Pressing a button in a pane should write a messege to the corresponding label in that pane.
I tried this code:
from tkinter import *
from tkinter import ttk
n = 5
root = Tk()
root.geometry('250x500+100+100')
p = ttk.Panedwindow(root, orient=VERTICAL)
p.grid(column=0, row=0, sticky=(N, S, W, E))
for i in range(n):
pane = ttk.Labelframe(p, width=25, borderwidth=0)
p.add(pane)
def writeToLabel():
paneLabel.config(text='This is Pane number %d' %(i+1))
paneLabel = ttk.Label(pane, width=20, relief='solid')
paneButton = ttk.Button(pane, text='Press', command=writeToLabel)
paneButton.grid(column=0, row=0, padx=5)
paneLabel.grid(column=1, row=0, padx=5)
root.rowconfigure(0, weight=1)
root.mainloop()
But no matter which button is pressed the label in the last row is set with the message.
I would be grateful if somebody could help me to fix the problem.
I think this is the sort of thing you are looking for:
from tkinter import *
from tkinter import ttk
n = 5
root = Tk()
root.geometry('250x500+100+100')
p = ttk.Panedwindow(root, orient=VERTICAL)
p.grid(column=0, row=0, sticky=(N, S, W, E))
def writeToLabel(pl, i):
pl.config(text='This is Pane number %d' %(i+1))
for i in range(n):
pane = ttk.Labelframe(p, width=25, borderwidth=0)
p.add(pane)
paneLabel = ttk.Label(pane, width=20, relief='solid')
paneButton = ttk.Button(pane, text='Press', command=lambda pl=paneLabel, i=i: writeToLabel(pl, i))
paneButton.grid(column=0, row=0, padx=5)
paneLabel.grid(column=1, row=0, padx=5)
root.rowconfigure(0, weight=1)
root.mainloop()
Your current method does not work as the definition of writeToLabel will use the last value of paneLabel and i. You instead need to pass a reference to the label and the value of i using a lambda function. The pl=paneLabel and i=i parts of the lambda function are explained here.

TKInter - Confused about frames and scrolling

I am trying to code a tkinter application that has three frames - a top frame, where the user inputs some text, a dynamically constructed middle section where some pre-analysis is conducted on the text, and a bottom frame where, once the user has selected which option they want in the middle section, the output will be produced.
The problem is that, depending upon the input, there could be around 10-20 (and in the worst case 30) lines displayed and on a small monitor the output will disappear off the screen.
What I would like is for the top (input) and bottom (output) frames to be visible no matter how the screen is re-sized, and for the middle section to scroll (if required) and still allow the user to select their choice.
I am confused as to how to get the middle section to resize when the screen is resized, show a scrollbar if required, and still allow all of the content to be accessed.
I have created a cut-down version here (for simplicity, I have removed the processing methods and have instead created some fake output in a loop that resembles what the actual middle section would look like).
Please ignore the hideous colour-scheme - I was just trying to understand which frame went where (I will remove the colours as soon as I can!)
Thank you for any suggestions...
import tkinter as tk
from tkinter import scrolledtext
class MyApp(tk.Tk):
def __init__(self, title="Sample App", *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title(title)
self.configure(background="Gray")
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# Create the overall frame:
master_frame = tk.Frame(self, bg="Light Blue", bd=3, relief=tk.RIDGE)
master_frame.grid(sticky=tk.NSEW)
master_frame.rowconfigure([0, 2], minsize=90) # Set min size for top and bottom
master_frame.rowconfigure(1, weight=1) # Row 1 should adjust to window size
master_frame.columnconfigure(0, weight=1) # Column 0 should adjust to window size
# Create the frame to hold the input field and action button:
input_frame = tk.LabelFrame(master_frame, text="Input Section", bg="Green", bd=2, relief=tk.GROOVE)
input_frame.grid(row=0, column=0, padx = 5, pady = 5, sticky=tk.NSEW)
input_frame.columnconfigure(0, weight=1)
input_frame.rowconfigure(0, weight=1)
# Create a frame for the middle (processing) section.
middle_frame = tk.LabelFrame(master_frame, text = "Processing Section")
middle_frame.grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)
# Create the frame to hold the output:
output_frame = tk.LabelFrame(master_frame, text="Output Section", bg="Blue", bd=2, relief=tk.GROOVE)
output_frame.grid(row=2, column=0, columnspan=3, padx=5, pady=5, sticky=tk.NSEW)
output_frame.columnconfigure(0, weight=1)
output_frame.rowconfigure(0, weight=1)
# Add a canvas in the middle frame.
self.canvas = tk.Canvas(middle_frame, bg="Yellow")
self.canvas.grid(row=0, column=0)
# Create a vertical scrollbar linked to the canvas.
vsbar = tk.Scrollbar(middle_frame, orient=tk.VERTICAL, command=self.canvas.yview)
vsbar.grid(row=0, column=1, sticky=tk.NS)
self.canvas.configure(yscrollcommand=vsbar.set)
# Content for the input frame, (one label, one input box and one button).
tk.Label(input_frame,
text="Please type, or paste, the text to be analysed into this box:").grid(row=0, columnspan = 3, sticky=tk.NSEW)
self.input_box = scrolledtext.ScrolledText(input_frame, height=5, wrap=tk.WORD)
self.input_box.columnconfigure(0, weight=1)
self.input_box.grid(row=1, column=0, columnspan = 3, sticky=tk.NSEW)
tk.Button(input_frame,
text="Do it!",
command=self.draw_choices).grid(row=2, column=2, sticky=tk.E)
# Content for the output frame, (one text box only).
self.output_box = scrolledtext.ScrolledText(output_frame, width=40, height=5, wrap=tk.WORD)
self.output_box.grid(row=0, column=0, columnspan=3, sticky=tk.NSEW)
def draw_choices(self):
""" This method will dynamically create the content for the middle frame"""
self.option = tk.IntVar() # Variable used to hold user's choice
self.get_input_text()
for i in range(30):
tk.Radiobutton(self.canvas,
text=f"Option {i + 1}: ", variable=self.option,
value=i,
command=self.do_analysis
).grid(row=i, column=0, sticky=tk.W)
tk.Label(self.canvas,
text=f"If you pick Option {i + 1}, the output will look like this: {self.shortText}.",
anchor=tk.W
).grid(row=i, column=1, sticky=tk.W)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def get_input_text(self):
""" Will get the text from the input box and also create a shortened version to display on one line"""
screenWidth = 78
self.input_text = self.input_box.get(0.0, tk.END)
if len(self.input_text) > screenWidth:
self.shortText = self.input_text[:screenWidth]
else:
self.shortText = self.input_text[:]
self.shortText = self.shortText.replace('\n', ' ') # strip out carriage returns just in case
def do_analysis(self):
"""This will ultimately process and display the results"""
option = self.option.get() # Get option from radio button press
output_txt = f"You picked option {option + 1} and here is the output: \n{self.input_text}"
self.output_box.delete(0.0, tk.END)
self.output_box.insert(0.0, output_txt)
if __name__ == "__main__":
app = MyApp("My Simple Text Analysis Program")
app.mainloop()
I understand that you can't mix grid and pack geometries in the same container, and that a scrollbar must be attached to a canvas, and objects to be placed on that canvas must therefore be in yet another container so, attempting to follow Bryan's example, I created a minimal version of what I want - window with three sections - top, middle and bottom. The Top and bottom sections will contain a simple text field, the middle section will contain dynamic content and must be able to scroll as required.
Imports:
ScrollbarFrame
Extends class tk.Frame to support a scrollable Frame]
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("A simple GUI")
# Top frame
self.top_frame = tk.Frame(self, bg="LIGHT GREEN")
self.top_frame.pack(fill=tk.X)
tk.Label(self.top_frame, bg=self.top_frame.cget('bg'),
text="This is a label on the top frame")\
.grid(row=0, columnspan=3, sticky=tk.NSEW)
# Middle Frame
# Import from https://stackoverflow.com/a/62446457/7414759
# and don't change anything
sbf = ScrollbarFrame(self, bg="LIGHT BLUE")
sbf.pack(fill=tk.X, expand=True)
# self.middle_frame = tk.Frame(self, bg="LIGHT BLUE")
self.middle_frame = sbf.scrolled_frame
# Force scrolling by adding multiple Label
for _ in range(25):
tk.Label(self.middle_frame, bg=self.middle_frame.cget('bg'),
text="This is a label on the dynamic (middle) section")\
.grid()
# Bottom Frame
self.bottom_frame = tk.Frame(self, bg="WHITE")
self.bottom_frame.pack(fill=tk.X)
tk.Label(self.bottom_frame, bg=self.bottom_frame.cget('bg'),
text="This is a label on the bottom section")\
.grid(row=0, columnspan=3, sticky=tk.NSEW)
if __name__ == '__main__':
App().mainloop()

tkinter error calling function through entries in listbox

Recently, I tried to make a full application window with a side panel menu with separate frames running some functions and submitting forms in the canvas frame.
But I found that every time I click on any entry in listbox it runs the function or method without clearing the existing one .
I tried destroy() and forget() didn't work for me (maybe I didn't know exactly how to use it?!, and the destroy() function prevent using the function again till I close the whole application and run it again!) this is a photo of my problem
this is my code :
import tkinter as tk
from tkinter import ttk
class MainWindow() :
def __init__(self,root):
# menu left
self.menu_upper_frame = tk.Frame(root, bg="#dfdfdf")
self.menu_title_label = tk.Label(self.menu_upper_frame, text="menu title", bg="#dfdfdf")
self.menu_title_label.pack()
self.menu_left_container = tk.Frame(root, width=150, bg="#ababab")
self.menu_left_upper = tk.Frame(self.menu_left_container, width=150, height=150, bg="red")
self.menu_left_upper.pack(side="top", fill="both", expand=True)
# create a listbox of items
self.Lb1 = tk.Listbox(self.menu_left_upper,bg ="red", borderwidth=0, highlightthickness=0 )
self.Lb1.insert(1, "Python")
self.Lb1.insert(2, "Perl")
self.Lb1.insert(3, "C")
self.Lb1.insert(4, "PHP")
self.Lb1.insert(5, "JSP")
self.Lb1.insert(6, "Ruby")
self.Lb1.bind("<<ListboxSelect>>", self.OnClick ) #return selected item
self.Lb1.pack(fill="both", expand=True, pady=50 )
# right area
self.inner_title_frame = tk.Frame(root, bg="#dfdfdf")
self.inner_title_label = tk.Label(self.inner_title_frame, text="inner title", bg="#dfdfdf")
self.inner_title_label.pack()
self.canvas_area = tk.Canvas(root, width=500, height=400, background="#ffffff")
self.canvas_area.grid(row=1, column=1)
# status bar
self.status_frame = tk.Frame(root)
self.status = tk.Label(self.status_frame, text="this is the status bar")
self.status.pack(fill="both", expand=True)
self.menu_upper_frame.grid(row=0, column=0, rowspan=2, sticky="nsew")
self.menu_left_container.grid(row=1, column=0, rowspan=2, sticky="nsew")
self.inner_title_frame.grid(row=0, column=1, sticky="ew")
self.canvas_area.grid(row=1, column=1, sticky="nsew")
self.status_frame.grid(row=2, column=0, columnspan=2, sticky="ew")
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(1, weight=1)
def OnClick(self,event):
widget = event.widget
selection = widget.curselection()
value = widget.get(selection)
if value == 'Python':
self.tabtop()
def tabtop(self):
self.tabControl = ttk.Notebook(self.canvas_area, width=400) # Create Tab Control
self.tab1 = ttk.Frame(self.tabControl) # Create a tab
self.tab2 = ttk.Frame(self.tabControl)
self.tab3 = ttk.Frame(self.tabControl)
self.tab4 = ttk.Frame(self.tabControl)
self.tab5 = ttk.Frame(self.tabControl)
self.tabControl.add(self.tab1, text='Login data' ) # Add the tab
self.tabControl.add(self.tab2, text='Permission')
self.tabControl.add(self.tab3, text='Roles')
self.tabControl.add(self.tab4, text='Personal data')
self.tabControl.add(self.tab5, text='Business data')
self.tabControl.pack(expand=1, fill="both") # Pack to make visible
self.l2 = tk.Label(self.tab2, text="label 2").pack()
self.l3 = tk.Label(self.tab3, text="label 3").pack()
root = tk.Tk()
root.title("Control Panel")
root.style = ttk.Style()
root.style.theme_use("clam")
user = MainWindow(root)
root.mainloop()
If what you're really asking is how to replace an existing notebook with a new notebook, all you need to do is call destroy() on the old notebook before creating the new one.
First, define self.tabControl to None somewhere in MainWindow.__init__. Then, in tabtop you can delete the old notebook before creating the new one:
def tabtop(self):
if self.tabControl is not None:
self.tabControl.destroy()
...

How to access the checkbox value from different frames?

I am creating a tkinter application where trying to get value from multiple checkboxes from different frames.When i am clicking the checkbox in one frame it is also checked in the other frame. I want to know how to access the checkbox value from different frames.
when trying to checking the checkbox (Tb1 in frame1) it is also checking in (NLTb1 in NLframe). I would like know how to access these two check box values separately.
from tkinter import *
def create_widgets_in_first_frame():
task_type=Button(main_frame,text='Task Type',command=call_second_frame_on_top).grid(row=6,column=0,sticky=W)
Network_Location=Button(main_frame,text='Network Location',command=call_third_frame_on_top).grid(row=7,column=0,sticky=W)
def create_widgets_in_second_frame():
T1=Label(frame1,text="Verify and ensure there is no duplicate entries present in task type",bg='Light blue')
T1.grid(row=3,columnspan=2,sticky=W)
#creating checkbutton
Tb1=Checkbutton(frame1,text='Pass',font=('Times New Roman',14),bg='Green')
Tb1.grid(row=3,column=4,padx=6)
#creating checkbuttonx
r2=Checkbutton(frame1,text='Fail',font=('Times New Roman',14),bg='red')
r2.grid(row=3,column=5,padx=6)
#creating Run button
b1=Button(frame1,text='Run').grid(row=3,column=6,padx=6)
button=Button(frame1,text='Go to Main page',command=call_first_frame_on_top)
button.grid(row=20,column=0,padx=6,sticky=W)
def create_widgets_in_third_frame():
NL1=Label(NLframe,text="Verify the migrated Network location in NGMSS and ensure all the mandatory information's are migrated along with it",bg='Light blue')
NL1.grid(row=3,columnspan=2,sticky=W)
#creating checkbutton
NLTb1=Checkbutton(NLframe,text='Pass',font=('Times New Roman',14),bg='Green')
NLTb1.grid(row=3,column=4,padx=6)
#creating checkbuttonx
nbr2=Checkbutton(NLframe,text='Fail',font=('Times New Roman',14),bg='red')
nbr2.grid(row=3,column=5,padx=6)
#creating Run button
nlb1=Button(NLframe,text='Run').grid(row=3,column=8)
button=Button(NLframe,text='Go to Main page',command= call_first_frame_on_top)
button.grid(row=20,column=0,padx=6,sticky=W)
def call_first_frame_on_top():
frame1.grid_forget()
NLframe.grid_forget()
main_frame.grid(row=0,column=0,sticky=W+E+N+S)
def call_second_frame_on_top():
NLframe.grid_forget()
main_frame.grid_forget()
frame1.grid(row=0,column=0,sticky=W+N+E+S)
def call_third_frame_on_top():
NLframe.grid(row=0,column=0,sticky=W+N+E+S)
frame1.grid_forget()
main_frame.grid_forget()
def quit_program():
root_window.destroy()
def raise_frame():
main_frame.tkraise()
root= Tk()
main_frame=Frame(root,height=30,width=500,borderwidth=10,bg='Powder Blue',highlightthickness=10,highlightcolor="red")
main_frame.grid(row=0,column=0,sticky=W+E+N+S)
frame1=Frame(root,height=30,width=500,borderwidth=10,bg='Powder Blue',highlightthickness=10,highlightcolor="red")
frame1.grid(row=0,column=0,sticky=W+N+E+S)
NLframe=Frame(root,height=30,width=500,borderwidth=10,bg='Powder Blue',highlightthickness=5,highlightcolor="red")
NLframe.grid(row=1,column=0,sticky=W+N+E+S)
create_widgets_in_third_frame()
create_widgets_in_second_frame()
create_widgets_in_first_frame()
frame1.grid_forget()
NLframe.grid_forget()
raise_frame()
root.mainloop()
`
Firstly, in your quit_program function, you call for root_window to be destroyed. You never defined this prior. My guess is you mean root.destroy()?
Secondly, I would say you might want to consider using radio buttons instead of checkboxes. Radio buttons will exclusively allow one option to be checked. This would prevent you from checking both the Pass and the Fail box on the same page.
As far as question is concerned, the reason your checkboxes are staying checked in the other frame because you are only forgetting the grid and not destroying the widgets. The grid is just what is organizing the widgets, not the actual widgets themselves. Therefore when you forget the grid and move to a new frame, the widget in that grid spot retains its state. Thats not to say destroying the widgets is the solution however. To fix your problem just make sure that you are storing the state of the button in a variable. I've tweaked your code a little bit and got it working.
from tkinter import *
def create_widgets_in_first_frame():
task_type = Button(main_frame, text='Task Type', command=call_second_frame_on_top).grid(row=6, column=0, sticky=W)
Network_Location = Button(main_frame, text='Network Location', command=call_third_frame_on_top).grid(row=7,
column=0,
sticky=W)
def create_widgets_in_second_frame():
t_var1 = BooleanVar()
t_var2 = BooleanVar()
T1 = Label(frame1, text="Verify and ensure there is no duplicate entries present in task type", bg='Light blue')
T1.grid(row=3, columnspan=2, sticky=W)
# creating checkbutton
Tb1 = Checkbutton(frame1, text='Pass', font=('Times New Roman', 14), bg='Green', variable=t_var1)
Tb1.grid(row=3, column=4, padx=6)
# creating checkbuttonx
r2 = Checkbutton(frame1, text='Fail', font=('Times New Roman', 14), bg='red', variable=t_var2)
r2.grid(row=3, column=5, padx=6)
# creating Run button
b1 = Button(frame1, text='Run').grid(row=3, column=6, padx=6)
button = Button(frame1, text='Go to Main page', command=call_first_frame_on_top)
button.grid(row=20, column=0, padx=6, sticky=W)
def create_widgets_in_third_frame():
nl_var1 = BooleanVar()
nl_var2 = BooleanVar()
NL1 = Label(NLframe,
text="Verify the migrated Network location in NGMSS and ensure all the mandatory information's are migrated along with it",
bg='Light blue')
NL1.grid(row=3, columnspan=2, sticky=W)
# creating checkbutton
NLTb1 = Checkbutton(NLframe, text='Pass', font=('Times New Roman', 14), bg='Green', variable=nl_var1)
NLTb1.grid(row=3, column=4, padx=6)
# creating checkbuttonx
nbr2 = Checkbutton(NLframe, text='Fail', font=('Times New Roman', 14), bg='red', variable=nl_var2)
nbr2.grid(row=3, column=5, padx=6)
# creating Run button
nlb1 = Button(NLframe, text='Run').grid(row=3, column=8)
button = Button(NLframe, text='Go to Main page', command=call_first_frame_on_top)
button.grid(row=20, column=0, padx=6, sticky=W)
def call_first_frame_on_top():
frame1.grid_forget()
NLframe.grid_forget()
main_frame.grid(row=0, column=0, sticky=W + E + N + S)
def call_second_frame_on_top():
NLframe.grid_forget()
main_frame.grid_forget()
frame1.grid(row=0, column=0, sticky=W + N + E + S)
def call_third_frame_on_top():
frame1.grid_forget()
main_frame.grid_forget()
NLframe.grid(row=0, column=0, sticky=W + N + E + S)
def quit_program():
root.destroy()
def raise_frame():
main_frame.tkraise()
root = Tk()
main_frame = Frame(root, height=30, width=500, borderwidth=10, bg='Powder Blue', highlightthickness=10,
highlightcolor="red")
main_frame.grid(row=0, column=0, sticky=W + E + N + S)
frame1 = Frame(root, height=30, width=500, borderwidth=10, bg='Powder Blue', highlightthickness=10,
highlightcolor="red")
frame1.grid(row=0, column=0, sticky=W + N + E + S)
NLframe = Frame(root, height=30, width=500, borderwidth=10, bg='Powder Blue', highlightthickness=5,
highlightcolor="red")
NLframe.grid(row=1, column=0, sticky=W + N + E + S)
create_widgets_in_third_frame()
create_widgets_in_second_frame()
create_widgets_in_first_frame()
frame1.grid_forget()
NLframe.grid_forget()
raise_frame()
root.mainloop()
Side note: In the future please take steps to make sure your code is readable and that you use proper naming conventions. You can find the PEP 8 Style Guide here.

Tkinter GUI failing to exit

I have a code written and at the top in the file I have the option to Exit which closes the GUI. For some reason when I click "Exit" it doesn't exit. I feel as if there is a simple error that I made when writing this code, but I can't seem to find it. I hope another set of eyes might be able to do so.
#Import tkinter to make gui
from tkinter import *
from tkinter import ttk
#Makes the GUI the same way it was at the beginning of the program
def login(*args):
try:
if (len(name.get())>=2) and (len(pw.get())>=2):
name_entry.delete(0, END)
pw_entry.delete(0, END)
valid.set("")
else:
valid.set("Invalid!")
pass
except TypeError:
valid.set("Invalid!")
pass
except ValueError:
valid.set("Invalid!")
pass
#Sets title and creates gui
root = Tk()
root.title("Login")
topMenu=Menu(root)
root.config(menu=topMenu)
#Creates menu and submenus
subMenu=Menu(topMenu)
#Gives menu options
topMenu.add_cascade(label="File", menu=subMenu)
subMenu.add_command(label="Exit", command=root.quit)
#Configures column and row settings and sets padding
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
name=StringVar()
pw=StringVar()
valid=StringVar()
#Widgets asking name and age
name_entry = ttk.Entry(mainframe, width=15, textvariable=name)
name_entry.grid(column=2, row=1, sticky=(W, E))
pw_entry = ttk.Entry(mainframe, width=15, textvariable=pw)
pw_entry.grid(column=2, row=2, sticky=(W, E))
ttk.Label(mainframe, text="Username").grid(column=1, row=1, sticky=(N, W))
ttk.Label(mainframe, text="Password").grid(column=1, row=2, sticky=(N,W))
#ttk.Label(mainframe, text=" ").grid(column=3, row=3, sticky=(E))
#Makes a button that clears everything and returns it to start of program
ttk.Button(mainframe, text="Login", width=30, command=login).grid(column=2, row=3, columnspan=2, sticky=(W, E))
ttk.Label(mainframe, textvariable=valid).grid(column=1, row=3, sticky=(E))
for child in mainframe.winfo_children(): child.grid_configure(padx=5, pady=5)
name_entry.focus()
root.bind('<Return>', login)
root.mainloop()
"try changing root.quit to root.destroy to see if that works any better." (Bryan Oakley)
This managed to do it. I don't know why but root.quit doesn't do it on idle.

Resources