I have a program that requires input to a module while it is running, so I am implementing a simple Dialog box to get input from the user to use in my Tkinter program. But I also need it to timeout when running the module as a console program, to pass over it or timeout after so many seconds when the user is not interacting with it. And not to have it just sit there and wait forever until the user interacts with it. How do I terminate the window after a timeout? Here is what I have now ...
def loginTimeout(timeout=300):
root = tkinter.Tk()
root.withdraw()
start_time = time.time()
input1 = simpledialog.askinteger('Sample','Enter a Number')
while True:
if msvcrt.kbhit():
chr = msvcrt.getche()
if ord(chr) == 13: # enter_key
break
elif ord(chr) >= 32: #space_char
input1 += chr
if len(input1) == 0 and (time.time() - start_time) > timeout:
break
print('') # needed to move to next line
if len(input1) > 0:
return input1
else:
return input1
Not entirely sure what the question is, but you could use the tkinter after() method in a similar way to this:
import tkinter as tk
root = tk.Tk()
root.geometry('100x100')
def get_entry() -> str:
"""Gets and returns the entry input, and closes the tkinter window."""
entry = entry_var.get()
root.destroy() # Edit: Changed from root.quit()
return entry
# Your input box
entry_var = tk.StringVar()
tk.Entry(root, textvariable=entry_var).pack()
# A button, or could be an event binding that triggers get_entry()
tk.Button(root, text='Enter/Confirm', command=get_entry).pack()
# This would be the 'timeout'
root.after(5000, get_entry)
root.mainloop()
So, if the User enters something they can hit confirm or launch an event binded to the entry, or after the delay the program runs get_entry anyway. Maybe this will give you an idea, you can also check out the other widget methods:
https://effbot.org/tkinterbook/widget.htm
EDIT: I'm not sure how this is arranged in your program, but root.mainloop() blocks, so once it's running, code after it won't run until the mainloop() exits. If you're function is part of a toplevel() window, you could use wait_window() to prevent the function from advancing until your timeout destroys the toplevel window or the function is called before by the User. The example, although probably not the best solution, would be:
def loginTimeout(timeout_ms: int=5000):
root = tk.Tk()
root.geometry('200x50')
# function to get input and change the function variable
input_ = ''
def get_input():
nonlocal input_
input_ = entry_var.get()
root.destroy()
# build the widgets
entry_var = tk.StringVar()
tk.Entry(root, textvariable=entry_var).pack(side='left')
tk.Button(root, text='Confirm', command=get_input).pack(side='right')
# timeout
root.after(timeout_ms, get_input)
# mainloop
root.mainloop()
# won't get here until root is destroyed
return input_
print(loginTimeout())
Related
Tkinter ignores root.after
The countdown animation plays but it ignores the delay and does everything that's after root.after before the countdown is over
All tests suggest that the countdown is happening and the variable is changing, it just skips the delay
def StartTheSpam():
global TimeLable, ErrorLabel
#destoryes error labels and the countdown timer
try:
for child in root.winfo_children():
if child == TimeLable or child == ErrorLabel:
child.destroy()
except NameError:
pass
#needed for fail-safe 1
mouse = MouseController()
countdown_value = 5
counter = tk.StringVar()
counter.set(countdown_value)
TimeLable = tk.Label(frame, textvariable=counter, padx=10, pady=5, bg=Modes[mode.get()][3]['background'], fg=Modes[mode.get()][2]['text'])
TimeLable.pack()
#coundown
for countdown in range(1, countdown_value):
root.after(1000 * countdown, counter.set, countdown_value - countdown)
x = 100
y = 100
try:
with open(PreferencesStorage['FilePath'], 'r') as SpamText:
while PreferencesStorage['loop']:
for word in SpamText:
#fail safe 1
if x < 21 and y < 21:
break
TempX, TempY = mouse.position
x = int(TempX)
y = int(TempY)
#fail-safe 2
if keyboard.is_pressed('ctrl+d'):
break
keyboard.write(word)
print(word)
#return to begining when at end, thats why its outside of the loop
SpamText.seek(0)
for word in SpamText:
keyboard.write(word)
except FileNotFoundError:
NoFile = tk.Label(frame, text = 'Please Select A File', padx=10, pady=5, fg=Modes[mode.get()][2]['text'], bg=Modes[mode.get()][3]['background'])
NoFile.pack()
root.after does not cause a delay. What that says is "please have the main loop call this function later". Your code is going to queue up 5 timer callback requests very quickly, and then continue on. The callbacks will happen later.
When you're coding for a GUI, you have to start thinking about event-driven programming. When you create widgets or call "pack", NOTHING HAPPENS. All that does is queue up a message to be handled later. At some future point, when the mainloop is able to execute, your messages will be popped off and processed. Only then will your visible screen change. That requires a change in the way you program things. You set up your screen, start the mainloop, and then wait for events. Your event callbacks change the state and return, and Tk in the background will update the screen.
I'm writing a program in tkinter using Progressbar. But there is a problem when I added stop function it doesn't work. When I press "stop" button nothing happens, it should stop loading progressbar. I use Python version 3.8. The code below:
from tkinter import *
from tkinter import ttk
import time
root = Tk()
def run():
pb['maximum']=100
for i in range(101):
time.sleep(0.05)
pb['value']=i
pb.update()
def stop():
pb.stop()
runbutt = Button(root,text="Runprogr",command=run)
runbutt.pack()
stopbutt = Button(root,text="Stopbut",command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root,length=300,orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()
The cause is that pb.stop couldn't stop the function in run.it will also increase by itself.
You could use .after(ms, callback) to add the value(then you no longer need to use time.sleep()).
If you want to stop it,use .after_cancel():
from tkinter import *
from tkinter import ttk
import time
root = Tk()
root.add_value = None
def run():
def add():
if pb['value'] >= 100:
return
pb['value'] += 1
root.add_value = root.after(50, add)
if root.add_value: # to prevent increasing the speed when user pressed "Runprogr" many times.
return
root.add_value = root.after(50, add)
def stop():
if not root.add_value: # to prevent raising Exception when user pressed "Stopbut" button many times.
return
root.after_cancel(root.add_value)
root.add_value = None
runbutt = Button(root, text="Runprogr", command=run)
runbutt.pack()
stopbutt = Button(root, text="Stopbut", command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root, length=300, orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()
This is sort of a multi-threading question but not really. I have a Python Tkinter Toplevel window (Python 2.7 & 3.5 tested):
The second button is defined as:
btn2 = tk.Button(button_frame, text='Remove new', \
command=self.remove)
btn2.pack(side=tk.LEFT)
Which when the button is pressed calls this function:
def remove(self):
''' Remove windows on monitor now that weren't there at start '''
new_windows = []
new_windows_cnt = 0
(... SNIP out boring stuff ...)
The above code works when window has focus and user clicks button. Now I'm creating this Ubuntu Unity Keyboard Custom Shortcut:
Note: Shortcut doesn't appear to work but that's not a biggie
Now the parallel processing part of the question
Create a function that continuously runs when Toplevel window doesn't have focus:
def parallel_processing(self)
while (not_toplevel_destroyed):
try:
f = open("/tmp/w")
self.remove()
except IOError:
pass # TODO: Test if IOError is even, else delete this and above
finally:
f.delete()
time.sleep(.1)
Note: It is OK if this new function also runs when Toplevel window has focus in which case it will be a 1/10th second delayed accelerator key.
How do I call this new function after btn2 is packed?
How do I kill this new function when Toplevel is destroyed by button 1 which contains:
btn = tk.Button(button_frame, text='Close', \
command=self.toplevel.destroy)
btn.pack(side=tk.LEFT)
Solved!!!
The accepted answer below works but there were some errors in my concept draft. Here is the final code:
import os
import time
(... SNIP ...)
button_frame = tk.Frame(self.toplevel)
button_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
btn = tk.Button(button_frame, text='Close', \
command=self.close_toplevel)
btn.pack(side=tk.LEFT)
btn2 = tk.Button(button_frame, text='Remove new', \
command=self.remove)
btn2.pack(side=tk.LEFT)
self.toplevel_active = True
self.parallel_processing()
def close_toplevel(self):
self.toplevel_active = False
time.sleep(0.2)
self.toplevel.destroy()
def parallel_processing(self):
if os.path.exists("/tmp/w"):
os.remove("/tmp/w")
self.remove()
if self.toplevel_active:
root.after(100, self.parallel_processing)
def remove(self):
''' Remove windows on monitor now that weren't there at start '''
new_windows = []
new_windows_cnt = 0
(... SNIP ...)
Here is the test proof:
$ echo 1 > /tmp/w
$ ll /tmp/w
ls: cannot access '/tmp/w': No such file or directory
The file doesn't exist because the python program is working. The parallel_processing(self) function appears to add less than 1% CPU load.
I'm not sure if I understand problem.
You can use root.after instead of while and sleep and it will not block root.mainloop()
def parallel_processing(self)
try:
f = open("/tmp/w")
self.remove()
except IOError:
pass # TODO: Test if IOError is even, else delete this and above
finally:
f.delete()
if self.not_toplevel_destroyed:
root.after(100, self.parallel_processing)
btn2 = tk.Button(button_frame, text='Remove new', command=self.remove)
btn2.pack(side=tk.LEFT)
self.not_toplevel_destroyed = True
self.parallel_processing()
You can assign function which will set not_toplevel_destroyed = False to stop function and wait few milliseconds to make sure, and then it will destroy window.
def close_app(self)
self.not_toplevel_destroyed = False
time.sleep(0.2)
self.toplevel.destroy()
btn = tk.Button(button_frame, text='Close', command=self.close_app)
btn.pack(side=tk.LEFT)
I'm new to python and I am trying to create a program but I can't even get the basics right. I have a button app that looks like this:
#simple GUI
from tkinter import *
import time
#create the window
root = Tk()
#modify root window
root.title("Button Example")
root.geometry("200x50")
button1state = 0
def start():
count = 0
button1["text"] ="Busy!"
while (count < 5):
root.after(1000)
count = count + 1
def button1clicked():
global button1state
if button1state == 0:
start()
button1["text"] ="On!"
button1state = 1
else:
button1["text"] ="Off!"
button1state = 0
app = Frame(root)
app.pack()
button1 = Button(app, text ="Off!", command = button1clicked)
button1.pack()
#kick off the event loop
root.mainloop()
Now everything works except it doesn't change the button text to busy while
**start()** is called. How can I fix this? Once I've got it working I want to use images to show the user that its OFF ON and BUSY. Please help me
You need to force the GUI to update before starting the task:
def start():
count = 0
button1.configure(text="Busy!")
root.update() # <-- update window
while (count < 5):
root.after(1000)
count = count + 1
But if you don't want your GUI to be frozen while the task is executed, you will need to use a thread as Dedi suggested.
You have to make a thread in order to make you function as a "background event" while your interface is working. Consider using that :
from threading import Thread
and then :
my_thread=Thread(target=start())
my_thread.start()
Where the first "start()" is the name of your function and the second one a call for the thread to begin.
So here´s the deal. I have a button. That button triggers a while loop and opens a new window with a "cancel" button. Through that cancel-button I want to terminate the while loop.
NEW APPROACH:
def process():
parent,child = Pipe()
p = process(target=new_window(),args=(child))
p.start()
parent.send("True")
while true:
if child.recv()!="False":
do something
else:
p.join()
def new_window(p):
id = "cancel"
window = Toplevel(root)
window.geometry("240x160")
state = p.recv()
def closewindow():
global state
state = "False"
p.send(state)
window.destroy()
label = Label(window, text="Überwache...").place(x=90,y=60)
buttonc = Button(window,text="Abbrechen!",fg="black",command=closewindow).place(relx=0.35, rely=0.5)
The idea is that the parent (def process) executes the while loop as long as it doesn´t get a "False" from the child process (new_window).
Do you see any errors? Can´t test it right now, since the code replacing my placeholder "do something" is another difficult task...
OLD POST:
state variable
state = bool(True)
button that triggers loop and new window
button3 = Button(containerd,text="Starte Überwachung",fg="black",height=10,width=16,command=sound)
function new window
def new_window():
id = "cancel"
window = Toplevel(root)
window.geometry("240x160")
def closewindow():
global state
state = bool(False)
window.destroy()
label = Label(window, text="Überwache...").place(x=90,y=60)
buttonc = Button(window,text="Abbrechen!",fg="black",command=closewindow).place(relx=0.35, rely=0.5)
function while loop
def sound():
global inp
global state
new_window()
while state:
l,data = inp.read()
if l > 60:
subprocess.call(['/home/pi/RPi_Cam_Web_Interface/cycle.sh'],shell=True)
else:
continue
The problem is the new window never opens. The while loop keeps the button / programm "occupied". Any ideas how to solve this?