Registering Multiple Actions (or Handlers) in JavaFX
Java developers, especially those performing any type of GUI work, will ultimately encounter Java's event-driven programming paradigm. In short, if programmers want to act upon some kind of event, they bundle up a chunk of code into a Java method, typically referred to as a handler, and register the handler with that event. Whenever that event occurs, the handler code will automatically be executed.
JavaFX provides a similar mechanism. For a straightforward example, the code below defines a simple timer in JavaFX with a resolution of 1 second. Each time a second expires, the function specified by the action instance variable will be executed. Here's what it looks like:
import javafx.animation.*; public class SimpleTimer { public def timeline = Timeline { repeatCount: 5 interpolate: false keyFrames: [ KeyFrame { time: 1s action: function () : Void { println("tick"); } } ] } }
Adding a run() function, as follows, to the bottom of this source will enable you run an instance of this timer:
function run() : Void { var s = SimpleTimer{}; s.timeline.playFromStart(); }
The output from this run looks like this:
tick tick tick tict tick
It's all well and good if you only need a single action. What if you wanted to perform multiple actions and/or dynamically add or subtract a number of actions? We can enhance our previous SimpleTimer class to dynamically register and unregister handlers by taking advantage of two of JavaFX's features: sequences and function pointers.
Our new class provides more flexibility:
- It defines an instance variable called duration, which enables the user to specify the resolution of a clock tick at object instantiation.
- It defines two additional public functions called registerHandler() and unRegisterHandler() which take a function pointer (a handler) as an argument. By registering a handler, the function will be included in the list of handlers to be executed each time the specified duration expires.
- A handler is registered by inserting it's function pointer argument into an internal sequence of function pointers called handlers[].
- A handler is similarly unregistered by deleting it's function pointer argument from the handlers[] sequence.
- The action instance variable, which is part of the KeyFrame instance, now calls an internal function called runHandlers(). runHandlers() sequentially executes the functions found in the handlers[] sequence.
Here's the new class:
import javafx.animation.*; public class Timer { /** * User-definable: specifies the length of time for one cycle. */ public var duration = 100ms; public def timeline = Timeline { repeatCount: Timeline.INDEFINITE interpolate: false keyFrames: [ KeyFrame { time: duration action: runHandlers } ] } // Holds the list of handlers to run protected var handlers: function() []; /** * Add the function, represented by the handler argument, to the list * of handlers. These will run when the elapsed time, specified * by the duration instance variable, expires. */ public function registerHandler(handler : function()) : Void { for (func in handlers) { if (handler == func) { return; // handler already registered -- skip } } insert handler into handlers; } /** * Remove the function, represented by the handler argument, from * the list of handlers. */ public function unRegisterHandler(handler : function()) : Void { delete handler from handlers; } protected function runHandlers() : Void { for (handler in handlers) { handler(); } } }
To test this class out, we'll add a run() function at the script level. The run() function creates a Timer instance and registers two handler functions, decrementTenthsRemaining() and processTicks(). Here's the code:
function run() : Void { var t = Timer {}; var tenthsRemaining = 100; var decrementTenthsRemaining = function() : Void { tenthsRemaining -= 1; } var processTick = function() : Void { if (tenthsRemaining mod 10 == 0) { println("seconds left: {tenthsRemaining / 10}"); } if (tenthsRemaining == 0) { t.timeline.stop(); } }; t.registerHandler(decrementTenthsRemaining); t.registerHandler(processTick); t.timeline.play(); }
And this is the output from the run:
seconds left: 9 seconds left: 8 seconds left: 7 seconds left: 6 seconds left: 5 seconds left: 4 seconds left: 3 seconds left: 2 seconds left: 1 seconds left: 0
Jim Connors, a long-time member of Sun’s system engineering community, has spent a decade helping customers leverage Java technologies ranging from Java Card and Java ME to Java EE and JavaFX. His new book, co-written with Jim Clarke and Eric Bruno, is JavaFX: Developing Rich Internet Applications (also available in Safari Books Online and as a downloadable eBook.
Editor's Note: This article was previously posted on Jim Connor's blog and is
Copyright 1994-2009 Sun Microsystems, Inc. Reprinted with permission.