I've a ttk::progressbar
in my toplevel (the one and only .
) which I update via the following function:
proc progress {x} {
global prog
set prog [expr fmod(($prog +$x),100)]
update idletasks
}
prog
is the variable bound to the progressbar via the -variable
option.
Everything works fine if I keep the focus on my window. If I switch to another window the progressbar stops to be updated and it doesn't restart even if I switch back to my application.
I'm using tcl/tk 8.6 on Windows 7 (just in case it might be relevant).
What could be the cause of this behaviour? Have I missed something on how to update a progressbar?
EDIT
It seems that a full update
does the trick, so the question would be why this is the case and if there's any way to avoid a full update refresh.
When you update the value of a progress bar widget (or, generally, the value or configuration of any widget) Tk creates an internal idle* event to do the redrawing; the idea is that if you update multiple things in a row in response to a sequence of events (a very common thing!) then you only get one redraw of the widget. This works really well most of the time, but not when you're doing some kind of busy animation loop.
Doing an update idletasks
makes any scheduled idle events happen immediately; the internally-generated redraw gets processed at that point.
But that's not all. There are also externally-generated redraws that come from the outside world (i.e., the host windowing system) via real events: on Windows, WM_PAINT
, on X11, Expose
, on OSX, …, well it's complicated on OSX. (Ignore that platform for the sake of argument.) Because these external events come from the outside world, they're only noticed by a full run of the event loop — the low-level event processing system doesn't know that they're requested redraws until after they're received — which means using the main event loop, or a subsidiary one as started by vwait
, a full update
, or tkwait
. (In fact, the way that those external redraw events are handled is by scheduling a redraw in an idle event because there might be other events, like a widget resize or a focus change, that also need to be handled at the same time and which need a redraw as well.)
The recommended way of handling this is to split your code that is doing the long-running processing up so that you can return to the event loop periodically and allow it to process any pending external events. In 8.5 and before, you do this by using after $aFewMilliseconds doTheNextBit
from time to time; this requires structuring your code so that it works in continuation-passing style, which can be a bit painful. On the other hand, in 8.6 you can put your long-running code inside a coroutine and do the stop-for-a-bit with after $aFewMilliseconds [info coroutine];yield
without significantly breaking the flow of your code.
Less recommended is to sprinkle in an update
. This has the problem that it starts a subsidiary event loop, which can cause problems with stack exhaustion if you try to reenter any long-running processing. It's possible to make this work with appropriate interlocking (e.g., disabling buttons that could otherwise cause problems while you're doing long processing) but that's genuinely more tricky. You could also consider farming off the processing to a separate non-GUI thread** that just sends messages back to the main GUI thread from time to time to cause the progress bar to change. (Tcl treats inter-thread messages as events, BTW.) Going multithreaded might or might not make sense in your case.
* You can make your own idle events with after idle
but you don't usually need them.
** True multi-threaded GUIs are pretty crazy, and Tk doesn't work that way anyway. Each thread with Tk has its complete own copy.