Planet JFX
Register
Advertisement

Motivation / Goal

Just looking at JavaFX code doesn't get you too far in terms of understanding it. Let's create a little problem for us to solve in JavaFX. Going through the steps one-by-one is really the only way to learn.

Something simple. How about a calculator? A text field, some buttons with actions, we're all good. Oh, wait, I'm way to lazy for that. Let's scale it back to something like a calculator. How about, a frame with a text field, which is not editble. There will be buttons to add characters to the text field, or clear it out. Upon every textfield change, let's print the old and new text value.

Pretty simple, pretty useless. But it should hit upon several JavaFX features. Afterwards, we will make an equivalent swing version.

Building the JavaFX application

Let's make an attempt at the JavaFX version with SDK 1.0.

You will want to at least follow the Getting Started tutorial over on OpenJFX to the point of getting your IDE set up. Eclipse set-up is largely the same as Netbeans, so the instructions there still apply.

Hello World

We'll start with the Hello World example on that page:

HelloWorld.fx
Code Preview
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;;

Stage {
    title: "Hello World JavaFX"
    width: 200
    height: 50
    scene: Scene {
        content: Text {
           font : Font {
               size : 12
           }
           x: 10, y : 30
           content: "Hello World"
        }
    }
}

Preview

Adding a text field

So, there are a good amount of changes needed to reach our goal. We'll need a Swing Component Textfield, one that is not editible:

early.fx
Code Preview
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.ext.swing.SwingTextField;


Stage {
    title: "Pat's JavaFX Attempt"
    width: 200
    height: 100
    scene: Scene {
        content: SwingTextField {
              columns: 100
              height:100
              width: 200
              text: "Hello, Text"
              editable: false
           }
    }
}
Preview

Notice that I have introduced a a Swing Component TextField where I made the editable property false.

Multiple Components

We need to add buttons, so we will have multiple components in this frame. Traditional GUI programming would have us set up layout managers to accomplish this. For this simplistic example, we can use Swing Components, SwingButton, SwingTextField.

In JavaFX SDK 1.0, there are some convenience classes that help us with this:layout, in the javafx.scene package. In layout, HBox - create a horizontal box container and VBox - Vertical Box container and as the names suggest content i.e. buttons are either aligned horizonality or vertically according to the selected choice.

AllWidgets.fx
Code Preview
import javafx.ext.swing.SwingButton;
import javafx.ext.swing.SwingTextField;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.stage.Stage;


Stage {
    title: "Pat's JavaFX Attempt"
    width: 250
    height: 100    
    scene: Scene {
        content : HBox {
            content: [
                SwingTextField {
                    columns: 10
                    width : 100
                    text: "Hello, Text"
                    editable: false
                },

                    SwingButton { // swing button A
                    text : "a"
                },

                    SwingButton { // swing button B
                    text : "b"
                },
                    SwingButton { // swing button Clear
                    text : "Clear"

                },

            ]
        }
    }
Screen2

We simply use an array of components, denoted by the '[' and ']' symbols. Each component is seperated by commas, as in Java. Note that, also like Java, the final component can have a trailing comma, which is ignored (see tag note2).

Adding Behavior

We want something to happen in response to button clicks, namely that they change the value of the textfield. To get to that point, we need to add the equivalent of ActionListeners to our buttons.

From this point on, the preview of the GUI stays the same, so I will not be including them. Also, you must have the Java Console visible, or be running JavaFX from the command line in order to see the System.out.println results.

behavior.fx
Code
import java.lang.System; //note1
import javafx.ext.swing.SwingButton;
import javafx.ext.swing.SwingTextField;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.stage.Stage;


Stage {
    title: "Pat's JavaFX Attempt"
    width: 250
    height: 100
    scene: Scene {
        content: HBox {
            content: [
                SwingTextField {
                    columns: 10
                    width: 100
                    text: "Hello, Text"
                    editable: false
                },

                SwingButton { // swing button A
                    text: "a"
                    action: function(){
                        System.out.println("'a' clicked");
                    }
                },

                SwingButton { // swing button B
                    text: "b"
                    action: function(){
                        System.out.println("'b' clicked");
                    }
                },
                SwingButton { // swing button Clear
                    text: "Clear"
                    action: function(){
                        System.out.println("'clear' clicked");
                    }
                },

            ]
        }
    }
}

Adding an ActionListener is pretty straightforward. Define an function() that does what you want. However, what we want is to change the text in the textfield, and we can't quite get there just yet.

Also note the import statement tagged with note1... almost every class must be imported explicitly. There is no freebie import java.lang.* as there is when coding Java.

Binding a data model to a view

(For a more detailed tutorial on binding, see Introduction to Binding in JavaFX‎.)

In JavaFX SDK 1.0, a developer is free to define new classes and assign them to variables. In the same way variables can be defined to replace any code that is used in a javaFX script ("var txt = Text {...} or var myButton = SwingButton {..}" and then call them appropriately. This provides great flexibility in how we organize our code, and can help in separating out non-visual behavior and clarifying the code.

There is something else, something very cool, that building our own data models provides us: binding. We can make the value of some attributes dependent on others. For example, we can make the textField's text attribute dependent upon some other value, one that is easier for our buttons to access.

bindingAndDataModel.fx
Code
import java.lang.System;
import javafx.ext.swing.SwingButton;
import javafx.ext.swing.SwingTextField;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.stage.Stage;

class TextValue {
    var value: String;
    function clear(){ //note1
        value = "";
    }
};


var model = TextValue { // note2
    value: "Hello, Model"
}

//variable of the content elements
var win = HBox {
    content: [ // content array
        SwingTextField { // swing textfield
            columns: 10
            width: 100
            text: bind model.value // note3
            editable: false
        }

        SwingButton { // swing button A
            text: "a"
            action: function() {
                model.value = model.value.concat("a"); //note4
            }
        },

        SwingButton { // swing button B
            text: "b"
            action: function() {
                model.value = model.value.concat("b"); //note4
            }
        },
        SwingButton { // swing button for clear
            text: "Clear"
            action: function() {
                model.clear(); // note5
            }
        }
    ] // content array
}
// main staging area of the application
Stage {
    title: "Pat's JavaFX Attempt"
    width: 250
    height: 100
    scene: Scene {
        content: win //note 6

     }
}

There are three new entries at the top of the file: A class definition, a method definition, and a variable declaration. The class definition defines the values present on an object (attributes) and the methods (functions). The first strange thing you'll notice (lines tagged note1) is that the operation is not actually defined within the class definition. This was an intentional feature... see some of Chris Oliver's early postings to the mailing lists (TODO add link) as to why.

The variable declaration (tagged note2) is creating a new instance of TextValue and assigning its value attribute to "Hello, Model".

Our TextField's value attribute (tagged note3 is now a little different. Rather than specifiying its initial value as we declare the text field, we are telling it to use the value of model.value. Furthermore, since we are using the bind keyword, we are telling it to update every time that model.value updates. Also note that the binding is two-way; if the text field were editable, every change made to it would be reflected in model.value.

At the lines tagged note4 we have made the a and b buttons change the value of the model by concatenating 'a' or 'b' to the end of the string. Note also that we don't have the convenience of using the '+' operator... it is not overloaded for string usage.

For the Clear button's behavior (at note5) we could have done something similar to the other buttons, by assigning model.value = "". We used the operation clear() here just to show its use.

With note6, I have tried to remove all the content code and replaced only with a variable win and shown the declaration above the main staging area declared with the var win = HBox {..} Its the exact copy of whats in the HBox {..} in previous code did this is a good technique to extract and avoid coding errors or reducing complexity.

Adding Triggers

We are missing one thing from our original specification: Every time the text field changes, we want to print its contents to standard out. There is no syntax for adding a little code to the bind instruction; we can't just insert a sysout there.

So, we can move on to triggers, a bind-like feature of JavaFX. It allows us to do something when an attribute changes. Triggers are more sophisticated than shown here, this is only a simple usage.

trigger.fx
Code
import java.lang.System;
import javafx.ext.swing.SwingButton;
import javafx.ext.swing.SwingTextField;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.stage.Stage;


class TextValue { //note 1
    var oldValue:String;  //note 1a
    // declaration with trigger attached to its declaration
    var value:String on replace {   // note 1b
        System.out.println("Old: {oldValue} to New: {value}");
        oldValue = value; // note 1c
    }
    function clear(){
        value = "";
        oldValue = ""; //note 1d
    }
};


var model = TextValue { // note2
    value: "Hello, Model"
}

//variable of the content elements
var win = HBox {
    content: [ // content array
        SwingTextField { // swing textfield
            columns: 10
            width: 100
            text: bind model.value // note3
            editable: false
        }

        SwingButton { // swing button A
            text: "a"
            action: function() {
                model.value = model.value.concat("a"); //note4
            }
        },

        SwingButton { // swing button B
            text: "b"
            action: function() {
                model.value = model.value.concat("b"); //note4
            }
        },
        SwingButton { // swing button for clear
            text: "Clear"
            action: function() {
                model.clear(); // note5
            }
        }
    ] // content array
}
// main staging area of the application
Stage {
    title: "Pat's JavaFX Attempt"
    width: 250
    height: 100
    scene: Scene {
        content: win //note 6

     }
}

The only change here is at class TextValue at note 1. The syntax for triggers is interesting and has changed in javaFX SDK 1.0 but its interesting in implementation. Unlike before triggers don't have constructors and they apply simple SQL-like syntax ie insert, replace, delete operations to handle data modification events. Please review documentation for indepth information on triggers in javaFX.

In our case the following changes were made;

note 1a: declared a new var oldValue as a String

note 1b: appended to the var value trigger keywords "on replace {.doSomething..}"

note 1c: assigned value to the oldValue

note 1d: added clear the var oldValue just incase

Final and Annotated version

Here is the final version of the fx file, along with lots of comments inline.

FinalAnnotated.fx
Code
import java.lang.System;
import javafx.ext.swing.SwingButton;
import javafx.ext.swing.SwingTextField;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.stage.Stage;

/**
 *  Original : Patrick F 
 *  Modifications to javaFX SDK version 1.0 from original problem by Serete I
 */

/**
 * Define a class for out data model.
 * classes have attributes and functions (methods)
 * Just like regular Classes their methods (functions) are defined 
 * in the class)
 * 
 * Previous name for methods was operation, current name is function
 * Previous name for variables was attribute, current name is var 
 * 
 */


class TextValue {
    // variable to hold the old value 
    var oldValue:String;
    // variable to hold a value and with a trigger attached which fires 
    // when value variable is changed and print out to system.out old value 
    // and current value  and also replace old vaue with current value 
    var value:String on replace {
        System.out.println("Old: {oldValue} to New: {value}");
        oldValue = value;
    }
    // Define a method (function and doSomething)
    function clear(){
        value = "";
        oldValue = "";
    }
}; // class TextValue

/**
 *  Create an instance (a real value) of the TextValue class, and assign a string 
 *  to the variable value 
*/
var model = TextValue { // note2
    value: "Hello, Model"
}

/**
 *  Assign a variable for the content elements preparation for the stage
*/
var win = HBox {
    content: [ // content array
        SwingTextField { 
            columns: 10
            width: 100
            text: bind model.value
            editable: false
        }

        SwingButton {
            text: "a"
            action: function() {
                model.value = model.value.concat("a");
            }
        },

        SwingButton {
            text: "b"
            action: function() {
                model.value = model.value.concat("b");
            }
        },
        SwingButton {
            text: "Clear"
            action: function() {
                model.clear();
            }
        }
    ] // content array
}//end HBox

/*
 * Staging area with dimensions ready for content and is visible by default
*/

Stage {
    title: "Pat's JavaFX Attempt" // left the original echo phase 
    width: 250
    height: 100
    scene: Scene {
        content: win  // Show the win variable contents 

     }// end Scene
}// end Stage

The Swing Version

For completeness, then, let's go back and make a Java Swing version just to see what it looks like compared to the JavaFX version. I'll skip the intermediate steps and present the final Swing version here. This is a quick-and-dirty implementation; I would never recommend coding Swing this way...

FinalSwing.java
Code
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;


public class FinalSwing {
    public static void main(String[] args) {
        final JTextField textField = new JTextField();
        class TextValue {
            String value;
            public void setValue(String newValue) {
                // this method is acting as a trigger and 
                // a binding replacement.
                System.out.println("old:"+value+"; new:"+newValue);
                value = newValue;
                // cheating here; should be an event listener:
                textField.setText(newValue); 
            }
            public void clear() {
                setValue("");
            }
        }
        final TextValue model = new TextValue();
        model.setValue("Hello, Swing");

        JPanel flowPanel = new JPanel(new FlowLayout());
        flowPanel.add(textField);
        flowPanel.add(new JButton(new AbstractAction("a") {
            public void actionPerformed(ActionEvent e) {
                model.setValue(model.value + "a");
            }
        }));
        flowPanel.add(new JButton(new AbstractAction("b") {
            public void actionPerformed(ActionEvent e) {
                model.setValue(model.value + "b");
            }
        }));
        flowPanel.add(new JButton(new AbstractAction("Clear") {
            public void actionPerformed(ActionEvent e) {
                model.clear();
            }
        }));

        JFrame win = new JFrame("Pat's JavaFX Attempt");
        win.setSize(200, 100);
        win.add(flowPanel);
        win.setVisible(true);
    }
}

While this code is short, it is largely uncommented, and I've cheated a couple of ways to get binding and the trigger working.

Conclusions

JavaFX offers a lot in terms of data binding and component setup. Developers wanting to really use the framework should be sure to write scripts that can take advantage of them.

Advertisement