Welcome, guest | Sign In | My Account | Store | Cart
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
          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.
          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
          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
          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
        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
            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
        self.targetIdx += 1
        if msg == None:
        elif msg.startswith( 'AbortIteration' ):                                # The target generator can signal that something irrevocable has happend by yielding a value of 'AbortIteration'
            self.statusMessage = msg
            self.event_generate( '<<Finished>>' )
            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.statusMessage = 'Canceled'
            self.event_generate( '<<Finished>>' )
        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) )
                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 ) )

if __name__=='__main__':