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.
Related
I have a very simple python code: a tkitner button that process some images in the background. I wanted to open a tkinter toplevel to show the user that it was doing something, but for my surprise is not working as I thought it would. The command on the tk.Button is the next method:
def processing(self):
"""Starts the images processing"""
# Open a Tk.Toplevel
aux_topLevel = Splash(self.window) # a simple Tk.Toplevel class, that works perfectly
self._process_images() # starts processing the images
# I wanted to kill here the topLevel created before
aux_topLevel.destroy()
My surprise: the window is displayed once the processing images is done (tried it out adding prints and time.sleep), however, i couldn't display the TopLevel when I wanted to.
Is there anything am I doing wrong? Any help is appreciated. Thank you.
Consider the following example and try to run it.
What you'd think should happen is that the new Toplevel window should open, some event happens for a period of time and then the window is destroyed.
What actually happens is the window is opened, but never displayed, the task occurs and then the window is destroyed.
from tkinter import *
import time
def processing():
new = Toplevel(root)
new.geometry("200x150")
lbl = Label(new,text="--")
lbl.grid()
for i in range(50):
time.sleep(0.1)
#Un-comment the line below to fix
#root.update()
print(i)
lbl['text'] = "{}".format(i)
new.destroy()
root = Tk()
root.geometry('200x100')
btnGo = Button(root,text="Go",command=processing)
btnGo.grid()
root.mainloop()
If you un-comment out the root.update() line and re-run the code, the window will be displayed.
There are better ways to deal with tasks that takes a while to process, such as threading.
Why i am getting this error with root.update() when i destroy the window:
_tkinter.TclError: invalid command name ".!label"
or
_tkinter.TclError: invalid command name ".!frame.!text"
here is an example code:
from tkinter import *
import random
r=Tk()
a=Label(r)
b=[1,2,3,4,5,6,7]
while(True):
a.configure(text=f'{random.choice(b)}')
a.pack()
r.update()
r.mainloop()
any way to fix this error?
The error is because you are running an infinite loop
After closing the window also loop is try to run the program and try to access the Tk() window, but it has been destroyed.
the solution is break the loop also when you close the window
try:
a.configure(text=f'{random.choice(b)}')
r.update()
except:
break
In above code program will try to access the window, if success it will run the program
Otherwise failed it will execute 'except' condition and break the loop
your full code will be
from tkinter import *
import random
r=Tk()
a=Label(r)
a.pack()
b=[1,2,3,4,5,6,7]
while(True):
try:
a.configure(text=f'{random.choice(b)}')
r.update()
except:
break
r.mainloop()
That, or similar, happens any time you close a tkinter window and there is an uncompleted action waiting to process. Frankly, it can be ignored...
..but, if you really want to get rid of it, you need to manage your ongoing processes and wait for them to finish when closing. You will need to overwrite the destroy() method in a customized version of the Tk class to have that, and you will probably be better off using after() if you want active processes going on with the program. The way you are doing it right now, it never hits the mainloop so some functionality will not work.
Here is a simple fix:
from tkinter import *
import random
class Globals:
stop = False
class MyTk(Tk):
def trueDestroy(self):
Tk.destroy(self)
def destroy(self):
Globals.stop = (self)
def myUpdate():
a.configure(text=f'{random.choice(b)}')
if Globals.stop:
r.trueDestroy()
else:
r.after(1, myUpdate)
r=MyTk()
a=Label(r)
a.pack()
b=[1,2,3,4,5,6,7]
r.after(1, myUpdate)
r.mainloop()
Really, you should make a queue for each process you have running instead of just one 'stop' function, and then only destroy it once they all are stopped, but hopefully you can extrapolate form this.
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()
I have been trying to set up a progress bar in a python tkinter gui that shows that a process is running. The process is long and I have no way to really measure the progress, so I need to use an indeterminate progress bar. However, I really dislike the style of the ttk indeterminate progress bar that bounces back and forth. I want one that scrolls across the bar over and over again, kind of like this image
Is this possible with tkinter?
have you tried ttk's determinate Progressbar? You can make the progress just continuously scroll across the bar.
for example:
#!/usr/bin/env python3
import tkinter
import tkinter.ttk as ttk
root = tkinter.Tk()
frame = ttk.Frame()
pb = ttk.Progressbar(frame, length=300, mode='determinate')
frame.pack()
pb.pack()
pb.start(25)
root.mainloop()
I know its an old question, but I have found a way to do this for anyone else writing tkinter.
I've been working on a tkinter app for a bit now and have determined that to handle tkinter objects, you absolutely need a separate thread. Although it is apparently frowned upon to handle tkinter objects via something else than the mainloop() method, it has been working well for me. I've never had a main thread is not in main loop error and never experienced objects that didn't update correctly.
I edited Corey Goldberg's code a bit and got it working. Here's what I got (some explanations in the comments).
import tkinter
import tkinter.ttk as ttk
import threading
def mainProgram(): # secure the main program initialization in its own def
root = tkinter.Tk()
frame = ttk.Frame()
# You need to use indeterminate mode to achieve this
pb = ttk.Progressbar(frame, length=300, mode='indeterminate')
frame.pack()
pb.pack()
# Create a thread for monitoring loading bar
# Note the passing of the loading bar as an argument
barThread = threading.Thread(target=keepLooping, args=(pb,))
# set thread as daemon (thread will die if parent is killed)
barThread.daemon=True
# Start thread, could also use root.after(50, barThread.start()) if desired
barThread.start()
pb.start(25)
root.mainloop()
def keepLooping(bar):
# Runs thread continuously (till parent dies due to daemon or is killed manually)
while 1:
"""
Here's the tricky part.
The loading bar's position (for any length) is between 0 and 100.
Its position is calculated as position = value % 100.
Resetting bar['value'] to 0 causes it to return to position 0,
but naturally the bar would keep incrementing forever till it dies.
It works, but is a bit unnatural.
"""
if bar['value']==100:
bar.config(value=0) # could also set it as bar['value']=0
if __name__=='__main__':
mainProgram()
I've added if __name__=='__main__': because I feel it defines the scope a bit better.
As a side note I've found that running threads with while 1: will crank my CPU at about 20-30% usage for that one thread in particular. It's easily solvable by importing time and using time.sleep(0.05) thereafter significantly lowering the CPU usage.
Tested on Win8.1, Python 3.5.0.
I am creating a little time management tool, using Tkinter, so I can keep on task at work. I am having trouble with one aspect that I cannot seem to get working. I'm using the error box so that it is displayed in front of all other windows.
As it is now, the program starts a new thread on a function that keeps track of time, and compares it to the time the user entered for their task. Once real time > the time entered by the user, it starts another thread to spawn the tkMessageBox. I have tried this without starting a new thread to spawn the tkMessageBox, and the problem is the same. If the user enters the same time for 2 separate tasks, the error pop up freezes. I'm having trouble finding information on this topic specifically... The behaviour is odd because if I have 2 alerts, lets say 1 at 0600 and one at 0601, but I do not close the first error box that pops up and let it stay up until the second alert triggers, the second alert will just replace the first one(I would like multiple error boxes to pop up if possible). It's only the alerts that have the same trigger time that cause the pop up to freeze though.
This is my first GUI program and only started learning the concept of threading, and GUIs in the past 24 hours, so I'm not sure if this is a problem with threading or the tkMessageBox. Because of the behaviour of the error box, I’m thinking it is the thread module combined with the tkMessageBox module. The command I'm using is:
tkMessageBox.showerror('TIMER ALERT!!!', comp_msg)
Here is the source I put comments in there to help. The tkMessageBox I’m talking about is line 56.
I guess I'm not sure if I can even do what I am trying to do with the pop-up box, if I can, I'm not sure how. If I can't, is there a alternative way to spawn multiple error type pop-up boxes with Tkinter? I just want multiple boxes to be able to appear at any given time.
Thanks in advance, and I really appreciate any help at all.
EDIT:
import thread
from Tkinter import *
#Spawns Error Box. Runs in it's own thread.
def message_box(comp_msg,q): # q is an empty string because of thread module.
print "Spawning Error Box..."
eb =Toplevel()
eb.config(master=None,bg="red")
pop_l = Label(eb,text="ALERT!!!")
pop_l2=Label(eb,text=comp_msg)
pop_l.pack(pady=10,padx=10)
pop_l2.pack(pady=15,padx=10)
return eb
thread.start_new_thread(message_box,(comp_msg,""))
tkmessageBox default dialog boxes are modal. You could implement a simple none modal dialog box for this application. Here is a good document about creating custom dialog boxes.
This way you can create as many new custom dialog boxes as your app requires, since each one is just a new Toplevel.
Here is a simple Tkinter app that shows the clock on the main window. When you click on the button it starts new tkMessageBox dialog boxes in new threads. (If you run it) You could see that the main thread that runs the TK event loop is working (since the time is getting updated), but the error boxes are not showing up as expected.
#!/usr/bin/env python
import datetime
import threading
from Tkinter import *
import tkMessageBox
class MyApp(Frame):
def __init__(self, root=None):
if not root:
root = Tk()
self.time_var = StringVar()
self.time_var.set('starting timer ...')
self.root = root
Frame.__init__(self, root)
self.init_widgets()
self.update_time()
def init_widgets(self):
self.label = Label(self.root, textvariable=self.time_var)
self.label.pack()
self.btn = Button(self.root, text='show error', command=self.spawn_errors)
self.btn.pack()
def update_time(self):
self.time_var.set( str(datetime.datetime.now()) )
self.root.after(1000, self.update_time)
def spawn_errors(self):
for i in range(3):
t = threading.Thread(target=self.show_error)
t.start()
def show_error(self):
now = datetime.datetime.now()
tkMessageBox.showerror('Error: %s' % (str(now)), now)
if __name__ == '__main__':
app = MyApp()
app.mainloop()