Version 4 of Indeterminate Progress Bar with Tile

Updated 2004-10-27 12:01:21 by lwv

Kevin Walzer: One of the coolest things about the Tile extension is its support for a native progressbar. This makes it easy to set up visual cues for tasks that take a fixed amount of time, such as looping through a file directory. However, with a little tweaking, it's also possible to set up a good visual cue for a task that is ongoing, or an "indeterminate" progressbar--something that isn't natively supported anywhere in Tk. (I'm thinking here about a "barber pole" progressbar, such as seen in Mozilla or on OS X.)

I wanted to set up an intdeterminate progress bar for an application I'm developing, and here's what I came up with:

http://www.wordtech-software.com/progress.jpg

This uses a pop-up window that runs the progress bar, and which can be easily closed by the user if the progress bar is a distraction.

This widget is built on several components. Let's walk through them.

The first snippet, a variation on the "every" procedure, was provided by Brian Griffin on the Tile developer's list in response to a question from me. This sets up the increments of time that the widget can be updated.

   proc every {ms body} {
      global everyId
      if {$ms eq "cancel"} {
         if {[info exists everyId($body)]} {
             after cancel $everyId($body)
             unset everyId($body)
         }
      } else {
         if {[info exists everyId($body)]} {
            # This makes sure there is only 1 every for this body
            after cancel $everyId($body)
         }
         set everyId($body) [after $ms [info level 0]]
         uplevel #0 $body
      }
 }

The second snippet, "Update," was provided by Pat Thoyts, also on the Tile developer's list, sets up the numerical logic for the progress widget:

   proc Update {w interval} {
    #global w interval

     set v [expr {[$w get] + $interval}] 
  if {$v > [$w cget -to]} {
      set v [$w cget -from]
  }  
     $w set $v

 }

The third snippet, "reset," sets up the "after cancel" call to close the progress bar loop:

 proc reset {} {
 foreach id [after info] {after cancel $id}
 }

And finally, I put all these procedures and call them from this "Show Progress" procedure, developed with assistance from Pat Thoyts, which can then be called from a menu or button command:

 proc ShowProgress {} {
    toplevel .progress
    tbutton .progress.button -width 10 -text close -command "reset; destroy .progress"  -highlightbackground gray97
   pack .progress.button -side right -fill x
    tlabel .progress.text -textvariable show
   pack .progress.text -side top -fill both
   pack [tprogress .progress.show -from 0 -to 10] -fill both -side bottom
  .progress.show set 0
    grab .progress
   wm title .progress "TkDarwinPorts Progress"
   every 250 {Update .progress.show 1}

 }

One caveat: it doesn't seem possible to directly tie this indeterminate progressbar to the execution of a specific procedure within the Tk event loop--i.e., call the ShowProgress procedure and then terminate it when the parent procedure has closed. When I tried that, the progressbar loop would go on and on even after the parent procedure had finished. Setting up the second top-level window with the progressbar and then calling "reset," and then destroying the window with the "close" button, is the most effective solution. While it doesn't exit on the closure of the parent procedure, it is cleanly terminated when the end user clicks on the button.


Category Widget