Sunday Sep 28, 2008
Sunday Sep 28, 2008
I knew my best path to learning JavaFX and creating my countdown timer app would be to study one of the existing sample applications. The stopwatch example seemed perfect, as it contained basic timer logic (though counting up and not down) and had both a digital and clock-style display. First though, I stopped by javafx.com and downloaded the JavaFX Preview SDK so I'd have all the runtime components and NetBeans IDE. I then grabbed the stopwatch example, installed it as a project in NetBeans, and started examining the code.
The file structure of the project helped me get a feel for what I needed to do, though the terminology was unfamiliar. A short main program (Main.fx) created a "Frame" and populated it with a StopwatchWidget. Looking next at StopwatchWidget.fx I saw it created a customized "Node" with a "Model" called StopwatchModel and a "Theme" called LightTheme. StopwatchModel.fx contained all the timer logic and functions to start, stop, and reset the stopwatch. LightTheme.fx contained all the code to render the user interface - stopwatch face, hands, dial markings, start/stop buttons, etc.. It extended an overall "Theme", which I found in Theme.fx, not surprisingly and discovered there was an alternative DarkTheme in DarkTheme.fx.
I wanted to get started by reversing the timing function so the stopwatch counted down and not up, so dug into StopwatchModel.fx. Reversing the rotation of the timer hands was easy enough but that didn't mean I was counting down. However, since JavaFX lets me mix JavaFX Script and Java I could splice in the Java code from my previous countdown timer both to calculate milliseconds remaining until the target date/time and to extract from that values for the days, hours, minutes, and seconds remaining so I could update the digital display. In doing so I learned a few things:
var year:String = "2008";
var now:Integer = System.currentTimeMillis() as Integer;
At this point, though, I had converted my timer logic into JavaFX inside StopwatchModel to produce:
private attribute targetString:String = "2009-03-27-8-0"
private attribute targetMilliseconds:Number = 0;
private attribute timerListener:ActionListener = ActionListener {
public function actionPerformed(evt:ActionEvent): Void {
if (targetMilliseconds == 0) {
var formatter:SimpleDateFormat = new SimpleDateFormat("yyyy-M-d-k-m");
var date:Date = formatter.parse(targetString);
targetMilliseconds = date.getTime();
}
var now:Integer = System.currentTimeMillis() as Integer;
var nn:Number = System.currentTimeMillis() as Number;
var n:Integer = ((targetMilliseconds - nn)/1000) as Integer;
if (n > 0) {
var s:Integer = n mod 60;
var m:Integer = n / 60;
var h:Integer = m / 60;
var d:Integer = h / 24;
h = h mod 24;
m = m mod 60;
timeString = "{%03d d}-{%02d h}:{%02d m}.{%02d s}";
}
else {
timeString = "000-00:00.00";
}
}
}
This gave me the ability to count down properly and set the right value in the magical global timeString, but I clearly couldn't map this to the digital display of the stopwatch much less the moving hands. That meant serious modification to the user interface, which I'll cover in my next blog entry.