For some experiments I need some basic timer function. I tried various methods, and neither of them are fully satisfactory. What I need is a button, that starts/stops a timer, resets timer with each restart, updates some other variables as well besides the timer and has a trigger effect when started/stopped too. It should also be customizable for having any text on the button.
Let's see how Trigger behaves. First, define a switch that tests whether the counter is active or not, a total time of 50, and continuous variables for the running time and timeLeft:
active = False;
time = 0;
total = timeLeft = 50;
The Trigger should switch active and update time and timeLeft:
Trigger[Dynamic[time, (time = #; timeLeft = total - #;
active = 0 < # < total) &], {0, total}]
Dynamic@Column@{time, timeLeft, active}
This however has the following issues:
- There seems to be no way to stop the timer other than the
Trigger's own pause button. - The way
activeis set is wasteful, it is unnecessary to test for it every timestep. Sadly, no other way I could figure out how to call a function only once when theTriggeris started or stopped. I've tried to play with the start/end arguments (as inDynamic[var, {start, update, end}]) with no success. Perhaps layering a button above the trigger? See next issue. - No label can be given for the button. The option
Appearancedoes not seem to have any effect here, and even if I remove all buttons viaAppearanceElements->{}, I cannot overlay a normal button over it and wrap it in anEventHandlerthat passes events down to the trigger, as the Trigger object cannot be set to have the sameImageSizeas theButton:
.
EventHandler[Overlay[{
Button[Dynamic@If[active, "Stop", "Start"]],
Trigger[
Dynamic[time, (time = #; timeLeft = total - #) &], {0, total},
AppearanceElements -> {"TriggerButton"(*,"PauseButton",
"ResetButton"*)}]
}],
{"MouseClicked" :> (active = ! active)}, PassEventsDown -> True]
Dynamic@Column@{time, timeLeft, active}

I also tried to construct my own clock function, as follows. It uses a global clock, that starts immediately when the cell is generated, thus it always runs in the background, and the actual stopwatch is created by subtracting the start time (the moment the button is pushed) from the global clock.
active = False;
total = 50;
start = end = time = 0;
Column@{
Dynamic[globalTime = Clock[{0, total}]; time = globalTime - start;
globalTime],
Dynamic@If[active, time, end - start],
Dynamic@Button[If[active, "Stop", "Start"],
If[active, end = globalTime, start = globalTime];
active = ! active]
}

(Note that the actual dynamic value of globalTime (upper value) is not copied correctly via Szabolcs's otherwise magnificent image upload palette, and it shows less than time (lower value).)
However, It has the annoying feature that if globalTime does not appear on screen, no dynamic updating is done. Thus if I want to hide it, I have to wrap it into Style[..., Opacity[0]]. Also, time is constantly (and unnecessarily) updated, even if the button was not pushed.
Question
Is there any reliable built-in way to provide such a functionality? How would you solve this problem?
I am interested in any idea, as for the last 3 months, I had to construct 3 guis with three different timing functions, and there is always something new (request/issue/feature) I have to deal with.
RunScheduledTask[time = timer["now"], 0.01]? – István Zachar Apr 13 '12 at 13:07timer["now"]. These haveAbsoluteTimeresolution and can be called at normal function speeds. So you can call it hundreds or thousands of time each second. – image_doctor Apr 13 '12 at 13:13AbsoluteTimeis indeed a clever idea, though this approach has one major problem. It does not return time in the expected resolution: if I define the scheduled task to be updated at each second, it returns the following times: {1.0110578, 2.0121151, 3.0131723, 4.0142296, 5.0152868, 6.0163441}, so there is a lag. – István Zachar Apr 13 '12 at 13:28Clock(and related) seems to be good in that, it has all the issues I described in my Q. – István Zachar Apr 13 '12 at 13:44makeTimer, it seemed to reflect the same degree of accuracy. My feeling is thatmakeTimeris of the same order of accuracy asAbsoluteTime[]or perhapsPause. Is error of the order or about 0.0003 seconds per sample period within your criteria? Maybe there are other ways of approaching interval timing in Mathematica. – image_doctor Apr 13 '12 at 13:59timerinterface. You can put whatever text you like on the buttons in the grid. And any actions you want to trigger can be linked to the buttons. – image_doctor Apr 13 '12 at 14:28list = {}; start = AbsoluteTime[]; RunScheduledTask[time = AbsoluteTime[] - start; AppendTo[list, time], {.1, 5}]; Differences[list]evaluating at every .1 sec for 5 times. It returns the following successive time intervals: {0.1248002, 0.0936001, 0.0936002, 0.1248002}. Clearly, it is rather not accurate. – István Zachar Apr 13 '12 at 17:49Pause, this seems to be accurate to a few hundred microseconds (~0.0002s), I suspect that if you investigate itClock[]will have a similar degree of error/accuracy, depending on how you view it. – image_doctor Apr 13 '12 at 18:11{Mean[Differences[ts]], Variance[Differences[ts]]^(1/2)} -> {0.100517, 0.005003}. This seems to be at the accuracy level of several hundred microseconds, which matches the results forPauseand probably forClock. Which I suspect might be the limit of Mathematica's capabilities in this respect. – image_doctor Apr 13 '12 at 18:50TriggerorClockobjects. Thus I ended up using your approach, and I'm quite satisfied with it! Thanks. – István Zachar Apr 14 '12 at 23:34