Indeterminate Progress Bar with Tile

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

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 command, a variation on every, 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 [namespace code [info level 0]]]
       uplevel #0 $body
    }
}

The second command, 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 command, 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.


MAK 2005-02-19: Actual barber poles ought to be possible with patch [L1 ], which implements phased progress bar support. With patch the Aqua progress bar still looks the same but you get the "moving bubbles" effect that you often see in other applications. I suspect you could do your own progress bar theme with a phased barber pole graphic, and then not have to do anything to keep it animated beyond choosing the right phase graphic to display when told to. (Though, it might have to be a theme in C.)