after_cancel doesn't work in python tkinter - python

I made a simple countdown in my trivia game and every time that I go to the next slide it doesn't cancel the previous countdown timer and overlaps with the previous one.
I searched about that on the web and found the after_cancel function that cancels the timer after I go to the next slide and then I recreate it. But it still overlaps even after I added this function.
I think that I didn't give the after_cancel the correct arguments.
from tkinter import *
window = Tk()
window.attributes('-fullscreen',True)
WaitState = StringVar()
count = 10
button_label = StringVar()
def clear():
WaitState.set(1)
for widgets in window.winfo_children():
widgets.destroy()
def clear_time():
clear()
time_up_label = Label(
window,
text="Time is up",
bg = "#6378ff",
fg="#000000",
font=("Arial", 100,"bold"))
time_up_label.place(relx = 0.5,rely = 0.5,anchor = 'center')
continue_but = Button(
window,
text="Continue",
font=("Knewave",25,"bold"),
bg="#942222",
width=11,
height=5,
command=clear)
continue_but.place(relx = 1.0,rely = 1.0,anchor =SE)
continue_but.wait_variable(WaitState)
def button_countdown(i, label):
if i > 0:
i -= 1
label.set(i)
window.after_id = window.after(1000, lambda: button_countdown(i, label))
else:
window.after_cancel(window.after_id)
clear_time()
for index in range(5):
continue_but = Button(
window,
text="Continue",
font=("Knewave",25,"bold"),
bg="#942222",
width=11,
height=5,
command=clear)
continue_but.place(relx = 1.0,rely = 1.0,anchor =SE)
button_label.set(count)
timer_label = Label(
window,
textvariable=button_label,
bg="#6378ff",
fg="#ff0000",
font=("Arial",46,"bold"))
timer_label.pack()
button_countdown(count, button_label)
continue_but.wait_variable(WaitState)
window.mainloop()

Related

Tkinter Entry Widget: How to constantly check for activity and delete the field if nothing is typed in a period of time

Trying to write a little program where you type in a Tkinter Entry widget and if you don't type for 5 seconds it deletes everything. The best I can do is have it delete everything on the first key pressed after the five seconds elapses, but I can't figure out how to get it to do it without that extra key press.
import time
from tkinter import *
def click(key):
global click_before_last_click, last_click
# print(key.char)
click_before_last_click = last_click
last_click = time.time()
# print(click_before_last_click)
# print(last_click)
delete_shit()
def disappearing_text_start():
global click_before_last_click, last_click
click_before_last_click = time.time()
last_click = time.time()
entry.delete(1.0, END)
entry.bind("<Key>", click)
def disappearing_text_end():
text_file = open("result.txt", "w")
text_file.write(entry.get(1.0, END))
text_file.close()
entry.delete(1.0, END)
def delete_shit():
if last_click > click_before_last_click + 5:
print("TOO LONG")
entry.delete(1.0, END)
if __name__ == "__main__":
click_before_last_click = time.time()
last_click = time.time()
window = Tk()
window.title("Disappearing Text")
window.config(padx=50, pady=20, bg="#D3D3D3")
title_label = Label(text="Disappearing Text App", fg="black", bg="#D3D3D3", font=("Courier", 24))
title_label.grid(column=1, row=0, columnspan=2)
label = Label(text="Click start to begin, and end to save your text. "
"If you stop typing for 5 seconds, you lose everything.",
bg="#D3D3D3", font=("Courier", 14))
label.grid(column=1, row=1, columnspan=2)
entry = Text(width=100, height=30)
entry.grid(column=1, columnspan=2, row=3)
start_button = Button(text="Start", command=disappearing_text_start)
start_button.grid(column=1, row=4, pady=20)
end_button = Button(text="Save", command=disappearing_text_end)
end_button.grid(column=2, row=4, pady=20)
window.mainloop()
You can use after to delete the characters after the time interval. Each time the user presses a key, delete the old scheduled function and then reschedule it.
Also, FWIW, you've used an index of 1.0 which is invalid. Tkinter will accept it, but an index is a string rather than a floating point number.
Let's start by writing a function that will schedule the text to be deleted in 5 seconds. It will also cancel any pending job, effectively resetting the timer to zero. It needs to accept an event parameter since it will be called from a key binding.
after_id = None
def schedule_delete(event=None):
global after_id
if after_id:
window.after_cancel(after_id)
after_id = window.after(5000, delete_shit)
Next, arrange for this to be called when the user clicks the "start" button. disappearing_text_start might look something like this:
def disappearing_text_start():
schedule_delete()
entry.delete("1.0", END)
You can then call this bind command once in the main body of of your program to have reschedule_delete called on every keypress:
entry.bind("<Any-KeyPress>", reschedule_delete)
Finally, we need to cancel any pending job when the user clicks the "stop" button:
def disappearing_text_end():
global after_id
if after_id:
window.after_cancel(after_id)
after_id = None
... the rest of your code here ...
Here's a complete working example:
from tkinter import *
def click(key):
schedule_delete()
def disappearing_text_start():
schedule_delete()
entry.delete("1.0", END)
def schedule_delete(event=None):
global after_id
if after_id:
window.after_cancel(after_id)
after_id = window.after(5000, delete_shit)
def disappearing_text_end():
global after_id
if after_id:
window.after_cancel(after_id)
after_id = None
text_file = open("result.txt", "w")
text_file.write(entry.get(1.0, END))
text_file.close()
entry.delete("1.0", END)
def delete_shit():
entry.delete("1.0", END)
if __name__ == "__main__":
# this is used to keep track of the scheduled function call
after_id = None
window = Tk()
window.title("Disappearing Text")
window.config(padx=50, pady=20, bg="#D3D3D3")
title_label = Label(text="Disappearing Text App", fg="black", bg="#D3D3D3", font=("Courier", 24))
title_label.grid(column=1, row=0, columnspan=2)
label = Label(text="Click start to begin, and end to save your text. "
"If you stop typing for 5 seconds, you lose everything.",
bg="#D3D3D3", font=("Courier", 14))
label.grid(column=1, row=1, columnspan=2)
entry = Text(width=100, height=30)
entry.grid(column=1, columnspan=2, row=3)
start_button = Button(text="Start", command=disappearing_text_start)
start_button.grid(column=1, row=4, pady=20)
end_button = Button(text="Save", command=disappearing_text_end)
end_button.grid(column=2, row=4, pady=20)
entry.bind("<Any-KeyPress>", schedule_delete)
window.mainloop()

Adding button (with variables) by pressing button - tkinter

I'm making a point of sale system and trying to implement a button, that when pressed a new button appears but also, a window which asks the user to input an Item
def newButton ():
w = Toplevel()
w.title("New Item") #creates new window when button is pressed
w.geometry("200x200")
itemNameLabel = Label(w, font=("arial", 15), text="What is the item called?")
itemNameLabel.grid(row=0, padx=5)
itemName = Entry(w, width=18, borderwidth=5)
itemName.grid(row=1, padx=5)
newItemName = itemName.get
itemPriceLabel = Label(w, font=("arial", 15), text="What is the item's price?")
itemPriceLabel.grid(row=4, padx=5)
itemPrice = Entry(w, width=18, borderwidth=5)
itemPrice.grid(row=5, padx=5)
def item6_Button():
global item6_qty
item6_price = itemPrice.get
item6_text = newItemName
item6_qty += 1
item6_text = (item6_text + " "+str(item6_price) +" "+ str(item6_qty)) #concatonates text & variable
item6.config(text=item6_text) #updates label text - doesn't add multiple
item6.pack()
item6_Button = Button(itemFrame, text=newItemName, width=10, height=5, command=item6_Button)
item6_Button.grid(row=7, column=1, padx=5)
item6 = Label(receiptFrame)
w.mainloop()
newButton= Button(itemFrame, text="Add New Button", width=20, height=5, command=newButton) #creates button for new window
newButton.place(x=480, y=600)
newButton = Label(itemFrame)
*item6_qty and item6_price are declared near the beginning of the program
This is what I have so far and although the window appears, I don't think the variables are actually set, on top of the new button appearing in the item frame. I'm not entirely sure how to go about this - do I need to use .insert for the variables?
This is the standard code I have which creates the normal button
#Item1 Button + Function
def item1_Button():
global item1_qty #making qty variable global so it can used
item1_text = ("Chips")
item1_qty += 1 #increments qty variable by one everytime button is clicked
item1_text = (item1_text + " "+str(item1_price) +" "+ str(item1_qty)) #concatonates text & variable
item1.config(text=item1_text) #updates label text - doesn't add multiple
item1.pack() #places label within the frame
item1_Button = Button(itemFrame, text="Chips", width=10, height=5, command=item1_Button)
#creates button + links to function
item1_Button.grid(row=4, column=1, padx=5) #positions button
item1 = Label(receiptFrame)#creates label for button
I'm not sure if I've provided enough code of what I've done to give a better picture of what I'm trying to achieve but I know large chunks of code aren't very favoured
here is an example of what You could do (does this help?):
from tkinter import Tk, Button, Entry, Toplevel
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry('100x150')
self.btn = Button(self, text='Create New!', command=self.ask)
self.btn.pack()
def ask(self):
ask_window = InputWindow(self)
ask_window.focus_force()
def create(self, text):
button = Button(self, text=text)
button.pack()
class InputWindow(Toplevel):
def __init__(self, parent):
Toplevel.__init__(self, parent)
self.parent = parent
self.bind('<FocusOut>', self.destroy_)
self.user_input = Entry(self)
self.user_input.pack()
self.submit_btn = Button(self, text='Submit!', command=self.retrieve)
self.submit_btn.pack()
def retrieve(self):
text = self.user_input.get()
self.parent.create(text)
self.destroy()
def destroy_(self, event):
if isinstance(event.widget, Toplevel):
self.destroy()
root = MainWindow()
root.mainloop()

Close Command and Update Command Don't Work With One Button

So for this larger program I'm making I want it so when a user presses a button it closes the dialog windows and updates all the values the user input. Therefore, I have one button do these two things: update the values and close the program. However, trying to combine these two functions doesn't work as when I use both of them only the update() command is called, not the close command. Either works separately btw. Any way to fix this?
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import *
from tkinter.ttk import *
propDiameterInch = 10.5
propDiameterMetric = propDiameterInch*0.0254
class Counter_program():
def __init__(self):
self.window = tk.Tk()
self.window.title("Test")
style = ttk.Style()
style.configure("BW.TLabel", foreground="black", background="white")
#default unit color
unitColor = "slategrey"
boxWidth = 5
# Create some room around all the internal frames
self.window['padx'] = 5
self.window['pady'] = 5
propeller_frame = ttk.LabelFrame(self.window, text="Propeller", relief=tk.RIDGE)
propeller_frame.grid(row=1, column=1, sticky=tk.E + tk.W + tk.N + tk.S)
#propeller diameter
propellerDiameter_label = ttk.Label(propeller_frame, text="Propeller Diameter")
propellerDiameter_label.grid(row=1, column=1, sticky=tk.W + tk.N +tk.S)
propellerDiameter_Units = ttk.Label(propeller_frame, text="inches",foreground=unitColor)
propellerDiameter_Units.grid(row=1, column=3, sticky=tk.W)
propellerDiameter_entry = ttk.Entry(propeller_frame, width=boxWidth)
propellerDiameter_entry.grid(row=1, column=2, sticky=tk.W, pady=3)
propellerDiameter_entry.insert(tk.END, "10")
#now set all global variables from entries - update function
def update():
global propDiameter
propDiameter = propellerDiameter_entry.get()
# Finish button in the lower right corner
#finish_button = ttk.Button(self.window, text = "Submit Input", command = self.window.destroy)
finish_button = ttk.Button(self.window, text = "Submit Input", command=lambda:[update(),self.window.destroy])
finish_button.grid(row=2, column=2)
# Create the entire GUI program
program = Counter_program()
# Start the GUI event loop
program.window.mainloop()
propDiameter
Since your using lambda, its safe to use () with the functions, so just change finish_button to:
finish_button = ttk.Button(self.window, text = "Submit Input", command=lambda:[update(),self.window.destroy()])
Or you could make a new function that does both of this for you, like:
def both():
update()
self.window.destroy()
finish_button = ttk.Button(self.window, text = "Submit Input", command=both)
TIP:
Also its not recommended to use global with OOP, so I recommend you change your code and use proper "methods" and self with OOP for a better experience.
Here is how I think your class should like:
class Counter_program():
def __init__(self):
self.window = tk.Tk()
self.window.title("Test")
style = ttk.Style()
style.configure("BW.TLabel", foreground="black", background="white")
#default unit color
unitColor = "slategrey"
boxWidth = 5
# Create some room around all the internal frames
self.window['padx'] = 5
self.window['pady'] = 5
self.propeller_frame = ttk.LabelFrame(self.window, text="Propeller", relief=tk.RIDGE)
self.propeller_frame.grid(row=1, column=1, sticky=tk.E + tk.W + tk.N + tk.S)
#propeller diameter
self.propellerDiameter_label = ttk.Label(self.propeller_frame, text="Propeller Diameter")
self.propellerDiameter_label.grid(row=1, column=1, sticky=tk.W + tk.N +tk.S)
self.propellerDiameter_Units = ttk.Label(self.propeller_frame, text="inches",foreground=unitColor)
self.propellerDiameter_Units.grid(row=1, column=3, sticky=tk.W)
self.propellerDiameter_entry = ttk.Entry(self.propeller_frame, width=boxWidth)
self.propellerDiameter_entry.grid(row=1, column=2, sticky=tk.W, pady=3)
self.propellerDiameter_entry.insert(tk.END, "10")
# Finish button in the lower right corner
#finish_button = ttk.Button(self.window, text = "Submit Input", command = self.window.destroy)
self.finish_button = ttk.Button(self.window, text = "Submit Input", command=self.both)
self.finish_button.grid(row=2, column=2)
def update(self):
self.propDiameter = self.propellerDiameter_entry.get()
def both(self):
self.update()
self.window.destroy()
Hope this solved the issue, do let me know if any errors or doubts.
Cheers

When I click on button 2 after pressing on button 1 it does not work

When I click on button 2 after pressing on button 1 it does not work.
I am making an auto clicker for fun, as a side project.
import tkinter as tk
from pynput.mouse import Button, Controller
import time
Height = 700
Width = 800
mouse = Controller()
flag = True
def click_function():
while flag == True:
time.sleep(.001)
mouse.click(Button.left, 1)
def endclick_function():
flag = False
root = tk.Tk()
canvas = tk.Canvas(root, height=Height, width=Width)
canvas.pack()
frame = tk.Frame(root,bg='black')
frame.place(relx=0.1, rely=0.1, relwidth=0.8, relheight=0.5)
button = tk.Button(frame, text="Start" , bg='white', fg='black', font=50, command=click_function)
button.place(relx=0, rely=0, relwidth=0.5, relheight=0.5)
button2 = tk.Button(frame, text="Stop" , bg='white', fg='black', font=50, command=lambda: endclick_function)
button2.place(relx=.5, rely=0, relwidth=0.5, relheight=0.5)
label = tk.Label(frame, text='Time to Sleep:', bg='white', font=50)
label.place(relx=0, rely =0.5, relwidth=0.5, relheight=0.25)
label2 = tk.Label(frame, text='How many times to click:', bg='white', font=50)
label2.place(relx=0, rely =0.75, relwidth=0.5, relheight=0.25)
entry = tk.Entry(frame, bg='white')
entry.place(relx=0.5, rely =0.5, relwidth=0.5, relheight=0.25)
entry2 = tk.Entry(frame,text='Time to Sleep(ms):', bg='white')
entry2.place(relx=0.5, rely =0.75, relwidth=0.5, relheight=0.25)
root.mainloop()
you have to declare flag global if you want to change it
also as Joe Ferndz pointed out, the flag is never set back to True
def click_function():
global flag
flag = True # of course, only if you want to use clicker more than once
while flag == True:
time.sleep(.001)
mouse.click(Button.left, 1)
def endclick_function():
global flag
flag = False
Something I just noticed
in button2, command=lambda:end_f
remove lambda
this is basically saying
def l():
return end_f
button2['command'] = l
and since the command (l) is executed at the click on the button,
it only returns the function, does not execute it
When you click the first button, the flag is set to True. However, when you click on second button, the flag gets set to False. Later when you come back to first button, the flag is False so it never goes into the while loop.
Do you want to try and implement this an alternate way?
def click_function():
while flag == True:
time.sleep(.001)
mouse.click(Button.left, 1)
def endclick_function():
flag = False

How to make a Tkinter window not resizable?

I need a Python script that uses the Tkinter module to create a static (not resizable) window.
I have a pretty simple Tkinter script but I don't want it to be resizable. How do I prevent a Tkinter window from being resizable? I honestly don't know what to do.
This is my script:
from tkinter import *
import ctypes, os
def callback():
active.set(False)
quitButton.destroy()
JustGo = Button(root, text=" Keep Going!", command= lambda: KeepGoing())
JustGo.pack()
JustGo.place(x=150, y=110)
#root.destroy() # Uncomment this to close the window
def sleep():
if not active.get(): return
root.after(1000, sleep)
timeLeft.set(timeLeft.get()-1)
timeOutLabel['text'] = "Time Left: " + str(timeLeft.get()) #Update the label
if timeLeft.get() == 0: #sleep if timeLeft = 0
os.system("Powercfg -H OFF")
os.system("rundll32.exe powrprof.dll,SetSuspendState 0,1,0")
def KeepGoing():
active.set(True)
sleep()
quitButton1 = Button(root, text="do not sleep!", command=callback)
quitButton1.pack()
quitButton1.place(x=150, y=110)
root = Tk()
root.geometry("400x268")
root.title("Alert")
root.configure(background='light blue')
timeLeft = IntVar()
timeLeft.set(10) # Time in seconds until shutdown
active = BooleanVar()
active.set(True) # Something to show us that countdown is still going.
label = Label(root, text="ALERT this device will go to sleep soon!", fg="red")
label.config(font=("Courier", 12))
label.configure(background='light blue')
label.pack()
timeOutLabel = Label(root, text = 'Time left: ' + str(timeLeft.get()), background='light blue') # Label to show how much time we have left.
timeOutLabel.pack()
quitButton = Button(root, text="do not sleep!", command=callback)
quitButton.pack()
quitButton.place(x=150, y=110)
root.after(0, sleep)
root.mainloop()
The resizable method on the root window takes two boolean parameters to describe whether the window is resizable in the X and Y direction. To make it completely fixed in size, set both parameters to False:
root.resizable(False, False)

Categories

Resources