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.
Related
I am creating a simple GUI program that utilizes Python and Tkinter to log the time/date when a user presses a button on the interface (by appending information to a .txt file), as well as sending an e-mail to a list of addresses informing the recipients that the log has been updated.
The program has three main frames/screens that I would like the user to navigate through. The navigation between the screens should be time-based. In other words, I would like the user to be redirected from the main screen to a secondary screen upon the press of a Tkinter button (which I have already established using the 'command' argument of the Tkinter widgets), and then be automatically redirected back to the main screen after a 5-second time delay.
I understand that using time.sleep() is not encouraged in GUI programs. However, I have had some trouble implementing Tkinter's .after() method, and haven't quite been able to achieve my desired result.
I have attached a simplified example of my program code that models my problem:
import tkinter as tk
class mainApplication(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)
self.frames = {}
for F in (MainScreen, AcknowledgeScreen):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(MainScreen)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class MainScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Press Button to Log Time")
label.pack()
button = tk.Button(self, text="Confirm", command=lambda: controller.show_frame(AcknowledgeScreen))
button.pack()
class AcknowledgeScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Logging Completed. Please Wait.")
label.pack()
# The implementation below is giving me trouble.
self.after(5000, controller.show_frame(MainScreen))
root = mainApplication()
root.mainloop()
The other solutions I have attempted (for the line of interest) include:
# Attempt 1
self.after(5000, controller.show_frame(MainScreen)) # This code waits 5 seconds before opening the GUI window; does not redirect back to MainScreen from AcknowledgeScreen.
# Attempt 2
root.after(5000, controller.show_frame(MainScreen)) # This code throws an error 'NameError: name 'root' is not defined.
# Attempt 3
label.after(5000, controller.show_frame(MainScreen)) # This code waits 5 seconds before opening the GUI window; does not redirect back to MainScreen from AcknowledgeScreen.
Unfortunately, I have never been exposed to object-oriented programming before beginning with Tkinter, so I believe that my errors might be due to a fundamental misunderstanding of how OOP works. Nonetheless, I would appreciate if anyone could point me in the right direction or clarify my errors.
In mainApplication, both screens are initialized and their classes used for dictionary keys mapping to their instances.
From the ordering of your operations, the MainScreen should be raised in the stacking order after AcknowledgeScreen is displayed.
This operation shouldn't live in the AcknowledgeScreen.__init__ except you initialize the screens at the time they are needed.
You want to move this to MainScreen. You can refactor the MainScreen the following way.
class MainScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Press Button to Log Time")
label.pack()
button = tk.Button(self, text="Confirm", command=self.confirm)
button.pack()
def confirm(self):
self.controller.show_frame(AcknowledgeScreen)
self.after(5000, self.back)
def back(self):
self.controller.show_frame(MainScreen)
after() (like bind() and command=) needs callback - it means function name without () and without arguments.
Use lambda
self.after(5000, lambda:controller.show_frame(MainScreen))
But I see different problem.
When you run program then it creates instances of all frames in mainApplication.__init__ so it runs also AcknowledgeScreen.__init__ at start - and it runs after() at start. It doesn't wait for your click.
BTW: because frames are create inside mainApplication.__init__ so they can't use root which will be created after mainApplication.__init__ ends its job.
You would have to add some parts in methods and run everytime when you click button.
class MainScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Press Button to Log Time")
label.pack()
button = tk.Button(self, text="Confirm", command=self.change)
button.pack()
def change(self):
self.controller.show_frame(AcknowledgeScreen)
self.controller.frames[AcknowledgeScreen].change_after()
class AcknowledgeScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Logging Completed. Please Wait.")
label.pack()
def change_after(self):
# The implementation below is giving me trouble.
#both works
#root.after(5000, lambda:self.controller.show_frame(MainScreen))
self.after(5000, lambda:self.controller.show_frame(MainScreen))
Let's look at this code:
self.after(5000, controller.show_frame(MainScreen))
The above code does exactly the same thing as this:
result = controller.show_frame(MainScreen)
self.after(5000, result)
Notice what happens? The function controller.show_frame(MainScreen) executes immediately, rather than being executed by after.
Instead, you need to give after a callable -- roughly speaking, a reference to a function. If that function requires additional arguments, you can add those arguments when calling after:
self.after(5000, controller.show_frame, MainScreen)
The above code tells after to run controller.show_frame in five seconds, and to pass it the argument MainScreen when it is called.
A script should open an application with two buttons visible. When Hello button is pressed a new button is gridded into the row number 1 and Hello button to be deactivated. When this new button is pressed it should delete itself off the grid and reactivate hello button but it does not do it.
Please check the video to see it in action.
Code edited to comment suggestion
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
self.master = master
self.master.geometry('300x100+10+10')
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def new_button(self):
print("enable_b")
self.hi_there.config(state=ACTIVE)
self.new_button.grid_remove()
def say_hi(self):
print("hi there, everyone!")
self.new_button = Button(self)
self.new_button.config(text = "New BTN", command=self.new_button)
self.new_button.grid(row=1,column=0)
self.hi_there.config(state=DISABLED)
def createWidgets(self):
self.QUIT = Button(self)
self.QUIT.config(text="QUIT",fg="red",command=self.quit)
self.QUIT.grid(row=0,column=1)
self.hi_there = Button(self)
self.hi_there["text"] = "Hello",
self.hi_there["command"] = self.say_hi
self.hi_there.grid(row=0,column=0)
def quit(self):
self.master.destroy()
def testit():
root = Tk()
app = Application(master=root)
app.mainloop()
if __name__ == '__main__':
testit()
Initially, self.new_button refers to a method. Then, you do this:
self.new_button = Button(self)
That effecting removes the method and replaces it with the button widget itself.
Also, you never assign a command to the new button, so clicking it doesn't cause anything to be called.
Where your program will technically work just fine with the 2 correction mentioned in Bryan's answer I am not sure why you are taking all the extra effort configuring the widgets for each individual field. All your configs can be done when you create the widget.
That said you can also change a few things for a cleaner code and 1 change I think that really needs to be made is how you are removing the new_button from the grid. When you do grid_remove() this only takes the widget off the screen but does not get rid of the widget. Then next time you press the say_hi button you will end up creating a new button and the old button will still exist. Instead I think I would use destroy() on the button and then let say_hi recreate it.
See this revised version of your code. You will see what I mean about configuring everything when creating the widget and also you do not need to write your own function for quit you can simply do self.master.destroy in the quit button command.
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.master = master
self.master.geometry('300x100+10+10')
self.create_widgets()
def new_btn(self):
print("enable_b")
self.hi_there.config(state="active")
self.new_button.destroy()
def say_hi(self):
print("hi there, everyone!")
self.new_button = tk.Button(self, text="New BTN", command=self.new_btn)
self.new_button.grid(row=1, column=0)
self.hi_there.config(state="disabled")
def create_widgets(self):
tk.Button(self, text="QUIT", fg="red", command=self.master.destroy).grid(row=0,column=1)
self.hi_there = tk.Button(self, text="Hello", command=self.say_hi)
self.hi_there.grid(row=0, column=0)
if __name__ == '__main__':
root = tk.Tk()
app = Application(master=root).pack()
root.mainloop()
I have been working with several tkinter tutorials including one with Text window and a very helpful tutorial but with out text window. See here: https://www.youtube.com/watch?v=oV68QJJUXTU
I have tried to add a Text window to this example but found the constant END was not defined and the Text window did not open or show up in the frame. I traced it down to a difference in the import. Using "from tkinter import *" the constant END was defined (it was 'end') but using the method of this tutorial, "import tkinter as tk" the constant END was not defined. I defined it to clear the error when I try to use Text window the window never opens (never shows up) in the example so I think either I have to rewrite to use the import * method or I need to understand how to over come the import as tk difference.
It seams that importing as tk is likely to be the more correct method rather than as * so that is the way I think I should be learning to do it.
Any suggestions out there?
This code works
from tkinter import *
.....
class set_window(Thread):
def __init__(self, labelText):
Thread.__init__(self)
self.labelText = labelText
self.labelText.set("Text Window Display")
self.T = Text(root, height=40, width=60, bd=10)
self.T.grid(row=1, column=0)
self.T.focus_set()
self.T.insert(END, "Just a text Widget\nin two lines\n")
But this did not:
import tkinter as tk
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Blast it!", font=XLARGE_FONT)
label.pack(pady=10, padx=10)
label_2 = tk.Label(self, text="Input Text Here", font=LARGE_FONT)
label_2.pack(pady=10, padx=10)
self.T = tk.Text(self, height=40, width=60, bd=10)
# print(type(END))
# input ("Press Enter")
self.T.insert(END, "Just a text Widget\nin two lines\n")
self.T.insert('end', "Just a text Widget\nin two lines\n")
self.T.focus_set()
If you want to access END which is available when you do from tkinter import *, you'd have to access it as tk.END when you do import tkinter as tk. Or, you can simply use 'end'. Another solution would be from tkinter.constants import END.
I found that I had to prefix the END constant as tk.END and that cleared on error.
I found I had to add a PACK statement after the Text window insert statement. The page code becomes:
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Blast it!", font=XLARGE_FONT)
label.pack(pady=10, padx=10)
label_2 = tk.Label(self, text="Input Text Here", font=LARGE_FONT)
label_2.pack(pady=10, padx=10)
self.T = tk.Text(self, height=40, width=60, bd=10)
self.T.insert(tk.END, "Just a text Widget\nin two lines\n")
self.T.focus_set()
self.T.pack()
use "end" instead of END
from tkinter import *
self.T.insert("end", "Just a text Widget\nin two lines\n")
I have try to write program for library Management system I am using tkinter module for it. I have writen the below code but when I am trying to create multiple Text box i am getting below error.
File "Hope_work.py", line 22, in __init__
frame = F(container, self)
File "Hope_work.py", line 62, in __init__
pwd_lable.pack()
UnboundLocalError: local variable 'pwd_lable' referenced before assignment
Below is the complete program I am getting error in PageOne class
import tkinter as tk
import os
LARGE_FONT= ("Verdana", 12)
class Myprogramapp(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):
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="Library Managment System", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button = tk.Button(self, text="Admin Login",
command=lambda: controller.show_frame(PageOne))
button.pack()
button1 = tk.Button(self, text="Lib Login")
button1.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
name_label = tk.Label(self, text="User ID : ")
pwd_label = tk.Label(self.name_lable, text="Password:")
name_label.pack(pady=10,padx=10)
pwd_lable.pack(pady=10,padx=10)
name_lable = tk.Entry(self)
pwd_lable = tk.Entry(self, show="*")
name_lable.pack()
pwd_lable.pack()
button1 = tk.Button(self, text="Login")
button1.pack()
if __name__ == "__main__":
app = Myprogramapp()
app.mainloop()
**
It would appear that you are trying to bite off more than you can chew so to speak with this example of code. You are miss using parts of tkinter that should be understood before moving onto something more complicated like this.
Before you try to use multiple classes like this try to focus more on understanding how tkinter works and how to implement all its widgets properly in a single class or even outside of a class first.
You do not assign widgets to another widget like you are attempting to do here:
pwd_label = tk.Label(self.name_lable, text="Password:")
This is the problem refereed to in your trackback. You need to assign the Label widget to either a root window, a frame or a toplevel.
Your indention is not clean and if the way you have it pasted in your question is accurate then you code will not work as the def show_frame() method is not inside the Myprogramapp class.
You are importing os for no reason here and it is not good practice to import libraries you are not currently using.
You should make some important parts of your program into a class attribute, like your entry fields. If you are going to put in a password to that field and try and get() the string from the entry field inside a method you wont be able to as its not a class attribute. You can fix this by adding self. prefix to the widget name.
All and all after making those changes you will get a tkinter window with 2 buttons. The Admin Login button will display a login screen. That being said I think you should either step away from classes all together while learning tkinter or work in a single class for now until you have a solid understanding of how classes, methods, and attributes work and are used.
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)