Friday, June 22, 2007

progress counters and dialogs

I recently had a colleague ask me how to pop up a progress bar dialog to indicate to a user the percentage complete of a process. I did not know how to answer that at first... Smallworld has historically not used progress bar dialogs as much as some users would expect. So I will present two options for showing progress status.

Text-based process

If you have some kind of text based process use progress_counter...


_block
_local some_collection << rope.new(20)

# create a progress_counter class that reports every 5
# increments. Because we know some_collection.size we can also
# estimate completion time
_local pc << progress_counter.new_on_count(5,some_collection.size)

_for idx,an_el _over some_collection.fast_keys_and_elements()
_loop
# the write() and sleep() statements are here to make a simple
# example...
write(an_el)
_thisthread.sleep(1000)

# ... but the :next() method is important to let the
# progress_counter (aka PC) know that you have completed one
# iteration of the loop.
pc.next()
_endloop
_endblock
$
unset
unset
unset
unset
unset
Done 5 (15 left) time taken 5 seconds 297 milliseconds (ETC. 15 seconds 891 milliseconds ) average rate 0.9439305267
unset
unset
unset
unset
unset
Done 10 (10 left) time taken 10 seconds 625 milliseconds (ETC. 10 seconds 625 milliseconds ) average rate 0.9411844826
unset
unset
unset
unset
unset
Done 15 (5 left) time taken 15 seconds 812 milliseconds (ETC. 5 seconds 270 milliseconds ) average rate 0.9487724325
unset
unset
unset
unset
unset
Done 20 (0 left) time taken 21 seconds 15 milliseconds (ETC. 0 milliseconds ) average rate 0.9562876356



... you can also ask the progress_counter to report at a given time interval...


_block
_local some_collection << rope.new(20)

# create a progress_counter class that reports every 2
# seconds. Because we know some_collection.size we can also
# estimate completion time
_local pc << progress_counter.new_on_time(3,some_collection.size)

_for idx,an_el _over some_collection.fast_keys_and_elements()
_loop
# the write() and sleep() statements are here to make a simple
# example...
write(an_el)
_thisthread.sleep(1000)

# ... but the :next() method is important to let the
# progress_counter (aka PC) know that you have completed one
# iteration of the loop.
pc.next()
_endloop
_endblock
$
unset
unset
unset
Done 3 (17 left) time taken 3 seconds 125 milliseconds (ETC. 17 seconds 708 milliseconds ) average rate 0.9600000000
unset
unset
unset
Done 6 (14 left) time taken 6 seconds 250 milliseconds (ETC. 14 seconds 583 milliseconds ) average rate 0.9600000000
unset
unset
unset
Done 9 (11 left) time taken 9 seconds 359 milliseconds (ETC. 11 seconds 439 milliseconds ) average rate 0.9616468318
unset
unset
unset
Done 12 (8 left) time taken 12 seconds 453 milliseconds (ETC. 8 seconds 291 milliseconds ) average rate 0.9648530373
unset
unset
unset
Done 15 (5 left) time taken 15 seconds 578 milliseconds (ETC. 5 seconds 199 milliseconds ) average rate 0.9616468318
unset
unset
unset
Done 18 (2 left) time taken 18 seconds 703 milliseconds (ETC. 2 seconds 81 milliseconds ) average rate 0.9612351238
unset
unset
Done 20 (0 left) time taken 20 seconds 765 milliseconds (ETC. 0 milliseconds ) average rate 0.9612351238



If you want to use a more user-friendly indicator, use the progress_indicator_dialog. It is loaded into the core 4.1 image and you can find details about it in the header of product/modules/sw_core/progress_manager/source/progress_indicator_dialog.magik


_block
_local q << progress_indicator_dialog.new ( "Generating" )
q.interrupt_handler << handler.new ( _unset, _unset )
q.info_string << "The current series will contain approx 10 frames."
q.bar_label << "Generating frames..."
q.image << { :help, _unset }
q.activate ()

q.max_count << 10

_for n _over range(1,10,1)
_loop
_thisthread.sleep(1000)

q.progress_changed(n)
_endloop
_endblock
$


And there you have it... easy ways to let your users know the progress of a process.

1 comment:

Robert Lilley said...

Alfred,
Interesting code and very useful for the users who are trigger-happy. I tried something like this the other day with a plugin that was running a trace to capture the downstream kva load.
So to do this, before I start looping over the circuits I'm about to trace, I call _self.install_progress_indicator() passing in the name and engine. The problem however is that when we're tracing out of say 3 circuits we don't know if one of the circuits is short and the other long. In situations like this how can a progress_indicator be used effectively to indicate the progress when the trace time will vary from one circuit to the next? The progress_indicator needs to know the increments and the max_count in order to show the progress. In this situation all I could indicate to the user was that one circuit was traced and then the next one, and finally the third. Not very effective.

I'm not sure that this was my problem but for the 'engine' parameter for the indicator I was passing in the plugin itself rather than the trace engine that I had stored in the plugin's slot. Could this have been the issue?

btw, love the Blog...Thanks.

Rob