Creating a Digital Clock

In this tutorial, you will learn how to program the following digital clock:


The logic for this program can be divided into two main sections: nongraphical code (to track the current time), and GUI code (to render that time on screen as LED digits). The following step-by-step instructions contain technical discussions, complete source code listings, and screenshots of the expected output.

1) Create the DigitalClock Project


To create the DigitalClock project within NetBeans IDE:

  1. Click the new project icon (or choose New Project from the File menu).
  2. Choose JavaFX from the Category menu.
  3. Choose JavaFX Script Application from the Projects menu.
  4. Click Next.
  5. Type DigitalClock as the project name.
  6. Click Finish.

These steps create the Main.fx source file in the digitalclock package. Delete any autogenerated code, leaving only the package statement at the top of the file:

Source Code
package digitalclock;

2) Get the Current Time


To get the current time, import the javafx.date.DateTime class, create a DateTime object, then read the value of its instant variable:
Source Code
package digitalclock;

import javafx.date.DateTime;

def dt = DateTime{}.instant;

println(dt);

A DateTime object represents a specific instant in time, with millisecond precision (for example, 1263764554296). Its instant variable represents the exact time at which the DateTime object was created.

3) Format the Current Time


Next, convert that time into hh:mm:ss (hour, minute, second) format. The conversion is most easily accomplished by using the SimpleDateFormat class from the Java programming language:
Source Code
package digitalclock;

import javafx.date.DateTime;
import java.text.SimpleDateFormat;

// Get the current date and time from within JavaFX
def dt = DateTime{}.instant;
println(dt);

// Format date and time with Java
def hh = new SimpleDateFormat("hh").format(dt);
def mm = new SimpleDateFormat("mm").format(dt);
def ss = new SimpleDateFormat("ss").format(dt);

println("hh:{hh} mm:{mm} ss:{ss}");

The output of this program will now be much more readable (for example, hh:04 mm:52 ss:09). Note that the hour, minute, and second data have been stored in their own defs for later access.

4) Download the LED Images


The LED digits for this clock are provided as .png images. Save all 11 images in the same directory as your .fx source code.

                     

Using images mimics real-world workflow where a designer would create the graphics, and you (the developer) would focus only on the clock's logic.

5) Create an Image Sequence


Next, import the Image class and load the image data into a sequence named images:
Source Code
package digitalclock;

import javafx.date.DateTime;
import java.text.SimpleDateFormat;
import javafx.scene.image.Image;

// Get the current date and time from within JavaFX
def dt = DateTime{}.instant;
println(dt);

// Format date and time with Java
def hh = new SimpleDateFormat("hh").format(dt);
def mm = new SimpleDateFormat("mm").format(dt);
def ss = new SimpleDateFormat("ss").format(dt);

println("hh:{hh} mm:{mm} ss:{ss}");
 
// Load 11 images total (nums 0-9 plus dots image)
def images:Image[] = for(i in [0..10]){Image {url: "{__DIR__}{i}.png"};}

As you complete this step, remember that the Image class simply stores the image data. It is not responsible for displaying the images. (To render an image, you would use ImageView, as discussed in Step 6 below.)

The code marked in red can be analyzed as follows:

  1. def images:Image[] = for(i in [0..10]){Image {url: "{__DIR__}{i}.png"};} declares a sequence -- named images -- that will hold the individual Image objects.

  2. def images:Image[] = for(i in [0..10]){Image {url: "{__DIR__}{i}.png"};} creates a for loop that iterates 11 times (10 iterations for the LED digits 0 through 9, and one iteration for the LED colon image).

  3. def images:Image[] = for(i in [0..10]){Image {url: "{__DIR__}{i}.png"};} uses an object literal to create one Image object per loop iteration. Because this loop iterates 11 times, a total of 11 Image objects will be returned.

6) Display Static Images


Now create the main application frame and statically display the LED images. You can accomplish this goal by completing the following tasks:

  1. Creating the Stage and Scene objects.
  2. Adding a Flow object to the Scene's content.
  3. Adding ImageView objects to the Flow object's content.

Source Code
package digitalclock;
 
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.Image; 
import javafx.scene.image.ImageView;
import javafx.scene.layout.Flow;
import javafx.date.DateTime;
import java.text.SimpleDateFormat;
 
// Get the current date and time from within JavaFX
def dt = DateTime{}.instant;
 
// Format date and time with Java
def hh = new SimpleDateFormat("hh").format(dt);
def mm = new SimpleDateFormat("mm").format(dt);
def ss = new SimpleDateFormat("ss").format(dt);

// Load 11 images total (nums 0-9 plus dots image)
def images:Image[] = for(i in [0..10]){Image {url: "{__DIR__}{i}.png"};}

Stage {
    title: "Digital Clock"
    scene: Scene {
        fill: Color.BLACK
        width: 374
        height: 93
        content: Flow {
            hgap: 8
            content:
                [ImageView{image: images[0]}, ImageView{image: images[0]}, 
                 ImageView{image: images[10]}, // LED dots
                 ImageView{image: images[0]}, ImageView{image: images[0]}, 
                 ImageView{image: images[10]}, // LED dots
                 ImageView{image: images[0]}, ImageView{image: images[0]}] 
        }
    }
}


Adding the Stage and Scene is standard practice--something you will do in every GUI program. The Flow layout ensures that GUI objects will be arranged from left to right as they are added to the scene's content. Within that Flow object, there is another content, which is where the LEDs (instances of the ImageView class) are added. The Flow object's hgap variable controls the spacing between the images (8 pixels in this case).

The output minus window decorations should look like the following:

7) Set the Current Time


By now you have a program that determines the current time and draws static LED images to the screen. The next step is to hook everything together to make the LEDs reflect the current time:

Source Code
package digitalclock;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Flow;
import javafx.date.DateTime;
import java.text.SimpleDateFormat;

def images: Image[] = for (i in [0..10]) {
            Image {url: "{__DIR__}{i}.png"};
        }

/* This image sequence is the binding target.
 * Assigning to this sequence will change
 * the time shown on the clock. 
 */
var currImgs: Image[];

updateClock();

insert images[10] into currImgs;

Stage {
    title: "Digital Clock"
    scene: Scene {
        fill: Color.BLACK
        width: 374
        height: 93
        content: Flow {
            hgap: 8
            content: [ImageView {image: bind currImgs[0]}, 
                      ImageView {image: bind currImgs[1]}, 
                      ImageView {image: bind currImgs[6]}, // LED dots 
                      ImageView {image: bind currImgs[2]}, 
                      ImageView {image: bind currImgs[3]}, 
                      ImageView {image: bind currImgs[6]}, // LED dots
                      ImageView {image: bind currImgs[4]}, 
                      ImageView {image: bind currImgs[5]}] 
        }
    }
}

function updateClock() {
    // Get the current date and time from within JavaFX
    var dt = DateTime{}.instant;

    /*
     * Get the two-digit hour, minute, and second as a String.
     * For this, we will use Java's SimpleDateFormat class.
     */
    def hh = new SimpleDateFormat("hh").format(dt);
    def mm = new SimpleDateFormat("mm").format(dt);
    def ss = new SimpleDateFormat("ss").format(dt);

    // Finally, map strings to images
    currImgs[0] = images[Integer.parseInt(hh.substring(0, 1))];
    currImgs[1] = images[Integer.parseInt(hh.substring(1))];
    currImgs[2] = images[Integer.parseInt(mm.substring(0, 1))];
    currImgs[3] = images[Integer.parseInt(mm.substring(1))];
    currImgs[4] = images[Integer.parseInt(ss.substring(0, 1))];
    currImgs[5] = images[Integer.parseInt(ss.substring(1))];
}

In this step, a new Image sequence named currImgs has been added. This sequence holds the images that will appear on screen at any given time. Unlike the previous images sequence (a def sequence containing all image data for the entire program), this sequence is declared as a var so that it can be freely assigned to.

By binding the Scene's ImageView objects to the images of the currImgs sequence, the GUI will now automatically update itself after each assignment. For example, the code currImgs[0] = images[0] would set the GUI's first LED digit to the number 0. Recall that the current time is already stored in the script as two-digit String objects (variables hh, mm, and ss). By converting those values from String to Integer, you can obtain the appropriate index value to make the clock display the correct time.

Note that in this version the code for obtaining the time has been moved to a separate script-level updateClock() function.

8) Create an Animation Timeline


Finally, you need to create an animation timeline that invokes the updateClock() function every second. Use the javafx.animation.Timeline class to create an infinite timeline that invokes updateClock() at one-second intervals:

Source Code
package digitalclock;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Flow;
import javafx.date.DateTime;
import java.text.SimpleDateFormat;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;

def images: Image[] = for (i in [0..10]) {
            Image {url: "{__DIR__}{i}.png"};
        }

/* This image sequence is the binding target.
* Assigning to this sequence will change
* the time shown on the clock.
*/
var currImgs: Image[]; 

updateClock();

insert images[10] into currImgs;

// Start the animation timeline, updating the clock every 1s
Timeline{ 
    repeatCount: Timeline.INDEFINITE 
    keyFrames: KeyFrame {
                   time: 1s 
                   action: function(){
                               updateClock();
                           }
               }
}.play();


Stage {
    title: "Digital Clock"
    scene: Scene {
        fill: Color.BLACK
        width: 374
        height: 93
        content: Flow {
            hgap: 8
            content: [ImageView {image: bind currImgs[0]}, 
                      ImageView {image: bind currImgs[1]}, 
                      ImageView {image: bind currImgs[6]}, // LED dots
                      ImageView {image: bind currImgs[2]}, 
                      ImageView {image: bind currImgs[3]}, 
                      ImageView {image: bind currImgs[6]}, // LED dots
                      ImageView {image: bind currImgs[4]}, 
                      ImageView {image: bind currImgs[5]}] 
        }
    }
}

function updateClock() {
    // Get the current date and time from within JavaFX
    var dt = DateTime {}.instant;

    /*
     * Get the two-digit hour, minute, and second as a String.
     * For this, we will use Java's SimpleDateFormat class.
     */
    def hh = new SimpleDateFormat("hh").format(dt);
    def mm = new SimpleDateFormat("mm").format(dt);
    def ss = new SimpleDateFormat("ss").format(dt);

    // Finally, map strings to images
    currImgs[0] = images[Integer.parseInt(hh.substring(0, 1))];
    currImgs[1] = images[Integer.parseInt(hh.substring(1))];
    currImgs[2] = images[Integer.parseInt(mm.substring(0, 1))];
    currImgs[3] = images[Integer.parseInt(mm.substring(1))];
    currImgs[4] = images[Integer.parseInt(ss.substring(0, 1))];
    currImgs[5] = images[Integer.parseInt(ss.substring(1))];
}

The LED clock application is now complete. You can replace the LED digits with images of your own design to change its look and feel.