Group Abstract Group Abstract

Message Boards Message Boards

How to trigger multiple events with one action in Dynamic@Graphics

GROUPS:
I have this simple code for making an interactive graphic.
y = 1;
f[] := Do[y = 2; Pause[2]; y = 4, {1}];
DynamicModule[{pt = {0, 0}},
 Magnify[EventHandler[
   ClickPane[
    Dynamic@Graphics[
      Flatten[{Rectangle[{0, 0}, {y,
          1}]}]], (pt = #) &], {"MouseClicked" :> f[]}], 0.5]]

After the execution, I want that with one click on the square, I can see the square shrink to a rectangle of size (4,2) for two seconds, and then shrinks again to a rectangle of size (4,1), as indicated in the function f[].For the moment, the action of clicking on the square does trigger these events, but I can only see the final result after the two seconds have passed.

Thank you.
POSTED BY: Dominic Biron
Answer
10 months ago
I believe you only see the final result after f[] has fully evaluated because the "MouseClicked" event is using preemptive evaluation, which basically disallows the interrupting dynamic update you were expecting when y = 2 is evaluated.

You can use the Method option on some objects to set their evaluation type. The choice is generally (and I think always, to the best of my knowledge) either "Preemptive" or "Queued". There are a few details about these evaluation methods at ref/Button:
With the default setting Method -> "Preemptive", button actions are performed immediately, preempting any other evaluation, but are allocated only a limited time to complete.

With the setting Method -> "Queued", button actions are added to the current queue of evaluations, and are performed when other evaluations are complete. No time limit is applied.
With "Queued", an update of y during the action evaluation would trigger an update of visible dynamic objects depending on y. With "Preemptive", the action evaluation finishes before dynamic updates can occur.

For a quick demo, evaluate these three inputs and try the buttons. Watch the value of y shown by the Dynamic:
Dynamic@y

y = 1; Button["testing preemptive", y = 2; Pause@2; y = 3]

y = 1; Button["testing queued", y = 2; Pause@2; y = 3,
Method -> "Queued"]

If you're really interested and have already read the two essential Dynamic tutorials (Intro and Advanced), there are some interesting notes on the underlying dynamic mechanism here: http://reference.wolfram.com/mathematica/tutorial/SomeNotesOnInternalImplementation.html#473534038

I don't know if there is a way to force the "MouseClicked" event callback to use non-preemptive evaluation (if my guess about the cause of this behavior is in fact correct).
POSTED BY: William Rummler
Answer
10 months ago
You can use Clock inside Dynamic to make the value represented by y depend on the time.  It will auto-update inside the rectangle while the `Clock` is present.  Once a certain amount of time has elapsed, the code for y resets y to the constant value 1, which removes the dynamic Clock.  Dynamic updating stops until the next mouse click instantiates another Clock.
 DynamicModule[{pt = {0, 0}, t0, t1, y = 1},
  Magnify[
   ClickPane[
    Graphics[{
      Rectangle[{0, 0}, {Dynamic[y], 1}],
      Red, PointSize[Large], Dynamic@Point[pt]},
     PlotRange -> {{0, 4}, {0, 2}}, Frame -> True,
     FrameLabel -> Dynamic[{t0, t1}]],
    (
     pt = #;
     y = (                             (* set y to dynamic clock *)
      t0 = Null;                       (* flags start of clock *)
      Dynamic[
        t1 = Clock[Infinity];          (* Clock inside Dynamic triggers continual updating *)
        If[! NumericQ[t0], t0 = t1];   (* initialize t0 first time through *)
        If[t1 - t0 > 4,                (* stopping criterion *)
          y = 1,                       (* reset y to constant 1 *)
          If[t1 - t0 > 2, 4, 2]]       (* dynamic value of y while Clock is running *)
        ]
       )
     ) &], 0.5]]
POSTED BY: Michael Rogers
Answer
10 months ago
At a second glance, I realize FinishDynamic is exactly what's needed here. Add it to your definition of f, just after you set y to 2:
y = 1;
f[] := Do[y = 2; FinishDynamic[]; Pause[2]; y = 4, {1}];
DynamicModule[{pt = {0, 0}},
Magnify[EventHandler[
   ClickPane[
    Dynamic@Graphics[
      Flatten[{Rectangle[{0, 0}, {y,
          1}]}]], (pt = #) &], {"MouseClicked" :> f[]}], 0.5]]
POSTED BY: William Rummler
Answer
9 months ago
Thank you so much Mr Rummler for providing exactly the function that is needed here. I thought for a while it was impossible to evaluate events dynamically within a EventHandler action/function, but FinishDynamic[] did exactly what I wanted.

Thank you also Mr Rogers for letting us know more about the Clock option and its use.
POSTED BY: Dominic Biron
Answer
9 months ago