"""
Demonstrates the use of the ProgressMeter class.
Author: Tucker Beck
Last Tested: 3/2/2009
Verified with: Python 2.6, Tkinter 8.4
"""
from __future__ import division
from Tkinter import *
from random import randint
from time import sleep
class ProgressMeter( Frame ):
"""
The ProgressMeter is-a Frame widget provides a progress bar and
accompanying information to a user regarding a long, computationaly
intensive process. A ProgressMetar can control any generator function
that returns string message or None after each iteration. Furthermore,
the ProgressMeter can interrupt the process at any time.
"""
def __init__( self, parent, height=30 ):
"""
Initializes this ProgressMeter
Arguments:
parent: The master widget for this ProgressMeter
height: The desired height of the progress bar
"""
self.parent = parent
Frame.__init__( self, parent )
self.columnconfigure( 0, weight=1 ) # Forces the canv object to resize any time this widget is resized
self.rowconfigure( 0, weight=1 )
self.statusMessage = 'Normal'
self.w = 0
self.h = 0
self.canv = Canvas( self, height=height) # This canvas will display the progress bar and accompanying percentage text
self.canv.grid( row=1, column=0, sticky=N+S+E+W )
self.canv.bind( '<Configure>', lambda e:
self.resize( e.width, e.height ) ) # When the canvas is resized the progress bar should be redrawn.
self.killVar = IntVar() # The killBtn can cancel execution
self.killVar.set( 0 )
self.killBtn = Button( self, text='Cancel',
command=lambda: self.killVar.set(1) )
self.killBtn.configure( state=DISABLED )
self.killBtn.grid( row=1, column=1 )
self.targetGen = None # Placekeeper for the generator function that will be metered
self.targetArgs = [] # Argument list for the generator function
self.targetKwds = {} # Keyword dictionary for the generator funciton
self.targetIdx = 0 # Keeps track of which step in iteration is currently being executed
self.targetLen = 0 # Total number of steps in exectuion
def resize( self, w, h ):
"""
Handles resize events for the canv widget. Adjusts the height and width
of the canvas for the progress bar calculations.
Arguments:
w: The new width
h: The new height
"""
self.w = w
self.h = h
self.canv.delete( 'frame' )
self.canv.create_rectangle( 1, 1, self.w, self.h, outline='black',
fill='gray75', tag='frame' )
def reset( self ):
"""
Resets the control values or the generator function and also clears the
progress bar
"""
self.canv.delete( 'bar' )
self.canv.delete( 'text' )
self.killBtn.configure( state=DISABLED )
self.targetGen = None
self.targetArgs = []
self.targetKwds = []
self.killVar.set( 0 )
self.targetIdx = 0
self.targetLen = 0
def clearStatus( self ):
""""
Clears the statusMessage member. Might be used by parent GUI that
reports child status.
"""
self.statusMessage = 'Normal'
def drawBar( self ):
"""
Updates the status bar for the percentage of completion.
"""
pct = self.targetIdx / self.targetLen # The percentage of completion
x0 = 2 # The bar is inset by 2 pixels
x1 = pct * ( self.w - 3 ) + 2
y0 = 2
y1 = self.h
self.canv.delete( 'bar' )
self.canv.create_rectangle( x0, y0, x1, y1, fill='SteelBlue3',
outline='', tag='bar' )
self.canv.delete( 'text' )
pctTxt = '%02.2f%%' % ( pct*100, )
self.canv.create_text( self.w/2, self.h/2, text=pctTxt,
anchor=CENTER, tag='text' )
def startGen( self, targetGen, targetLen, targetArgs=[], targetKwds={} ):
"""
Initializes the target generator function with supplied arguments and
keyword. Requests Tk to call iterGen after all idle events have been
handled.
Arguments:
targetGen: The target generator function
targetLen: The number of iterations in the target generator
targetArgs: The arguments for the generator function
targetKwds: The keyword arguments fo the generator function
Note:
Having iterGen called by Tk ensures that redraws and other sorts of
normal Tkinter events can be processed. Results in the status bar
updating real-time with execution while allowing the GUI to function
normally.
"""
self.targetGen = targetGen( *targetArgs, **targetKwds )
self.targetLen = targetLen
self.killBtn.configure( state=NORMAL )
self.after_idle( self.iterGen )
def iterGen( self ):
"""
Iterates through the target generator using delayed self referencing
funcition calls to allow GUI updates between iterations
"""
try:
msg = self.targetGen.next() # Execute the next iteration of the genrator
except StopIteration:
self.reset() # When the generator is finished, a StopIteration exception is raised. This signals a normal finish in the generator
self.statusMessage = 'Completed'
self.event_generate( '<<Finished>>' ) # A <<Finished>> virtual event signals the GUI that the progress meter is finished
return
self.targetIdx += 1
self.drawBar()
if msg == None:
pass
elif msg.startswith( 'AbortIteration' ): # The target generator can signal that something irrevocable has happend by yielding a value of 'AbortIteration'
self.reset()
self.statusMessage = msg
self.event_generate( '<<Finished>>' )
return
else:
self.statusMessage = msg # If the generator yields a value other than None or 'AbortIteration', this message will be sent out to the controlling gui
self.event_generate( '<<StatusRequest>>' )
if self.killVar.get() == 1: # Occurs if the user clicks the killBtn
self.reset()
self.statusMessage = 'Canceled'
self.event_generate( '<<Finished>>' )
return
self.update_idletasks()
self.after_idle( self.iterGen )
def dummy_gen( alices, bobs ):
"""
A simple, stupid example of a ProgressMeter iterable generator function
"""
for alice in alices:
for bob in bobs:
if bob==alice:
yield 'Match: %s==%s' % ( str(alice), str(bob) )
else:
yield 'No Match: %s!=%s' % ( str(alice), str(bob) )
def main():
root = Tk()
root.title( 'ProgressMeter Demo' )
pgress = ProgressMeter( root ) # Initialize the ProgressMeter with default arguments
pgress.grid( row=1 )
alices = range( 53 )
bobs = [ randint( 0,53 ) for i in range( 53 ) ]
btn = Button( root, text="Go!", command=lambda:
pgress.startGen( dummy_gen, len(alices) * len(bobs), [alices, bobs] ) )# Starts the ProgressMeter going when the button is clicked
btn.grid( row=0 )
statusVar = StringVar( root, 'None' )
status = Label( root, textvariable=statusVar )
status.grid( row=2 ) # This label will be used to display status messages from the ProgressMeter
root.bind( '<<StatusRequest>>', lambda event:
statusVar.set(pgress.statusMessage) )
root.bind( '<<Finished>>', lambda event:
statusVar.set( pgress.statusMessage ) )
root.mainloop()
if __name__=='__main__':
main()