My code is below. It is a very simple UI for logging into my program. The program has multiple TopLevel() instances branching from it, excluded for relevance and brevity. My issue is that once the user logs in, and a top-level instance appears, the main window(below) stays open in the background. Running both self.quit() and self.destroy() methods in the functions of the topLevel instances terminate the entire program instead of simply closing the main window. I believe it is due to how I declared my class but I do not know how to fix it. Any help would be greatly appreciated.
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.KTitle = tk.Label(self, text="Login ")
self.KTitle.grid(row=2,column=0, sticky=E)
self.KUsername = tk.Label(self, text="Username: ")
self.KUsername.grid(row=3,column=0, sticky=E)
self.KPassword = tk.Label(self, text="Password: ")
self.KPassword.grid(row=4,column=0, sticky=E)
self.KUEntry = tk.Entry(self, width=15)
self.KUEntry.grid(row=3,column=1, sticky=W)
self.KUPass = tk.Entry(self, show = '*', width=15)
self.KUPass.grid(row=4,column=1, sticky=W)
Related
I'm sure this is going to amount to my misunderstanding of what I'm calling. So I'm trying to make edits to a second window but I don't know that I'm doing it right as it doesn't appear to change. Under def open_win() I created a second window registration(which is supposed to be the equivalent of root). I got the second window to take the Screen position/size but for some reason it wont add the label/entry
from tkinter import *
from functools import partial
#outputs to IDLE
def validateLogin(username, password):
print("username entered :", username.get())
print("password entered :", password.get())
return
#centering Registration page
def open_win():
registration=Toplevel(root)
registration.title("Registration Page")
window_width=600
window_height=400
screen_width =registration.winfo_screenwidth()
screen_height =registration.winfo_screenheight()
center_x=int(screen_width/2-window_width/2)
center_y=int(screen_height/2-window_height/2)
registration.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
#registration label and text entry box
usernameLabel=Label(registration, text="User Name").grid(row=0, column=1)
username=StringVar()
usernameEntry=Entry(registration, textvariable =UserName).grid(row=0, column=2)
#Root Window
root=Tk()
root.title('Sign in Page')
#centering window
window_width=600
window_height=400
screen_width =root.winfo_screenwidth()
screen_height =root.winfo_screenheight()
center_x=int(screen_width/2-window_width/2)
center_y=int(screen_height/2-window_height/2)
root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
#username label and text entry box
usernameLabel=Label(root, text="User Name").grid(row=0, column=1)
username=StringVar()
usernameEntry=Entry(root, textvariable=username).grid(row=0, column=2)
#password label and password entry box
passwordLabel=Label(root,text="Password").grid(row=1, column=1)
password=StringVar()
passwordEntry=Entry(root, textvariable=password, show='*').grid(row=1, column=2)
validateLogin=partial(validateLogin, username, password)
#login button
loginButton=Button(root, text="Login", command=validateLogin).grid(row=4, column=1)
SignUpButton=Button(root, text="Sign up", command=open_win).grid(row=4, column=2)
#registration label and text entry box
usernameLabel=Label(registration, text="User Name").grid(row=0, column=1)
username=StringVar()
usernameEntry=Entry(registration, textvariable =UserName).grid(row=0, column=2)
root.mainloop()
Your main issue with not being able to work with the 2nd window is basically a issue of namespace. Your registration variable is stored in the local namespace of the function. If you want to edit it from outside the function like you attempt to do then you need your variable to be in the global namespace.
Because you appear to try and write the same label and entry field a couple of times to the registration top window then I suspect you do not actually need to edit it from outside the function but need to edit it when you created the window.
I have cleaned up your code a little and condensed it to make it a little easier to read.
You should first import tkinter ask tk instead of importing *. This will help prevent any issue with overwriting imports down the road and it makes it a little easier to ID what is referencing a tk widget or some other function.
You use 2 different naming conventions in your code. Chose one and stick with that. It will improve readability. I recommend following PEP8 guidelines.
Items that are not going to be changed later do not need to have variables assigned to them so you can clean up your code a bit there also.
You do not need to go the extra mile to use StringVar here. We can simply pull directly from the entry field as long as the geometry manager (ie grid()) is assigned on a new line so you can still access the variable reference for the entry field.
I am not sure what you were needing partial() for and I think you should use lambda instead in this situation.
If you have any questions let me know.
import tkinter as tk
def validate_login(username, password):
print("username entered :", username.get())
print("password entered :", password.get())
def open_win():
reg = tk.Toplevel(root)
reg.title("Registration Page")
reg.geometry(f'600x400+{int(reg.winfo_screenwidth()/2-600/2)}+{int(reg.winfo_screenheight()/2-400/2)}')
tk.Label(reg, text="User Name").grid(row=0, column=1)
r_un = tk.Entry(reg)
r_un.grid(row=0, column=2)
root = tk.Tk()
root.title('Sign in Page')
root.geometry(f'600x400+{int(root.winfo_screenwidth()/2-600/2)}+{int(root.winfo_screenheight()/2-400/2)}')
tk.Label(root, text="User Name").grid(row=0, column=1)
un = tk.Entry(root)
un.grid(row=0, column=2)
tk.Label(root, text="Password").grid(row=1, column=1)
pw = tk.Entry(root, show='*')
pw.grid(row=1, column=2)
tk.Button(root, text="Login", command=lambda u=un, p=pw: validate_login(u, p)).grid(row=4, column=1)
tk.Button(root, text="Sign up", command=open_win).grid(row=4, column=2)
root.mainloop()
When I was trying to play a "yay" sound effect after the user has passed the test, I will show the credits. However, considering that some users are impatient and they don't want to see it, I made a tkinter window to show the option of whether to see or not. But when the user clicks on "continue", the tk windows freeze (Freezed window) and after a while, it is normal again.
This may not be a very big problem. However, if it is compiled to exe file, it may cause sudden exit without warning and this is not a good user experience. So is there any way to stop the freeze?
This is part of the codes in which the window freezes.
class Success(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Yay! You have passed my chemistry challenge! Would you like to continue?",
font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text="Continue", command=lambda: [controller.show_frame(Credits), success1()])
button1.pack()
button2 = tk.Button(self, text="Quit", command=lambda: controller.destroy())
button2.pack()
def correspondingBehavior(self, choice):
print(choice)
class Credits(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self,
text="Credits: bababa..." )
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text="Quit", command=lambda: controller.destroy())
button1.pack()
def correspondingBehavior(self, choice):
print(choice)
Before the codes are written, I have imported playsound module and defined the success1() function like this:
def success1():
playsound("D:/Personal/Game/Yah.mp3")
Don't let it block the main thread:
def success1():
playsound("D:/Personal/Game/Yah.mp3", block=False)
Or you could create a new thread but that could potentially crash tkinter later.
Based on this question, I wrote the following mwe:
import tkinter as tk
class BaseFrame(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.bmanage = tk.Button(self, text='undock', command = self.undock)
self.bforget = tk.Button(self, text='dock', command = self.dock)
self.bmanage.grid(row=0, column=0, padx=20, pady=20, sticky='nsew')
self.bforget.grid(row=0, column=1, padx=20, pady=20, sticky='nsew')
self.dockable_frame = tk.Frame(self, bg="red", height=100)
self.dockable_frame.grid(row=1, column=0, padx=20, pady=20, columnspan=2, sticky='nsew')
self.label = tk.Label(self.dockable_frame, text="hi")
self.label.grid(row=0, column=0, padx=150, pady=20, sticky='nsew')
def undock(self):
self.parent.wm_manage(self.dockable_frame)
self.dockable_frame.configure(bg='blue')
print(type(self.dockable_frame))
def dock(self):
self.parent.wm_forget(self.dockable_frame)
self.dockable_frame.grid()
if __name__ == "__main__":
root = tk.Tk()
base_frame = BaseFrame(root)
base_frame.grid(row=0, column=0, padx=20, pady=20, sticky='nsew')
root.mainloop()
By clicking the undock button, the red frame is undocked and by clicking the dock button, the frame is docked again. I have two questions:
Why is the type of self.dockable_frame a <class 'tkinter.Frame'> and not a TopLevel since the wm manage documentation says: The widget specified will become a stand alone top-level window?
How can I handle the window close event since self.dockable_frame.protocol("WM_DELETE_WINDOW", insert_function_here) gives an error on my Windows pc?
The error is:
AttributeError: 'Frame' object has no attribute 'protocol'
I understand the error but how to handle the window close event?
The documentation is missleading. As I discovered this feature I thought simliar, the frame becomes a window. In fact that isnt really true which I can proof by my code below.
What really happens, at least under MS-Windows but I expect same functionality under other os, is that frames will be just packed on a different toplevel that will be created by wm_mange for this.
When tkinter defines a Window/Toplevel it always build a child window (frame) for the client area which you will work with. Thats why you need to call the win32gui.GetParent method when you will change your window style.
Code:
import tkinter as tk
import win32gui
def info():
print(f'python id: {id(frame)}')
print(f'tkinterID: {frame.winfo_id()}')
print(f'parent id: {win32gui.GetParent(frame.winfo_id())}')
def undock():
root.wm_manage(frame)
def forget():
root.wm_forget(frame)
frame.pack()
root = tk.Tk()
frame= tk.Frame()
frame.pack()
b1 = tk.Button(frame,text='INFO',command=info)
b2 = tk.Button(frame,text='mnge',command=undock)
b3 = tk.Button(frame,text='nrml',command=forget)
b1.pack()
b2.pack()
b3.pack()
root.mainloop()
Output:
By first appearance:
python id: 67118160
tkinterID: 3412074
parent id: 7867926
after undock
python id: 67118160
tkinterID: 3412074
parent id: 15666896
after forget
python id: 67118160
tkinterID: 3412074
parent id: 7867926
reference:
In Tk, Toplevel windows are basically a special form of a Frame which
are managed by the window manager. The proposal is to add the commands
wm manage and wm forget which will take an arbitrary Frame and allow
it to be managed by the window manager, making it a Toplevel window.
Why is the type of self.dockable_frame a <class 'tkinter.Frame'> and not a TopLevel since the wm manage documentation says: The widget specified will become a stand alone top-level window?
I think it is because self.dockable_frame is a python class and doesn't know that the underlying widget has been changed. Arguably this is a bug in wm_manage.
How can I handle the window close event since self.dockable_frame.protocol("WM_DELETE_WINDOW", insert_function_here) gives an error on my Windows PC?
The simplest way is to call the method directly from the tk.Wm class. It would look like this:
tk.Wm.protocol(self.dockable_frame, "WM_DELETE_WINDOW", self.whatever)
I created a library management system with GUI and want to add a login GUI on it as well. What I want in the login window to pop up first and then, if the credentials are right, the management system opens up.
The problem I'm facing is that when I hit the login button, the management system opens but the login window also stays there. I have tried self.root.destroy() before creating the window for management system but it closes all the windows and the management system also shuts down.`
What can I do?
class login_system():
def __init__(self, root):
self.root = root
self.root.title('Login')
self.root.geometry('500x250')
self.root.config(background='black')
self.loggedin = False
user = Label(root, text='Username:', font=('times new roman',20, 'bold'), fg='white', bg='black', padx=15,pady=10)
user.grid(row=0, column=0, padx=10, pady=10)
password = Label(root, text='Password:', font=('times new roman',20, 'bold'), fg='white', bg='black', padx=15 )
password.grid(row=1, column=0, padx=10, pady=10)
self.user_var= StringVar()
self.pass_var= StringVar()
user_ent = Entry(root, width=20, font=('times new roman',18, 'bold'),textvariable=self.user_var)
user_ent.grid(row=0, column=1)
pass_ent = Entry(root, width=20,font=('times new roman',18, 'bold'),textvariable=self.pass_var)
pass_ent.grid(row=1, column=1)
submit = Button(root, text='Login', command=self.login,font=('times new roman',18, 'bold'))
submit.grid(row=3, column=1, pady=10)
def login(self):
userinfo = self.user_var.get()
passinfo= self.pass_var.get()
conn = mysql.connector.connect(host='localhost', username='root', password = 'testpass', database = 'librarydb')
my_cursor = conn.cursor()
my_cursor.execute('SELECT username, password FROM login_system')
rows = my_cursor.fetchall()
conn.close()
for row in rows:
if row[0] ==userinfo:
if row[1]==passinfo:
tmsg.showinfo('Successful!', 'Logged In')
self.loggedin=True
self.newWindow= Toplevel(self.root)
self.app = LibraryManagementSystem(self.newWindow)
else:
tmsg.showinfo('Incorrect', 'Incorrect Password. Please try again')
break
else:
tmsg.showinfo('Incorrect', 'Incorrect username. Please try again')
break
class LibraryManagementSystem():
def __init__(self, root):
self.root= root
self.root.title('Library Management System')
self.root.geometry('1366x768')
When you destroy() the login window, all of the other windows are destroyed as well because it is the root window. To avoid this, the login window needs to be a Toplevel, not Tk.
You can create the root window like this:
root = Tk()
root.iconify()
login_system(root)
root.iconify() hides the root window, but it's still there so the other windows don't close. Then inside of the login class you need to create a Toplevel like this:
class login_system():
def __init__(self, root):
self.root = root
self.login_window = Toplevel(self.root)
self.login_window.title('Login')
self.login_window.geometry('500x250')
self.login_window.config(background='black')
...
This is similar to how you made the window for the managment system after the user has logged in.
After the new window is created, you need to destroy the login one like this:
self.newWindow= Toplevel(self.root)
self.app = LibraryManagementSystem(self.newWindow)
self.login_window.destroy() #This bit is new
Then it will all work as expected.
Here, you can use .withdraw method. This will hide the main window without destroying the whole application. So, you can get your Toplevel() and hide the original window
self.root.withdraw()
This will hide the main window without destroying it.
And, after you are done, and you want to show the login window,
self.root.deiconify()
This will make to hidden window to be revealed.
I'm somewhat new to tkinter and Python and am working on a semester long project. Basically I have a main tkinter window, then from that window, topLevel windows are called depending on the user input. In the each topLevel window I have a button that performs a function, I also want this button to close the topLevel window after performing that function. What would be the best way to approach this problem?
I have tried to destroy or close the window, but it ends up closing the main window also. I am just looking for a way to close the topLevel window and perform the function with the click of a button
class MainWindow:
# Entry box
self.entry = StringVar()
self.text_box = Entry(master, textvariable=self.entry)
self.text_box.grid(row=1, column=2)
# Displays and binds button, so when clicked, the enter_button function is called
self.input_button = Button(master, text='Enter', command=self.enter_button)
self.input_button.grid(row=1, column=3, sticky='W')
def enter_button(self):
# Get user input and perform the given command
command = self.entry.get()
# Creates a root for a toplevel window
top = Toplevel()
if command == '1':
add_gui = AddPayment(top)
top.mainloop()
elif command == '2':
#rest of classes/commands
main
def main():
root = Tk()
app = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
AddPayment class
class AddPayment:
def __init__(self,master):
self.master = master
self.add_label = Label(master, text='How much is the payment for?')
# payment box
self.pay = StringVar()
self.pay_box = Entry(master, textvariable=self.pay)
self.add_button = Button(master, text='Add', command=self.add_payment)
# position widgets
self.pay_box.grid(row=1, column=2)
self.add_label.grid(row=1, column=1)
self.add_button.grid(row=1, column=3)
def add_payment(self):
database.add_pay(self.pay.get())
In this example I would like something in the add_payment function to close the topLevel window after the add_pay function is performed somehow. Thanks in advance
You have a couple problems. For one, you should never call mainloop more than once. You need to remove the call to mainloop() from the enter_button function.
The other problem is that you're not saving a reference to the toplevel, so you've made it more-or-less impossible to destroy it. You simply need to save a reference and then call destroy on the reference.
self.top = Toplevel()
...
self.top.destroy()