Tkinter 60 seconds timer - python

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.

Related

Can't call function to change stringvar

I am attempting to create a little clock by changing a tkinter label and using the Time Module. What I can't figure out is how to call the test() function with the while loop because I constantly get an error for timee is not defined or test() is not defined. Is there something I'm missing?
import time
from tkinter import *
def test():
seconds = time.time()
local_time = time.ctime(seconds)
timee.set('The Time Is: ' + local_time)
while 1 > 0:
test()
root = Tk()
timee = StringVar()
l = Label(root, textvariable=timee)
l.pack(side=TOP)
root.mainloop()
If you imagine your program running from top to bottom, you never reach the part where you create the Tk, StringVar and Label object; since it gets stuck in the infinite while loop. The error you get is because when you are calling test, timee is yet to defined.
Simply put, your program can only do one thing at a time. When it is busy running the clock, it cannot create TKinter windows. OTOH, when it is managing a tkinter window (.mainloop function), it cannot update your clock.
You need to get both to play nice with each other. There is a special method for the Tkinter object to tell it that you want something done: after. (See tkinter Widget docs):
import time
from tkinter import *
def test():
seconds = time.time()
local_time = time.ctime(seconds)
timee.set('The Time Is: ' + local_time)
# after updating, schedule next call in 1 second
root.after(1000, test)
root = Tk()
timee = StringVar()
l = Label(root, textvariable=timee)
l.pack(side=TOP)
# Before starting up, instruct Tkinter to run test after 1 second
root.after(1000, test)
root.mainloop()
As you see, there is no explicit call of test() anymore. Instead, in after(), the test function is passed as a variable. You can think of handing after() a list of instructions without already executing them.
Then, when tkinter is running its window (.mainloop() call), it will look at its list of things-to-do and will find the test call that you ordered. At the right time it will call the test function one time; then at the end of the function, the next call is scheduled.
timee is not defined because your function is looking for the local var timee but you want to alter the global variable so you need to indicate it to your function
def test():
global timee
seconds = time.time()
local_time = time.ctime(seconds)
timee.set('The Time Is: ' + local_time)
Tkinter applications are user even-driven which means normal procedural programming techniques often won't work — all processing has to occur while mainloop() is running.
In this case you can use the universal widget method after{} to schedule calls to your test function at regular intervals, like every 1/4 second (250 ms):
import time
from tkinter import *
def test():
seconds = time.time()
local_time = time.ctime(seconds)
timee.set('The Time Is: ' + local_time)
def poll():
test()
root.after(250, poll) # Schedule another call.
root = Tk()
timee = StringVar()
l = Label(root, textvariable=timee)
l.pack(side=TOP)
poll() # Start periodic calls to test()
root.mainloop()

Change text on tkinter button during call back execution

I'm trying to change the text on a button when the call back of that particular button executing.
Let's say I have "Run" as the text on my button, and I want to change it to "Running" just after it has been clicked, and during the call back execution.
After the completion of call back execution, I want to change it back to "Run".
I am not getting which part of my code is buggy.
import Tkinter as tk
import time
root = tk.Tk()
def change():
button.config(text='Running')
button.config(state='disabled')
print "start"
time.sleep(5)
print "end"
button.config(state='normal')
button.config(text="Run")
button = tk.Button(root,text="Run",command=change)
button.pack()
root.mainloop()
Sleep makes the WHOLE program stop for a moment, including the interface, usually stopping it from changing the interface itself, due to how the mainloop works.
Here, try the .after function, this should work fine.
import Tkinter as tk
import time
root = tk.Tk()
def change():
button.config(text='Running')
button.config(state='disabled')
print "start"
root.after(5000,changeback)
def changeback():
print "end"
button.config(state='normal')
button.config(text="Run")
button = tk.Button(root,text="Run",command=change)
button.pack()
root.mainloop()
root.after(5000,changeback) will call a command after 5000 milliseconds, or 5 seconds without stopping the whole program, that being changeback()

label.configure works sometimes why?

Part of my code is as follows:
def get_songs():
label6.configure(text='Wait')
os.system('/home/norman/my-startups/grabsongs')
label6.configure(text='Done')
The label is not updated at the first .configure() but is at the second one.
Except if I cause a deliberate error immediately after the first one at which point it is updated and then the program terminates.
The system call takes about 2 minutes to complete so it isn't as if there isn't time to display the first one.
I am using Python 2.7.6
Does anyone know why please?
I'm going to guess you're using Tkinter. If so, as #albert just suggested, you'll want to call label.update_idletasks() or label.update() to tell Tkinter to refresh the display.
As a very crude example to reproduce your problem, let's make a program that will:
Wait 1 second
Do something (sleep for 2 seconds) and update the text to "wait"
Display "done" afterwards
For example:
import Tkinter as tk
import time
root = tk.Tk()
label = tk.Label(root, text='Not waiting yet')
label.pack()
def do_stuff():
label.configure(text='Wait')
time.sleep(2)
label.configure(text='Done')
label.after(1000, do_stuff)
tk.mainloop()
Notice that "Wait" will never be displayed.
To fix that, let's call update_idletasks() after initially setting the text:
import Tkinter as tk
import time
root = tk.Tk()
label = tk.Label(root, text='Not waiting yet')
label.pack()
def do_stuff():
label.configure(text='Wait')
label.update_idletasks()
time.sleep(2)
label.configure(text='Done')
label.after(1000, do_stuff)
tk.mainloop()
As far as why this happens, it actually is because Tkinter doesn't have time to update the label.
Calling configure doesn't automatically force a refresh of the display, it just queues one the next time things are idle. Because you immediately call something that will halt execution of the mainloop (calling an executable and forcing python to halt until it finishes), Tkinter never gets a chance to process the changes to the label.
Notice that while the gui displays "Wait" (while your process/sleep is running) it won't respond to resizing, etc. Python has halted execution until the other process finishes running.
To get around this, consider using subprocess.Popen (or something similar) instead of os.system. You'll then need to perodically poll the returned pipe to see if the subprocess has finished.
As an example (I'm also moving this into a class to keep the scoping from getting excessively confusing):
import Tkinter as tk
import subprocess
class Application(object):
def __init__(self, parent):
self.parent = parent
self.label = tk.Label(parent, text='Not waiting yet')
self.label.pack()
self.parent.after(1000, self.do_stuff)
def do_stuff(self):
self.label.configure(text='Wait')
self._pipe = subprocess.Popen(['/bin/sleep', '2'])
self.poll()
def poll(self):
if self._pipe.poll() is None:
self.label.after(100, self.poll)
else:
self.label.configure(text='Done')
root = tk.Tk()
app = Application(root)
tk.mainloop()
The key difference here is that we can resize/move/interact with the window while we're waiting for the external process to finish. Also note that we never needed to call update_idletasks/update, as Tkinter now does have idle time to update the display.

Python: Is it possible to create an tkinter label which has a dynamic string when a function is running in background?

I have created a tkinter GUI for my python script. When I run the script, I want a dynamic string in one of the Label widgets on the GUI window, which will display:
"Working."
Then:
"Working.."
then
"Working..."
and then start from "Working." again until the script is completed.
(Actually I'd prefer a progress bar in this area)
Is it possible?
I wrote two simple scripts to help demonstrate how to do what you want. The first is using the label:
import tkinter as tk
root = tk.Tk()
status = tk.Label(root, text="Working")
status.grid()
def update_status():
# Get the current message
current_status = status["text"]
# If the message is "Working...", start over with "Working"
if current_status.endswith("..."): current_status = "Working"
# If not, then just add a "." on the end
else: current_status += "."
# Update the message
status["text"] = current_status
# After 1 second, update the status
root.after(1000, update_status)
# Launch the status message after 1 millisecond (when the window is loaded)
root.after(1, update_status)
root.mainloop()
The next one is using a progressbar:
import tkinter as tk
# You will need the ttk module for this
from tkinter import ttk
def update_status(step):
# Step here is how much to increment the progressbar by.
# It is in relation to the progressbar's length.
# Since I made the length 100 and I am increasing by 10 each time,
# there will be 10 times it increases before it restarts
progress.step(step)
# You can call 'update_status' whenever you want in your script
# to increase the progressbar by whatever amount you want.
root.after(1000, lambda: update_status(10))
root = tk.Tk()
progress = ttk.Progressbar(root, length=100)
progress.pack()
progress.after(1, lambda: update_status(10))
root.mainloop()
Note however that I couldn't do too much with the progressbar script because progressbars are a little tricky and need to be customized to your script exactly. I just wrote it to maybe shed a little light on the subject. The main part of my answer though is the label script.
Yes, it is possible. There are two ways to do it:
Whenever you want to update the label from your code you can call the_widget.configure(the_text). This will change the text of the label.
You can create an instance of a tkinter.StringVar, and assign it to the textvariable attribute of a label. Whenever you change the value of the variable (via the_variable.set(the_text), the label will automatically update.
Note that for either of these to work, the event loop needs to be able to process events (ie: you won't see anything if your function takes a long time to run and you never call update_idletasks or re-enter the event loop).

How to run a function in the background of tkinter [duplicate]

This question already has an answer here:
Tkinter locks Python when an icon is loaded and tk.mainloop is in a thread
(1 answer)
Closed 7 months ago.
I am new to GUI programming and I want to write a Python program with tkinter. All I want it to do is run a simple function in the background that can be influenced through the GUI.
The function counts from 0 to infinity until a button is pressed. At least that is what I want it to do. But I have no idea how I can run this function in the background, because the mainloop() of tkinter has control all the time. And if I start the function in an endless loop, the mainloop() cannot be executed and the GUI is dead.
I would like to return control back to the mainloop() after each cycle, but how can I get the control back from the mainloop() to the runapp-function without a user-triggered event?
Here is some sample code that kills the GUI:
from Tkinter import *
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button = Button(frame, text="START", command=self.runapp)
self.button.pack(side=LEFT)
self.hi_there = Button(frame, text="RESTART", command=self.restart)
self.hi_there.pack(side=LEFT)
self.runapp()
def restart(self):
print "Now we are restarting..."
def runapp(self):
counter = 0
while (1):
counter =+ 1
time.sleep(0.1)
Event based programming is conceptually simple. Just imagine that at the end of your program file is a simple infinite loop:
while <we have not been told to exit>:
<pull an event off of the queue>
<process the event>
So, all you need to do to run some small task continually is break it down into bite-sized pieces and place those pieces on the event queue. Each time through the loop the next iteration of your calculation will be performed automatically.
You can place objects on the event queue with the after method. So, create a method that increments the number, then reschedules itself to run a few milliseconds later. It would look something like:
def add_one(self):
self.counter += 1
self.after(1000, self.add_one)
The above will update the counter once a second. When your program initializes you call it once, and from then after it causes itself to be called again and again, etc.
This method only works if you can break your large problem (in your case "count forever") into small steps ("add one"). If you are doing something like a slow database query or huge computation this technique won't necessarily work.
You will find the answer in this other question Tkinter locks python when Icon loaded and tk.mainloop in a thread.
In a nutshell, you need to have two threads, one for tkinter and one for the background task.
Try to understand this example : clock updating in backgroud, and updating GUI ( no need for 2 threads ).
# use Tkinter to show a digital clock
# tested with Python24 vegaseat 10sep2006
from Tkinter import *
import time
root = Tk()
time1 = ''
clock = Label(root, font=('times', 20, 'bold'), bg='green')
clock.pack(fill=BOTH, expand=1)
def tick():
global time1
# get the current local time from the PC
time2 = time.strftime('%H:%M:%S')
# if time string has changed, update it
if time2 != time1:
time1 = time2
clock.config(text=time2)
# calls itself every 200 milliseconds
# to update the time display as needed
# could use >200 ms, but display gets jerky
clock.after(200, tick)
tick()
root.mainloop( )
credits: link to site
I don't have sufficient reputation to comment on Bryan Oakley's answer (which I found to be very effective in my program), so I'll add my experience here. I've found that depending on how long your background function takes to run, and how precise you want the time interval to be, it can be better to put self.after call at the beginning of the recurring function. In Bryan's example, that would look like
def add_one(self):
self.after(1000, self.add_one)
self.counter += 1
Doing it this way ensures that the interval of time is respected exactly, negating any interval drift that might occur if your function takes a long time.
If you don't want to be away from those threads, I would like to give one suggestion for your GUI-
Place the function for your GUI just before the root.mainloop() statement.
Example-
root = tk.Tk()
.
.
graphicsfunction() #function for triggering the graphics or any other background
#function
root.mainloop()
Please up vote if you like.

Categories

Resources