I am making a GUI application in tkinter and I need the code in a function to run every 60 seconds. I have tried using the root.after(root, function) pause, but using this, my GUI window froze and the code in the function continued to run as I was interrupting the root.mainloop(). Here is a bit of the code with which I am having problems:
def start():
global go
go=1
while go == 1:
getclass()
root.after(1000,func=None)
def stop():
global go
go=0
def getclass():
#my code here
print("Hello World")
I just want getclass() to run every 60 seconds when the start button is pushed. Then I want this 60 second loop to stop when the stop button is pushed.
Thank you in advance,
Sean
You are overthinking this: What is needed is very simple, maybe like this:
def start():
getclass()
root.after(60000, start) # <-- 60,000 milliseconds = 60 seconds
When start() gets called, it executes a call to getClass(), then sets a callback that will call itself again in 60 seconds.
Minimal app demonstrating the use:
import tkinter as tk
def getclass():
print('hello')
def start():
getclass()
root.after(60000, start) # <-- 60,000 milliseconds = 60 seconds
root = tk.Tk()
start()
root.mainloop()
Related
i am trying to do a 60 timer for a project but i am stuck here. My variable temp doesnt update itself on my window everytime i go trough it. Anyhelp would be great.
import tkinter
root=tkinter.Tk()
root.geometry("300x250")
root.title("Time Counter")
Frame=tkinter.Frame(root)
Frame.pack(side=tkinter.TOP)
Label1=tkinter.Label(Frame,text="Timer")
Label1.pack(side=tkinter.LEFT)
def timer(*args,**kwargs):
temp=int(temps.get())
while temp>-1:
temps.set(str(temp))
root.update()
time.sleep(1)
temp-=1
temps=tkinter.StringVar()
temps.set("60")
temps.trace("w",timer)
label=tkinter.Label(Frame,textvariable=temps)
label.pack(side=tkinter.LEFT)
label.after(1000,timer)
root.mainloop()
It works fine if you just remove trace and after and call the function directly:
temps=tkinter.StringVar()
temps.set("60")
label=tkinter.Label(Frame,textvariable=temps)
label.pack(side=tkinter.LEFT)
timer()
Adding the timer() function to the trace of your StringVar leads to the function being called whenever the value of temps changes. This can't work since the function changes the value, itself.
I am using .after to invoke a function which moves an image inside my GUI. When i run the code and call the function "myFunc" the image does move, but it happens instantaneously. It should gradually move across the screen. I am unsure as to why this is happening.
def movRight():
global img
global imgx
canvas.move(imgx,20,0)
return
def myFunc():
moveController(1,20)
return
def moveController(extruder, position):
global e1current
global e2current
global e3current
global e4current
if extruder == 1:
while position > e1current:
print("moving")
e1current+=1
main.after(500,movRight)
return
.after does not block execution of the loop. It's more like it spawns a seperate thread which begins execution after 500 (in your case) milliseconds. In other words, .after does not block the loop for 500ms, call movRight and then continue the loop. Take a look at this example:
import tkinter as tk
def print_hi():
print("hi")
root = tk.Tk()
for i in range(5):
root.after(1000, print_hi)
print("loop done")
root.mainloop()
This outputs "loop done", and then, approximately one second later, it prints "hi" five times.
Consider this code:
while position > e1current:
main.after(500,movRight)
It is the same as if you had written it this way:
main.after(500,movRight)
main.after(500,movRight)
main.after(500,movRight)
...
Since calling main.after(500,movRight) probably executes in a millisecond or so, you are queueing up hundreds or thousands of calls that will all execute 500ms in the future.
In other words, it's no differen than if you had done this:
def something():
movRight()
movRight()
movRight()
...
main.after(500,something)
The proper way to do an animation is to create a function that does one frame of animation, and then reschedules itself.
For example:
def moveController():
movRight()
e1current+=1
if position > e1current:
main.after(500, moveController)
I have a simple Tkinter gui with about 20 buttons on it. When I click on a button, the script runs for about 5 minutes. During which time, I have to wait until the script stops running to click on the other buttons. Is there a way to setup the window so I can click on other button while the first clicked script is running?
from Tkinter import *
import Tkinter as tk
import time
def function1():
time.sleep(60)
print 'function1'
def function2():
time.sleep(60)
print 'function2'
root = Tk()
w = 450 # width for the Tk root
h = 500# height for the Tk root
frame = Frame(root, width=w,height =h)
button1=Button(frame, text = 'function 1',fg='black',command=function1).grid(row=1,column=1)
button2=Button(frame, text = 'function 2',fg='black',command=function2).grid(row=1,column=2)
frame.pack()
root.mainloop()
I want to be able to click on function2 after while function1 is still running
If you trigger a callback that takes 1 minute to run, you're not returning to the main loop for 1 minute, so the GUI can't respond to anything.
There are two common solutions to this.
The first is to use a background thread:
def function1():
time.sleep(60)
print 'function1'
def function1_background():
t = threading.Thread(target=function1)
t.start()
button1 = Button(frame, text='function 1', fg='black', command=function1_background)
This is simple, but it only works when your code is purely doing background work, not touching any of the tkinter widgets.
The only problem here is that you'd have to def 20 extra functions. You don't want to repeat yourself that much—that's 80 lines of repetitive boilerplate code that gets in the way of seeing the code that matters, and 20 chances to make a stupid bug in copy-pasting that's a pain to track down, and 20 places you have to change if you later decide you want, say, processes instead of threads so the work can parallelize better, or a pool of 4 threads with the background tasks queued up.
You can solve that in a few different ways. See this question for more in-depth explanation, but in short, you get Python to do some of the repetitive work for you.
You can def a single helper function:
def background(func):
t = threading.Thread(target=func)
t.start()
… and then lambda 20 separate function:
button1 = Button(frame, text='function 1', fg='black', command=lambda: background(function1))
Alternatively, you can partially apply the function using partial:
button1 = Button(frame, text='function 1', fg='black', command=functools.partial(background, function1))
Or, if you never want to call the functions except in the background, you can write a decorator and apply it to each function at def time:
def background(func):
#functools.wraps(func)
def wrapper():
t = threading.Thread(target=func)
t.start()
return wrapper
#background
def function1():
time.sleep(60)
print 'function1'
If you can't use threads (e.g., because the background work involves fiddling with your tkinter widgets), the alternative is to restructure your code so that, instead of being one monolithic task that takes 1 minute, it's a bunch of separate tasks that each takes a fraction of a second and schedules the next part:
def function1(count=60):
if count > 0:
time.sleep(0.1)
frame.after(0, function1, count-0.1)
else:
print 'function1'
button1 = Button(frame, text='function 1', fg='black', command=function1)
This always works, if you can find a way to do it. Your real work may not be as easy to divide into 0.1-second chunks as a sleep(60) is.
I have a Raspberry Pi with the Piface adaptor board. I have made a GUI which controls the LED's on the Piface board.
I wrote a small piece of code to make the LED's run up and down continuously, like Knight Riders car, using a While loop.
I then wrote another piece of code that created a GUI. In the GUI is a button that starts the LED's running up and down continuously with the While loop piece of code.
What I want to do is to have that GUI button start the LED running sequence, and then the same button stop the sequence at any time.
I do understand that the code is sitting/stuck in the While loop. And hence any buttons in the GUI are not going to have an effect.
So is there a better way of doing it? Any pointers would be appreciated.
Thanks in advance.
Another option is to run the LED while loop in a separate thread. Like in
the next code. The while loop is stopped by toggling the shared led_switch
variable.
"""
blinking LED
"""
import tkinter as tk
import threading
import time
led_switch=False
def start_stop():
global led_switch
led_switch=not led_switch
if led_switch:
t=threading.Thread(target=LED)
t.start()
def LED():
while led_switch:
print('LED on')
time.sleep(1)
print('LED off')
time.sleep(1)
root=tk.Tk()
button=tk.Button(root,command=lambda: start_stop(),text='start/stop')
button.pack()
tk.mainloop()
If you have a while loop and a GUI you can use generators to still use the loop and let the GUI run properly.
I sketch the Idea here and create an example for the Tkinter GUI.
You want to write your code as a loop and still use it in a GUI:
from Tkinter import *
from guiLoop import guiLoop # https://gist.github.com/niccokunzmann/8673951
#guiLoop
def led_blink(argument):
while 1:
print("LED on " + argument)
yield 0.5 # time to wait
print("LED off " + argument)
yield 0.5
t = Tk()
led_blink(t, 'shiny!') # run led_blink in this GUI
t.mainloop()
Output while the GUI is responsive:
LED on shiny!
LED off shiny!
LED on shiny!
LED off shiny!
...
Sadly Tkinter is the only GUI I know and it is a bad example because you can always update the GUI in your loop with the update() method of GUI elements:
root = Tk()
while 1:
print("LED on")
t = time.time() + 0.5
while t > time.time(): root.update()
print("LED off")
t = time.time() + 0.5
while t > time.time(): root.update()
But with such a guiLoop you can have multiple loops:
t = Tk()
led_blink(t, 'red')
led_blink(t, 'blue')
led_blink(t, 'green')
t.mainloop()
Here are some examples for starting and stopping the loop with a button.
If you're using Tkinter, there's a very easy pattern for running a loop. Given that the UI (in just about every UI toolkit) is already running an infinite loop to process events, you can leverage this to run code periodically.
Let's assume you have a python object "led" which has a method for toggling it on and off. You can have it switch from on to off every 100ms with something as simple as these three lines of code:
def blink(led):
led.toggle()
root.after(100, blink, led)
The above code will run forever, causing the led to blink every 100ms. If you want to be able to start and stop the blinking with a button, introduce a flag:
def blink(led):
if should_blink:
led.toggle()
root.after(100, blink, led)
When you set the toggle to True, the led will start blinking. When it's False, it will stop blinking.
The main thing to take away from this is that you already have an infinite loop running, so there's no need to create one of your own, and no need to use something as complex as threading. Simply create a function that does one frame of animation, or calls some function or does some unit of work, then have the function request that it be run again in the future. How far in the future defines how fast your animation or blink will run.
I am having problems with Tkinter's canvas.coords() method. I am trying to update the canvas every 1/60 of a second and trying to detect when the Right arrow key is pressed. The problem is that when I keep the right arrow pressed for a while I get an error :
http://postimage.org/image/bdfk3m8gx/
My script is 'novo.py' and the code is given below. Any help perhaps? I'm just a begginer.
import Tkinter, threading, time
def real_time(td, canvas):
while True:
time.sleep(1/60.0)
canvas.coords('line',1,1,200,200)
def func(event):
print 'key pressed'
root = Tkinter.Tk()
canvas = Tkinter.Canvas(root)
canvas.pack()
canvas.create_line(1,1,100,100, tag='line')
root.bind('<Right>', func)
thread = threading.Thread(target = real_time, args = (1/60.0, canvas))
thread.start()
root.mainloop()
Tkinter is not thread safe, and quite often threads simply aren't needed.
In your case you can use after to periodically ron something every N milliseconds. For example, to run something approximately 60 times per second you would do something like this:
def real_time():
<do something here>
self.after(16, real_time)
Then, call this function once when your program starts up:
real_time()