Scrolling Progress Bar in Tkinter - python

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.

Related

Correct way to run two Tk() mainloops independently, with second being started from first script?

script_a.py
from tkinter import *
from script_b import test
root = Tk()
var1 = stuff
var2 = stuff
def start_scriptb():
test([var1, var2])
Button(root, text="Start",
command=start_scriptb)
root.mainloop()
script_b.py
from tkinter import *
def test(x):
main = Tk()
Label(main, text=x)
Button(main, text="Exit", command=main.destroy())
main.mainloop()
This is a very basic version of what I'm trying to achieve. I actually am spawning a progress window that uses subprocess.Popen in script 2 with the passed through variables from script one and viewing the progess through a scrolled text widget on the 2nd program. I'm trying to spawn a new process, independent from the root GUI each time that button is hit from script_a.
It works fine in my tests so far, but I wanted to see if this could cause any issues or if this is actually spawning two processes?
I know there should only be one Tk() per process.
Using a TopLevel() window to show the progress works fine with the threading module, but the TopLevel() window will freeze as well as root (and any other open TopLevels()) if root is doing any sort of processing that takes any length of time.
With many hours of testing, I did have success in running two Tk() loops, but it had potential to be problematic, as "Bryan Oakley" had posted in many threads about.
Ultimately, I decided when I was in need of running something alone, I'd start my GUI with arguments and process it in an entirely new process instead of passing any arguments directly. Seems like a safer option.

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.

How can I spawn multiple tkMessageBox.showerror at the same time?

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()

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).

Creating Toplevel widgets that are thread safe

I'm trying to learn how to use the thread module. I followed along with the instructions here: http://effbot.org/zone/tkinter-threads.htm
My hope is the test script will:
Print out the "count" every two seconds
Show a pop-up dialog window (also every 2 seconds)
The pop-ups should be allowed to accumulate (if I don't click "OK" for a while, there should be
multiple pop-ups)
However, when I run this script it will freeze the main window and after a while crash. I think I'm not implementing the thread module correctly.
Could someone please have a look and point out what I'm doing wrong?
Here is what I've tried so far:
from Tkinter import *
import thread
import Queue
import time
class TestApp:
def __init__(self, parent):
self.super_Parent = parent
self.main_container = Frame(parent)
self.main_container.pack()
self.top_frame = Frame(self.main_container)
self.top_frame.pack(side=TOP)
self.bottom_frame = Frame(self.main_container)
self.bottom_frame.pack(side=TOP)
self.text_box = Text(self.top_frame)
self.text_box.config(height=20, width=20)
self.text_box.pack()
self.queue = Queue.Queue()
self.update_me()
def show_popup(self):
self.my_popup = Toplevel(self.main_container)
self.my_popup.geometry('100x100')
self.popup_label = Label(self.my_popup, text="Hello!")
self.popup_label.pack(side=TOP)
self.pop_button = Button(self.my_popup, text="OK", command=self.my_popup.destroy)
self.pop_button.pack(side=TOP)
def write(self, line):
self.queue.put(line)
def update_me(self):
try:
while 1:
line = self.queue.get_nowait()
if line is None:
self.text_box.delete(1.0, END)
else:
self.text_box.insert(END, str(line))
self.text_box.see(END)
self.text_box.update_idletasks()
except Queue.Empty:
pass
self.text_box.after(100, self.update_me)
def pipeToWidget(input, widget):
widget.write(input)
def start_thread():
thread.start_new(start_test, (widget,))
def start_test(widget):
count = 0
while True:
pipeToWidget(str(count) + "\n", widget)
count += 1
time.sleep(2)
widget.show_popup()
root = Tk()
widget = TestApp(root)
start_button = Button(widget.bottom_frame, command=start_thread)
start_button.configure(text="Start Test")
start_button.pack(side=LEFT)
root.title("Testing Thread Module")
root.mainloop()
I can't reproduce your problem, but I can see why it would happen.
You're using the queue to pass messages from the background thread to the main thread for updating text_box, which is correct. But you're also calling widget.show_popup() from the background thread, which means it creates and displays a new Toplevel in the background thread. That's not correct.
All UI code must run in the same thread—not all UI code for each top-level window, all UI code period. On some platforms, you may get away with running each window in its own thread (or even free-threading everything), but that isn't supposed to work, and definitely will crash or do improper things on some platforms. (Also, that single UI thread has to be the initial thread on some platforms, but that isn't relevant here.)
So, to fix this, you need to do the same dance for creating the popups that you do for updating the textbox.
The obvious way to do that is to move the widget.show_popup() to the loop in update_me(). If you want it to happen 2 seconds after the textbox updates, just add self.top_frame.after(2000, self.show_popup) to the method.
But I'm guessing you're trying to teach yourself how to have multiple independent updating mechanisms, so telling you "just use a single update queue for everything" may not be a good answer. In that case, just create two queues, and a separate update method servicing each queue. Then, do your pipeToWidget, sleep 2 seconds, then pipeToPopup.
Another way around this is to use mtTkinter. It basically does exactly what you're doing, but makes it automatic, pushing each actual Tk GUI call onto a queue to be run later by the main loop. Of course your objects themselves have to be thread-safe, and this also means that you have to deal with the GUI calls from one thread getting interleaved with calls from another thread. But as long as neither of those is a problem (and they don't seem to be in your case), it's like magic.
If you want to know why this is freezing and/or crashing for you on Win7 and not for me on OS X 10.8… well, you really need to look into a mess of Tcl, C, and Python code, and also at how each thing is built. And, unless it's something simple (like your Tk build isn't free-threaded), it wouldn't tell you much anyway. The code isn't supposed to work, and if it seems to work for me… that probably just means it would work every time until the most important demo of my career, at which point it would fail.

Categories

Resources