Why does time.sleep pause tkinter window before it opens - python

I'm developing a program for a stadium and time.sleep() pauses the program before the window opens instead of when I want it to. What is the explanation for this behavior?
import Tkinter as tk
import time
import random
root = tk.Tk()
label = tk.Label(root, text="Navigating To Seat")
label.pack(pady=10, padx=10)
rand = random.randint(6, 16)
while rand != 0:
label2 = tk.Label(root, text="Foward: " + str(rand) + "m")
label2.pack()
rand = rand - 1
time.sleep(1)
label2.pack_forget()
root.mainloop()

What time.sleep does is suspend the execution of your program. If you do that 6-16 times, for 1 second each time, before ever calling mainloop(), you're asking it to wait for 6-16 seconds before starting up your GUI.
You probably don't understand how event loop programming works. Reading through some Tkinter tutorials should get the idea across nicely. If you want a less Tkinter-focused explanation and more information about the details of what's happening and the different ways to get around it, see Why your GUI app freezes.
At any rate, I think I can guess what you want to do, even though it isn't clear from your question: You want to start the GUI up, and then, every second, replace the Label. To do that, you have to wait while the GUI is running, not before it starts.
But you can't just call sleep while the GUI is running, either. The GUI can't run while your program is asleep (again, that's what sleep means).
The easiest way out of this is to turn your loop into a sequence of function calls, each of which schedules the next one to run a second later, using the after method. For example:
import Tkinter as tk
import random
root = tk.Tk()
label = tk.Label(root, text="Navigating To Seat")
label.pack(pady=10, padx=10)
rand = random.randint(6, 16)
label2 = None
def add_label():
global rand
global label2
if not rand:
root.quit()
if label2:
label2.pack_forget()
label2 = tk.Label(root, text="Foward: " + str(rand) + "m")
label2.pack()
rand = rand - 1
root.after(1000, add_label)
add_label()
root.mainloop()
When you first call add_label(), it creates the initial label, asks Tkinter to call add_label() again in 1000 milliseconds, and returns. So, a second after you start the loop, it gets called again, which creates the next label and asks Tkinter to call it again a second later. This keeps going until you decrement rand all the way to 0, at which point you call quit instead of after, which ends the main loop, which ends the program.
There are other things you probably want to fix about this program. For example, instead of destroying and creating a new Widget label each time, you can just change its text—or, maybe even more simply, make rand an IntVar connected to the label, so just updating rand automatically changes the text. Also, for anything less trivial than this program, you'd probably want to replace the global variables with something cleaner—most Tkinter tutorials show you how to use a Frame subclass by about the second or third example, which gives you a convenient place to organize both widgets and member variables like rand.

Related

Python tkinter, labels, and function order

This seems so simple but I can't figure out what I need to do to remedy. I have a tkinter project and on a button press, a function runs that takes several seconds. I want a "loading..." type message while the function is running so it's obvious it's actually working and not crashed. I figured a label would be easy enough and on the first line of the function, have label1.set('loading') but I suppose because of the way functions work, the label doesn't set until the function is done running--which is not helpful.
I made a second short function
def update_status(message):
label1.set(message)
and for the button in tkinter, used command=lambda:[update_status('loading'),search()] in hopes that the update_status() function would run first, alter the label, and then the second search() function that takes upwards of 30 seconds would run. But I get the same effect.
What's the simplest way finish running the update_status() function--thereby updating my label acting as the "status", and THEN run the time consuming search() function?
I'm not opposed to something more complicated like a loading window or something similar, but just wanted something simple (I have not even googled any type of loading window--I'm mostly hung up on how to get 2 functions to run on a button click in a sequential order).
Hey I do not think you need 2 functions to do what you want. You simply have to update your root so that the label is directly updated.
Here is an example:
import tkinter as tk
import time
def update_status(message1, message2):
var.set(message1)
root.update()
time.sleep(5)
var.set(message2)
if __name__ == '__main__':
root = tk.Tk()
root.title("Wait for function")
var = tk.StringVar()
var.set('Waiting for input')
label1 = tk.Label(root, textvariable=var)
label1.pack()
Button1 = tk.Button(root, text="Wait", command=lambda:update_status('loading', 'done'))
Button1.pack()
root.mainloop()

Tkinter .after() freezes window

I'm trying to make a script with tkinter that displays some elements one after each other (not all of them at the same time) using .after().
The problem I have is that all elements are displayed at the same time after the overall waiting time has finished (overall waiting time = sum of the individual time of each element).
I've checked many examples on the web, and I guess I'm using the .after() method wrong, but I just can't figure out why.
I would expect the following code to update the label wait for 0.5 seconds after each loop. Nonetheless, what I get is a total wait of 3 seconds, after which the tkinter window appears, showing the last element of the list.
import tkinter as tk
import random
grid = [1, 2, 3, 4, 5, 6]
root = tk.Tk()
canvas = tk.Canvas(root, width=200, height=200)
canvas.pack()
def refresh():
for i in range(len(grid)):
text = grid[i]
label.configure(text=text)
root.after(500)
label = tk.Label(canvas, text='0', font='Courier 18')
label.place(relx=0.45, rely=0.4)
refresh()
root.mainloop()
Thanks in advance!
Yes, it's quite normal, but you have to add an handler after the time delay. If you don't, the computer only freezes the program until the time runs out.
Try this:
root.after(500, handler)
And what does it does is you create a function for the handler and after the 500ms the program will call that function.

Changing Label with Tkinter while canvas/widget is open

I would like to change the value of label while the widget is open, so I actually see it change after some time.
I've been trying to use time.sleep but the first label won't show. And yeah I know that's because once the program runs the mainloop only takes the last value. Is it somehow possible to show me the first value and then wait 5 seconds and after that the label changes to something else. I've been searching for a solution. I didn't figure it out yet.
Try using root.after.
from Tkinter import *
root = Tk()
label = Label(root, text="this message will self-destruct in three seconds")
label.pack()
def bang():
label.config(text="this message has self-destructed.")
root.after(3000, bang)
root.mainloop()

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