Tkinter button stays pressed - python

I am making a tkinter code that uses button widget but when I press the button, it stays pushed until the function which is executed on button press is not completed. I want the button to be released immediately and execute the function.
Here is a code that shows a good example of the happening:
from tkinter import *
import time
root = Tk()
root.geometry('100x100+100+100') # size/position of root
def callback(): # this function will run on button press
print('Firing in 3')
time.sleep(3) # wait for 3 seconds
def main(): #function 'main'
b = Button(root, text="ᖴIᖇE", width=10,height=2, command=callback)# setting the button
b["background"] = 'red' #button color will be red
b["activebackground"] = 'yellow' #button color will be yellow for the time when the button will not be released
b.place(x=25,y=25) #placing the button
main() # using function 'main'
mainloop()

GUI programs are typically driven in a single thread, which is controlled by the "main loop" of the graphic toolkit in use. That is: a program usually set up the application, and pass the control to the toolkit, which runs a tight loop that answers all users (and network, file, etc...) events, and the only user code ever to run again are the callbacks coded during the setup phase.
At the same time, when your code is running in during a callback, it holds controls - which means the toolkit won't be able to answer to any events while your function does not return.
What has to be done is to write code that cooperates with the GUI toolkit - that is, create events that generate further callbacks, if you need things spaced in time. In the case of tkinter, this is achieved with the method .after of a widget: after that many milliseconds, the callable passed will be run. time.sleep, on the other hand, stops the single thread there, and the event loop does not run.
In your example, you can simply write:
from tkinter import *
import time
root = Tk()
root.geometry('100x100+100+100') # size/position of root
def callback(): # this function will run on button press
print('Firing in 3')
root.after(3000, realcallback)
def realcallback():
print('Firing now!')
def main(): #function 'main'
b = Button(root, text="ᖴIᖇE", width=10,height=2, command=callback)# setting the button
b["background"] = 'red' #button color will be red
b["activebackground"] = 'yellow' #button color will be yellow for the time when the button will not be released
b.place(x=25,y=25) #placing the button
main() # using function 'main'
mainloop()

You can use threading inside the function you're being blocked while pressed. This is my edit on yours:
from tkinter import *
import time
import threading
root = Tk()
root.geometry('100x100+100+100') # size/position of root
def callback(): # this function will run on button press
def callback2():
print('Firing in 3')
time.sleep(3) # wait for 3 seconds
threading.Thread(target=callback2).start()
def realcallback():
print('Firing now')
def main(): # function 'main'
b = Button(
root,
text="Fire",
width=10,
height=2,
command=callback
) # setting the button
b["background"] = 'red' # button color will be red
# button color will be yellow for the time when the button will not be released
b["activebackground"] = 'yellow'
b.place(x=25, y=25) # placing the button
main() # using function 'main'
mainloop()

I would like to add:
from tkinter import *
import time
root = Tk()
root.geometry('100x100+100+100') # size/position of root
def callback(): # this function will run on button press
root.update() #<- this works for me..
print('Firing in 3')
root.after(3000, realcallback)
def realcallback():
print('Firing now!')
def main(): #function 'main'
b = Button(root, text="ᖴIᖇE", width=10,height=2, command=callback)# setting the button
b["background"] = 'red' #button color will be red
b["activebackground"] = 'yellow' #button color will be yellow for the time when the button will not be released
b.place(x=25,y=25) #placing the button
main() # using function 'main'
mainloop()

Related

Exit Python Tkinter app cleanly while also using "wait_variable" function on a button in an "after" loop

I have a problem similar to this post: Exit program within a tkinter class
My variation on the problem involves the wait_variable being used on a button to control "stepping forward" in an app, but also allowing the app to close cleanly.
See my code below:
# To see output unbuffered:
# python -u delete_win_test.py
import tkinter as tk
from tkinter import *
class GUI(Tk):
def __init__(self):
super().__init__()
# Close the app when the window's X is pressed
self.protocol("WM_DELETE_WINDOW", self.closing)
# When this var is set to 1, the move function can continue
self.var = tk.IntVar()
# Close the app if the button is pressed
button = tk.Button(self, text="Exit",
command=self.destroy)
button.place(relx=.5, rely=.5, anchor="c")
# Step forward
self.step_button = tk.Button(self, text="Step",
command=lambda: self.var.set(1))
self.step_button.place(relx=.5, rely=.75, anchor="c")
def move(self):
print("doing stuff") # simulates stuff being done
self.step_button.wait_variable(self.var)
self.after(0, self.move)
def closing(self):
self.destroy()
app = GUI()
app.move()
app.mainloop()
The window shows correctly
"Stepping forward" works because "doing stuff" prints to terminal on button click
Exiting the window by both pressing X or using the "exit" button both work
The problem: the Python app never exits from the terminal and requires a closing of the terminal.
How can I make the Python program exit cleanly so the user does not need to close and re-open a new terminal window?
Related references for animation, etc:
Animation using self.after: moving circle using tkinter
Button wait: Making Tkinter wait untill button is pressed
The original "exit" code: Exit program within a tkinter class
UPDATE (the solution):
(Credit to both response answers below)
# Close the app if the button is pressed
button = tk.Button(self, text="Exit",
- command=self.destroy)
+ command=self.closing)
button.place(relx=.5, rely=.5, anchor="c")
# Step forward
...
def closing(self):
self.destroy()
+ self.var.set("")
+ exit(0)
This allows the native window's "X" to close the window and the Tk button to close the window while still closing the Python app cleanly in the terminal.
Your closing function needs to set the variable to cause the app to stop waiting.
def closing(self):
self.destroy()
self.var.set("")
in the closing function, you need to call exit to exit the program.
def closing(self):
self.destroy() #closes tkinkter window
exit(0) #exits program

how to cancel to call the function in python tkinter?

I am creating a reminder application in python using the Tkinter module. I need to cancel to call the function when the user clicks on the cancel remind button. I tried to assign the time (time variable that contains the time in milliseconds when the function will call) variable to 0, but it does not work.
sorry,for late respond i was creating small example this is the smallest example i can create.
code:
# example:
from tkinter import Tk, mainloop, TOP
from tkinter.ttk import Button
time=10000
# creating tkinter window
root = Tk()
def function_to_cancel():
global time
time=0 # not works
button = Button(root, text = 'Remind Me! after 10 seconds')
button.pack(side = TOP, pady = 5)
cancel=Button(root,text='Cancel Remind',command=function_to_cancel)#this button will cancel the remind
cancel.pack()
print('Running...')
root.after(time, root.destroy)
mainloop()
If you understand the question, please answer.
You need to save the task ID returned by .after() and then use the ID with .after_cancel() to cancel the scheduled task:
from tkinter import Tk, mainloop, TOP
from tkinter.ttk import Button
time=10000
# creating tkinter window
root = Tk()
def function_to_cancel():
#global time
#time=0 # not works
root.after_cancel(after_id)
button = Button(root, text = 'Remind Me! after 10 seconds')
button.pack(side = TOP, pady = 5)
cancel=Button(root,text='Cancel',command=function_to_cancel)#this button will cancel the remind
cancel.pack()
print('Running...')
# save the ID returned by after()
after_id = root.after(time, root.destroy)
mainloop()

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)

Testing a Tkinter Button object with an if statement

I am working on a program that needs a GUI with buttons to do certain things, as is usually the case when having questions about Buttons, but I have ran into difficulties because while you can activate functions with buttons, you cannot test wether they are currently being pressed with an if statement. I know how to use check buttons and radio buttons, but I have not found anything else remotely useful. I need to be able to tell how long they are pressed, and to do things as they are being pressed that stop when they are released. I need a way of assigning a variable that will be true while you are still holding click down over the button, and false any other time, with a normal button, not one that toggles each time you press.
It's not clear to me what you're having trouble with, so I took the liberty of coding up a little GUI that times how long a button is pressed.
import tkinter as tk
import time
class ButtonTimer:
def __init__(self, root):
self.master = root
self.button = tk.Button(self.master, text="press me") # Notice I haven't assigned the button a command - we're going to bind mouse events instead of using the built in command callback.
self.button.bind('<ButtonPress>', self.press) # call 'press' method when the button is pressed
self.button.bind('<ButtonRelease>', self.release) # call 'release' method when the button is released
self.label = tk.Label(self.master)
self.startTime = time.time()
self.endTime = self.startTime
self.button.grid(row=1, column=1)
self.label.grid(row=2, column=1)
def press(self, *args):
self.startTime = time.time()
def release(self, *args):
self.endTime = time.time()
self.label.config(text="Time pressed: "+str(round(self.endTime - self.startTime, 2))+" seconds")
root = tk.Tk()
b = ButtonTimer(root)
root.mainloop()
Note: I tested this in python 2.7 then changed the import from Tkinter to tkinter. It will probably work in 3.x, but I haven't tested it with that version.

How to close a Tkinter window by pressing a Button?

Write a GUI application with a button labeled "Good-bye". When the
Button is clicked, the window closes.
This is my code so far, but it is not working. Can anyone help me out with my code?
from Tkinter import *
window = Tk()
def close_window (root):
root.destroy()
frame = Frame(window)
frame.pack()
button = Button (frame, text = "Good-bye.", command = close_window)
button.pack()
window.mainloop()
With minimal editing to your code (Not sure if they've taught classes or not in your course), change:
def close_window(root):
root.destroy()
to
def close_window():
window.destroy()
and it should work.
Explanation:
Your version of close_window is defined to expect a single argument, namely root. Subsequently, any calls to your version of close_window need to have that argument, or Python will give you a run-time error.
When you created a Button, you told the button to run close_window when it is clicked. However, the source code for Button widget is something like:
# class constructor
def __init__(self, some_args, command, more_args):
#...
self.command = command
#...
# this method is called when the user clicks the button
def clicked(self):
#...
self.command() # Button calls your function with no arguments.
#...
As my code states, the Button class will call your function with no arguments. However your function is expecting an argument. Thus you had an error. So, if we take out that argument, so that the function call will execute inside the Button class, we're left with:
def close_window():
root.destroy()
That's not right, though, either, because root is never assigned a value. It would be like typing in print(x) when you haven't defined x, yet.
Looking at your code, I figured you wanted to call destroy on window, so I changed root to window.
You could create a class that extends the Tkinter Button class, that will be specialised to close your window by associating the destroy method to its command attribute:
from tkinter import *
class quitButton(Button):
def __init__(self, parent):
Button.__init__(self, parent)
self['text'] = 'Good Bye'
# Command to close the window (the destory method)
self['command'] = parent.destroy
self.pack(side=BOTTOM)
root = Tk()
quitButton(root)
mainloop()
This is the output:
And the reason why your code did not work before:
def close_window ():
# root.destroy()
window.destroy()
I have a slight feeling you might got the root from some other place, since you did window = tk().
When you call the destroy on the window in the Tkinter means destroying the whole application, as your window (root window) is the main window for the application. IMHO, I think you should change your window to root.
from tkinter import *
def close_window():
root.destroy() # destroying the main window
root = Tk()
frame = Frame(root)
frame.pack()
button = Button(frame)
button['text'] ="Good-bye."
button['command'] = close_window
button.pack()
mainloop()
You can associate directly the function object window.destroy to the command attribute of your button:
button = Button (frame, text="Good-bye.", command=window.destroy)
This way you will not need the function close_window to close the window for you.
from tkinter import *
window = tk()
window.geometry("300x300")
def close_window ():
window.destroy()
button = Button ( text = "Good-bye", command = close_window)
button.pack()
window.mainloop()
You can use lambda to pass a reference to the window object as argument to close_window function:
button = Button (frame, text="Good-bye.", command = lambda: close_window(window))
This works because the command attribute is expecting a callable, or callable like object.
A lambda is a callable, but in this case it is essentially the result of calling a given function with set parameters.
In essence, you're calling the lambda wrapper of the function which has no args, not the function itself.
from tkinter import *
def close_window():
import sys
sys.exit()
root = Tk()
frame = Frame (root)
frame.pack()
button = Button (frame, text="Good-bye", command=close_window)
button.pack()
mainloop()

Categories

Resources