I wrote a Tkinter program that works great except that the entire program freezes when pressing a button that calls a long running function. The user has to wait for the function to finish before doing anything else.
I found a video that showed a solution for this and it worked for the guy in the video but not for me.
The following test case shows the problem.
import tkinter as tk
from random import randint
import time
import threading
root = tk.Tk()
root.title("Test Tkinter Threading")
root.geometry("500x400")
def five_seconds():
time.sleep(5)
my_label.config(text="5 Seconds is up!")
def rando():
random_label.config(text=f'Random Number: {randint(1, 100)}')
my_label = tk.Label(root, text="Hello there!")
my_label.pack(pady=20)
# This works but hangs program while executing five_seconds()
#sleep_button = tk.Button(root, text="5 seconds", command=five_seconds)
#sleep_button.pack(pady=20)
# Supposed to let user keep chosing random numbers while five_seconds() is running.
sleep_button = tk.Button(root, text="5 seconds",
command=threading.Thread(target=five_seconds).start())
sleep_button.pack(pady=20)
random_button = tk.Button(root, text="Pick Random Number", command=rando)
random_button.pack(pady=20)
random_label = tk.Label(root, text="")
random_label.pack(pady=20)
root.mainloop()
The guy in the video was able to ckick on the sleep button and keep clicking on the random button as expected. When I run this on windows 10 I get a strange result. If I click on the random button the five_seconds() function executes (even though I never hit the sleep buttton). The random button works and does not seem to hange but the program execution is messed up.
Is there an issue with windows 10 and threading Tkinter?
Related
I'm using Tkinter to show a login dialog and then run my main logic.
I intend for the following snippet to close the window (finish the main loop of tk) after clicking the button and just print indefinitely (it is wrapped in while True is in order for the whole script to continue executing, which simulates a real program logic).
But instead, the following snippet hangs the window and fails to close in macOS Ventura with Python 3.10:
from time import sleep
from tkinter import Tk, Button
def quit():
root.quit()
root = Tk()
Button(root, text="Quit", command=quit).pack()
root.mainloop()
while True:
sleep(1)
print("Running a program logic...")
I've tried to run a functional version of this code (which fails the same) and a threaded version of it (which just crashes since an NSWindow must be created on the main thread).
I just really can't wrap my head around it!
EDIT: Fully working example
EDIT 2: Clarify the intention of this code
EDIT 3: Even more minimal code
Try this:
from Tkinter import *
def quit():
global root
root.quit()
root = Tk()
while True:
Button(root, text="Quit", command=quit).pack()
root.mainloop()
In the code below, pressing the space bar twice results in two successive beeps. I want to avoid this and instead disable the key while the first beep is happening. I thought unbinding the space key might work, but it doesn't. It's strange that only two beeps seem to stack up rather than more. I'm guessing maybe the cause of the issue is that winsound.Beep is non-blocking so the rebinding occurs almost instantly.
Any suggestion on how to get this to work please?
import winsound
from tkinter import *
def beep(e):
frame.unbind("<space>")
winsound.Beep(440, 1000)
frame.bind("<space>", beep)
root = Tk()
frame = Frame(root, width=100, height=100)
frame.bind("<space>", beep)
frame.pack()
frame.focus_set()
root.mainloop()
Here is a solution that takes the focus away from the widget, so the binding wont get triggered:
import winsound
from tkinter import *
def beep(event):
dummy.focus_set() #setting focus to dummy
winsound.Beep(440, 1000) #playing it
root.after(1000,frame.focus_set) #setting focus back after playing for 1000 ms
root = Tk()
dummy = Label() #making a dummy widget
dummy.pack()
frame = Frame(root, width=100, height=100)
frame.bind("<space>",beep)
frame.pack()
frame.focus_set()
root.mainloop()
I've commented it to understand better, but this is just a way around and its not that complicated to understand either.
Also keep in mind, in all cases of using winsound, as long as that beep has started and finished playing, the GUI will be unresponsive, that is, GUI will be unresponsive for 1 sec(in your case).
This should fix it however you have to download keyboard module with pip install keyboard :
import winsound
from tkinter import *
import keyboard
from _thread import start_new_thread
def beep():
while True:
if keyboard.is_pressed('space'):
winsound.Beep(440, 1000)
root = Tk()
frame = Frame(root, width=100, height=100)
start_new_thread(beep, ())
frame.pack()
frame.focus_set()
root.mainloop()
First start_new_thread()(syntax is important) makes beep() threaded (runs in background?) and its a while loop so it runs continuously and whenever you press space it will beep and even if you spam space it will still just run one beep. However there is a downside. It will run while script is not terminated so if you focus out it will still beep if you press spacebar
You can use the elapsed time since the last successful keypress to decide if the beep should be produced or not.
maybe like this: I do not have access to winsound, so I am using an os feature to mimic a beep. You can comment this out, and uncomment the calls to winsound
# import winsound
import os
import tkinter as tk
import time
def beep(e, time_limit=1, timer=[0]):
t0 = timer[0]
t1 = time.time()
delta_t = t1 - t0
if delta_t < time_limit:
return
# winsound.Beep(440, 1000)
os.system('say "Beep"')
timer[0] = t1
root = tk.Tk()
frame = tk.Frame(root, width=100, height=100)
frame.bind("<space>", beep)
frame.pack()
frame.focus_set()
root.mainloop()
You can bind back the event via after_idle():
def beep(e):
e.widget.unbind('<space>')
winsound.Beep(440, 1000)
e.widget.after_idle(e.widget.bind, '<space>', beep)
explanation:
The callback passed to after_idle() will be executed when tkinter mainloop is idle, i.e. no pending works/events to be handled. So if the spacebar is pressed many times, the first press triggers beep() in which tkinter unbinds the event, beep and then schedules the rebind. After beep() returns, tkinter keeps handling the pending tasks, i.e. handle the rest spacebar events (but at that moment, no bind is active) and then do the after_idle schedule task which is the rebind.
How can I create a Tkinter GUI stop button to interrupt a function that takes a lot to be executed. I would like to be able to just hit the stop button and then it interrupts the main function and cleans all GPIOs. It would be cool if I could do it using the tk.after() instead of using threads. Thank you!
from Tkinter import *
def main_function():
#a function that open and closes GPIOs on a RPi, and takes hours to be
#fully executed.
root = Tk()
root.title("Title")
root.geometry("500x500")
app = Frame(root)
app.grid()
start = Button(app, text="Start Scan",command=scanning)
stop = Button(app, text="Stop",command="break")
start.grid()
stop.grid()
Once I hit the start I need to wait the main function to end to be able to click the stop button.
I'm running pycharm on the mac and noticed that the GUI would not open when it previously has. It bounces in the dock and then says that it is not responding.
So I wrote a very simple program to test the GUI and it still doesn't work.
from tkinter import *
import random
import time
root = Tk()
root.geometry("600x400")
var = 0
one = Label(root, textvariable=var)
one.pack()
while 1 == 1:
var = random.randint(1, 100)
time.sleep(1)
root.mainloop()
I tried reinstalling python but it doesn't help. Also, for my other program, textvariable wasn't able to work and I can't figure out why.
In order for the GUI to run and be responsive, mainloop has to execute. But your mainloop will never execute, because your while 1 == 1: loop will never finish. If you want to do something every second, remove that loop and use root.after instead.
Your textvariable isn't working because var is an integer. It needs to be a StringVar.
I want to see continuously changing value of label in tkinter window. But I'm not seeing any of it unless I make a keyboard interrupt in MS-CMD while running which shows me the latest assigned value to label. Plz tell me ..What's going on & what's the correct code ??
import random
from Tkinter import *
def server() :
while True:
x= random.random()
print x
asensor.set(x)
app=Tk()
app.title("Server")
app.geometry('400x300+200+100')
b1=Button(app,text="Start Server",width=12,height=2,command=server)
b1.pack()
asensor=StringVar()
l=Label(app,textvariable=asensor,height=3)
l.pack()
app.mainloop()
The function server is called when you click the button, but that function contains an infinite loop. It just keep generating random numbers and sending these to asensor. You are probably not seeing any of it because the server function is run in the same thread as the GUI and it never gives the label a chance to update.
If you remove the while True bit from your code, a new number will be generate each time you click the button. Is that what you wanted to do?
Edit after comment by OP:
I see. In that case your code should be changed as follows:
import random
from Tkinter import Tk, Button, Label, StringVar
def server():
x = random.random()
print x
asensor.set(x)
def slowmotion():
server()
app.after(500, slowmotion)
app = Tk()
app.title("Server")
app.geometry('400x300+200+100')
b1 = Button(app, text="Start Server", width=12, height=2, command=slowmotion)
b1.pack()
asensor = StringVar()
asensor.set('initial value')
l = Label(app, textvariable=asensor, height=3)
l.pack()
app.mainloop()
I also introduced a new function, slowmotion, which does two things: 1) calls server, which updates displays the value, and 2) schedules itself to be executed again in 500ms. slowmotion is first ran when you first click the button.
The problem with your code was that it runs an infinite loop in the main GUI thread. This means once server is running, the GUI will not stop and will not get a chance to display the text you asked it to display.