Tkinter not waiting for user input inside functions - python

I am trying to make a program that when conditions are met, goes back to the beginning and waits. But instead of waiting for the user to push a button, it continues through the code.
I am using python 3.7.4 and Windows 10.
I assume this problem occurs because tkinter doesn't wait for user input in this situation, and instead continues through the code.
My code:
from tkinter import *
from tkinter.ttk import *
def start():
print("Start")
# Removes all widgets without destroying root
for widget in root.winfo_children():
widget.destroy()
button_1 = Button(root, text="Begin", command=begin).pack()
button_2 = Button(root, text="Do something else", command=something).pack()
# I want the program to wait here for the user to click a button
def begin():
print("\nDoing stuff")
if True:
start()
print("This should not be printed")
def something():
pass
root = Tk()
root.geometry("300x300")
btn1 = Button(root, text = "Start", command = start)
btn1.pack()
root.mainloop()
This outputs:
Start
Doing stuff
Start
This should not be printed
I want this to output:
Start
Doing stuff
Start
And then wait for the user to select a button.

If you want a function to wait for a user action, you need to explicitly tell it to wait.
Tkinter has three functions for that. One is wait_window, which will wait for a window to be destroyed. One is wait_visibility, which will wait for the visibility of a window to change. The third one is wait_variable, which waits for a specific tkinter variable to be set.
While tkinter is waiting, it's able to service other events.
In your case, the solution might look something like this:
var = BooleanVar(value=False)
def do_something():
something()
var.set(True)
button_2 = Button(root, text="Do something else", command=do_something).pack()
print("waiting...")
root.wait_variable(var)
print("done waiting.")
When you modify your code to include the above snippet, you'll notice that "waiting..." will be printed on stdout, and then nothing else will be printed until you click on the "Do something else" button and something returns, allowing the variable to be modified.

Related

Tkinter window not closing until python program ends

I got a behavior that I don't understand in my test program;
I'm using tkinter to display a window that show some radio box, and sent back the value to the python program that will continue to execute.
But I've noticed that after the Tkwindow is asked to close with a destroy() command :
IF the following of my code is asking an input() the window is closing as requested,
BUT if the following of my code continue to execute without any user input, the window will not close until all the code ends.
the code after the maintop is basically some selenium based code that fills an online webpage.
But as I wrote earlier, if I ask an input() (after the maintop here), the window does close, but if I don't ak an input and let the code with selenium fills a webpage, the window will not close until the webpage fills (and it can take some times as it has multiple pages calls.)
def Close():
window.destroy()
window.quit()
def selection():
global bsiInput
bsiInput=str(value.get())
window = Tk()
window.geometry("350x350")
value = StringVar()
label = Label(window, text="Choose : ")
bouton1 = Radiobutton(window, text=" A ", variable=value, value='a',command=selection)
bouton2 = Radiobutton(window, text=" B " , variable=value, value='b',command=selection)
boutonOk = Button(window, text="Valider", command=Close)
label.pack()
bouton1.pack()
bouton2.pack()
boutonOk.pack()
window.mainloop()
Edit: I try both remarks suggested:
So, as I tried what you both suggest, I defines all my code in a MainProgram function.To be clear I just put a print("closed") after the mainloop(), and in my MainProgram() I start it with a print("prog starts"), from there:
def Close():
window.destroy()
window.after(0,MainProgram)
//outputs "closed" and end program as if MainProgram is never called
def Close():
window.destroy()
...
window.mainloop()
window.after(0,MainProgram)
//outputs "closed" and end programs as if MainProgram is never called
and
window.mainloop()
MainProgram()
//outputs "closed""prog starts" but don't close the window until the end of the program

TKinter how to stop the mainloop while its running and return to "idle" state

I have a tkinter application where user have many tasks to do. Most of the tasks call themselves recursively with tkinter after() method since every call I must check some pin states and other things, I figured that calling the function recursively every second will be fine. When the requirmenet is met ( some ping is toggled as required by the task), the task will complete and the function will go to mainloop "idle" state where I wait again for the user input.
Everything works fine except that I would like to implement one very important feature. Whenever user starts a task, I must have a emergency reset button, if pressed would restart the mainloop and immediately return to the "idle" state without the need to complete task. However, when the mainloop is running, I am not not sure how can I interrupt the task and stop it. I have made a simple "test" program to show exactly what I mean:
import tkinter as tk
from tkinter import Button,Entry,Canvas,Label,ttk
class Application(tk.Frame):
def __init__(self,master=None):
self.master = master
self.button_counter = 0
def Create_canvas(self,canvas_width,canvas_height):
global canvas#described as global because used outside class
canvas = tk.Canvas(master,bg='papaya whip',width=canvas_width,height=canvas_height)
def Application_Intro(self):
print("starting new app")
restart_program_button = tk.Button(canvas, text="Restart_program",font='Helvetica 12 bold', width=20, height=2,
command =self.Restart)
start_program_button = tk.Button(canvas, text="Start_program",font='Helvetica 12 bold', width=20, height=2,
command =self.Start_program)
increment_button = tk.Button(canvas, text="increment_button",font='Helvetica 12 bold', width=20, height=2,
command =self.increment_button)
canvas.create_text(960,20,text="MY PROGRAM",font='Helvetica 16 bold')
canvas.create_window(710,300,window = restart_program_button)
canvas.create_window(710,500,window = start_program_button)
canvas.create_window(710,100,window = increment_button)
canvas.pack()
master.mainloop()
def increment_button(self):
print("BUTTON INCREMENTED")
self.button_counter = self.button_counter +1 # increment button pressed everytime is pressed
def Start_program(self):
print("Program started")
if(self.button_counter > 1):
print("Task complete")
self.button_counter = 0
return
else:
master.after(1000,self.Start_program)
def Restart(self):# IF TASK STARTED SET RESTART =1, IF NOT restart devices and refresh app
print("HERE I WANT TO INTERRUPT START PROGRAM AND RETURN TO IDLE STATE")
print("REFRESH GUI ELEMENTS, DESTROY ANY WIDGETS IF CREATED")
print("RESET THE GLOBAL VARIABLE VALUES")
#master.mainloop()
#WHAT TO DO IN THIS FUNCTION TO GO BACK TO INITIAL MAINLOOP STATE??
return
master = tk.Tk()
app = Application(master=master)
app.Create_canvas(1920,1080)
app.Application_Intro()
The program above will create a simple tkinter program with 3 buttons.
Button at the top will increment button counter variable
Button in the middle is supposed to interrupt whatever task is running and return to "idle"state immediately
Button at the bottom will start a task. A task involves a person pressing Button at the top 2 times to increment button counter >1. After button incremented the counter 2 times, i return out of the recursive function which returns me back to mainloop "idle" state.
Now, as I mentioned, I must implement a technique where a user can return to "idle" state of the mainloop even if he didint press button to increment counter. It may be due to whatever emergency reason but I must be able to stop the current task and cancel it!
Can someone suggest me how should I do that since I could not find a neat way to do that.. It is also very important for me that this "reset" function will work for any possible task.
For example in reset function I could just set the button counter to any values > 1, and then the next call to task would trigger a reset, but this may not be the case for other tasks I will have that will not have any button counter involved.
Appreciate any help and suggestions

Python Tkinter, destroy toplevel after function

I'm programming some drives with python using Tkinter as GUI. When my machine is running, I'd like to show the user a toplevel window with some information which should close itself after the function completes. This is my minimal example:
from Tkinter import *
import time
def button_1():
window = Toplevel()
window.title("info")
msg = Message(window, text='running...', width=200)
msg.pack()
time.sleep(5.0)
window.destroy()
master = Tk()
frame = Frame(width=500,height=300)
frame.grid()
button_one = Button(frame, text ="Button 1", command = button_1)
button_one.grid(row = 0, column = 0, sticky = W + E)
mainloop()
The main problem is, that the toplevel window just appears after 5 seconds are over. Any suggestions?
Thanks!
time.sleep(5) is launched before the GUI has time to update, that's why the toplevel only appears after the 5 seconds are over. To correct this, you can add window.update_idletasks() before time.sleep(5) to force the update the display.
But, as Bryan Oakley points out in his answer, the GUI is frozen while time.sleep(5) is executed. I guess that your ultimate goal is not to execute time.sleep but some time consuming operation. So, if you do not want to freeze the GUI but do not know how long the execution will take, you can execute your function in a separated thread and check regularly whether it is finished using after:
import Tkinter as tk
import time
import multiprocessing
def function():
time.sleep(5)
def button_1():
window = tk.Toplevel(master)
window.title("info")
msg = tk.Message(window, text='running...', width=200)
msg.pack()
thread = multiprocessing.Process(target=function)
thread.start()
window.after(1000, check_if_running, thread, window)
def check_if_running(thread, window):
"""Check every second if the function is finished."""
if thread.is_alive():
window.after(1000, check_if_running, thread, window)
else:
window.destroy()
master = tk.Tk()
frame = tk.Frame(width=500,height=300)
frame.grid()
button_one = tk.Button(frame, text ="Launch", command=button_1)
button_one.grid(row = 0, column = 0, sticky = "we")
master.mainloop()
A general rule of thumb is that you should never call sleep in the thread that the GUI is running in. The reason is that sleep does exactly what it says, it puts the whole program to sleep. That means that it is unable to refresh the window or react to any events.
If you want to do something after a period of time, the correct way to do that is with after. For example, this will destroy the window after five seconds:
window.after(5000, window.destroy)

GUI Button hold down - tkinter

I'm trying to do a GUI in python to control my robotic car. My question is how I do a function that determine a hold down button. I want to move the car when the button is pressed and held down and stop the car when the button is released.
from Tkinter import *
hold_down = False
root = Tk()
def button_hold(event):
hold_down=true
while hold_down== True:
print('test statement')
hold_down = root.bind('<ButtonRelease-1>',stop_motor)
def stop_motor(event):
hold_down= False
print('button released')
button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<Button-1>',button_forward)
root.mainloop()
I'm trying to simulate what I found in this answer
I try to do it in a while loop with a boolean. When the user presses the button the boolean changes to True and code enters the while loop. When user releases the button the boolean changes to False and code exits from loop but in this code the boolean stay always true no matter if I released the button or not.
Edit: I want a function to be called until a condition occurs.The function to be called is hold_down() and the condition to check is the button is released.
Update: I found a way to make it work.
Set a flag when the button is pressed, unset the flag when the button is released. There's no need for a loop since you're already running a loop (mainloop)
from Tkinter import *
running = False
root = Tk()
def start_motor(event):
global running
running = True
print("starting motor...")
def stop_motor(event):
global running
print("stopping motor...")
running = False
button = Button(root, text ="forward")
button.pack(side=LEFT)
button.bind('<ButtonPress-1>',start_motor)
button.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
Assuming that you actually want to do something while the key is pressed, you can set up an animation loop using after. For example, to call a print statement once a second while the button is pressed you can add a function that does the print statement and then arranges for itself to be called one second later. The stop button merely needs to cancel any pending job.
Here's an example. The main difference to the original code is the addition of a move function. I also added a second button to show how the same function can be used to go forward or backward.
from Tkinter import *
running = False
root = Tk()
jobid = None
def start_motor(direction):
print("starting motor...(%s)" % direction)
move(direction)
def stop_motor():
global jobid
root.after_cancel(jobid)
print("stopping motor...")
def move(direction):
global jobid
print("Moving (%s)" % direction)
jobid = root.after(1000, move, direction)
for direction in ("forward", "backward"):
button = Button(root, text=direction)
button.pack(side=LEFT)
button.bind('<ButtonPress-1>', lambda event, direction=direction: start_motor(direction))
button.bind('<ButtonRelease-1>', lambda event: stop_motor())
root.mainloop()
You might want to try the repeatinterval option. The way it works is a button will continually fire as long as the user holds it down. The repeatinterval parameter essentially lets the program know how often it should fire the button if so. Here is a link to the explanation:
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/button.html
Search in-page for "repeatinterval".
Another name for this parameter is repeatdelay.
Building on Bryan Oakley's answer of using flags to simulate a press and hold button. The problem is that you can't have any while loops in your tkinter application to say while running move car forward.
Which is why I suggest using threads. This way you can have a while loop running in the background checking if the car should be moving foward.
from threading import Thread
from Tkinter import *
running = False
root = Tk()
def start_motor(event):
global running
print("starting motor...")
running = True
def stop_motor(event):
global running
running = False
print("stopping motor...")
def move_forward():
while True: # Thread will run infinitely in the background
if running:
print("Car is moving forward...\n")
button = Button(root, text ="forward")
button.pack(side=LEFT)
button.bind('<ButtonPress-1>',start_motor)
button.bind('<ButtonRelease-1>',stop_motor)
# Create and start the new thread
t = Thread(target = move_forward, args = ())
t.start()
root.mainloop()
Try this...
from Tkinter import *
root = Tk()
global hold_down
def button_hold(event):
hold_down = True
while hold_down:
print('test statement')
def stop_motor(event):
hold_down = False
print('button released')
button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<Button-1>',button_hold)
root.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
# Danny Try the following code:
def stop_motor(event):
print('button released')
return False
This answer run print 'test statement' one time. The while loop run one time when the button is pressed.
# Bryan Oakley Set a flag when the button is pressed, unset the flag when the button is released. There's no need for a loop since you're already running a loop (mainloop)
from Tkinter import *
running = False
root = Tk()
def start_motor(event):
global running
running = True
print("starting motor...")
def stop_motor(event):
global running
print("stopping motor...")
running = False
button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<ButtonPress-1>',start_motor)
root.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
This answer above stays in a infinite loop when the button is pressed.
# Joseph FarahYou might want to try the repeatinterval option. The way it works is a button will continually fire as long as the user holds it down. The repeatinterval parameter essentially lets the program know how often it should fire the button if so. Here is a link to the explanation:
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/button.html
Search in-page for "repeatinterval".
Another name for this parameter is repeatdelay.
I set the repeat interval in the parameter option for the button widget but it doesn't repeat the command.
Thanks for all the answer . Still looking to solve this problem.

Break ongoing loop started by a tkinter button

I want to build a tkinter interface that can start doing some repetitive work when a "start" button is pressed, and break the ongoing loop when the "stop" button is pressed. However I noticed that the tkinter does not process any further job untill the ongoing loop is finished, (e.g. does not respond to the "stop" button click, does not update the textbox or label text as shown in the attached code). It seems to be related to the single thread nature of tkinter from what I read at stackoverflow. Can someone help with some specific code? I am new to tkinter/Python, I could not figure out a real solution although I read many general discussions on board.
import time
import Tkinter as Tk
def _start():
for outer in range(5):
if active_stat:
time.sleep(1) # some code in the real app
else:
break
for inner in range(5):
if active_stat:
#counterstr.set("%02d-%02d" % (outer,inner)) #does not update till the end of loop
textbox.insert(Tk.END, "%02d-%02d\n" % (outer,inner)) #does not show till the end of loop
print "%02d-%02d" % (outer,inner)
time.sleep(1) #some code in the real app
else:
break
def _stop():
active_stat=False
active_stat=True
root = Tk.Tk()
#counterstr=Tk.StringVar()
#Tk.Label(root, textvariable=counterstr).pack(side=Tk.TOP)
textbox=Tk.Text(root)
textbox.pack(side=Tk.TOP)
Tk.Button(root, text='Start', command=_start).pack(side=Tk.LEFT)
Tk.Button(root, text='Stop', command=_stop).pack(side=Tk.LEFT)
Tk.Button(root, text='Quit', command=root.quit).pack(side=Tk.LEFT)
root.mainloop()
What I would do is, is that I would make your active_stat a tkinter variable, and then call its get method when ever you want to check if it:
EDIT: also, add root.update() to your loop. That should fix your problem. The reason for the tkinter variables is because of variable scope problems, which I initially though where your problem. (note: this code is for python 3...)
import time
import tkinter as Tk
def _start():
for outer in range(5):
if active_stat.get():
time.sleep(1) # some code in the real app
else:
active_stat.set(True)
break
for inner in range(5):
if active_stat.get():
#counterstr.set("%02d-%02d" % (outer,inner)) #does not update till the end of loop
textbox.insert(Tk.END, "%02d-%02d\n" % (outer,inner)) #does not show till the end of loop
print ("{}-{}".format(outer,inner))
time.sleep(1) #some code in the real app
else:
active_stat.set(True)
break
root.update()
def _stop():
active_stat.set(False)
root = Tk.Tk()
active_stat = Tk.BooleanVar(root)
active_stat.set(True)
#counterstr=Tk.StringVar()
#Tk.Label(root, textvariable=counterstr).pack(side=Tk.TOP)
textbox=Tk.Text(root)
textbox.pack(side=Tk.TOP)
Tk.Button(root, text='Start', command=_start).pack(side=Tk.LEFT)
Tk.Button(root, text='Stop', command=_stop).pack(side=Tk.LEFT)
Tk.Button(root, text='Quit', command=root.quit).pack(side=Tk.LEFT)
root.mainloop()

Categories

Resources