Recent changes Random page
GAMING
Technology
 
Gaming
Entertainment
Science Fiction
Biggest wikis
Hobbies
Music
See more...

Step by Step: How to build your first JavaFX application

From Planet JFX

Jump to: navigation, search

Contents

[edit] 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.

[edit] Building the JavaFX application

Let's make an attempt at the JavaFX version.

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.

[edit] Hello World

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

HelloWorld.fx
Code Preview
import javafx.ui.*;

Frame {
    title: "Hello World JavaFX"
    width: 200
    height: 50
    content: Label {
        text: "Hello World"
    }
    visible: true
}
Preview

[edit] Adding a text field

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

early.fx
Code Preview
import javafx.ui.*;

var win = Frame {
    title: "Pat's JavaFX Attempt"
    width: 200
    height: 100
    content: TextField {
        editable: false
	value: "Hello, Text"
	width: 100
    }
    visible: true // makes the frame show up.
};
Preview

Notice that I swapped out the label component, and just kept on defining properties no matter how far in we are, like editable on textfield.

[edit] 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 get by with a simplistic layout: FlowLayout.

In JavaFX, there are some convenience classes that help us with this: xxPanels. For a FlowLayout, we have a FlowPanel.

AllWidgets.fx
Code Preview
import javafx.ui.*;

var win = Frame {
    title: "Pat's JavaFX Attempt"
    width: 200
    height: 100
    content: FlowPanel { // note1
        content: [
        TextField {
            editable: false
            value: "Hello, Text"
            width: 100
        },
        Button {
            text: "a"
        },
        Button {
            text: "b"
        },
        Button {
            text: "Clear"
        }, // note2
        ]
    } // note1
    visible: true // makes the frame show up.
    };
Preview

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).

A frame's content also allows an array of components, but this doesn't work the same way. Comment out the two lines tagged note1 to see what it does. This is actually just like what would happen in a Swing application when multiple components are added. They are all added to the Frame's BorderLayout.CENTER, overlaying each other.

[edit] 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 javafx.ui.*;
import java.lang.System; // note1

var win = Frame {
    title: "Pat's JavaFX Attempt"
    width: 200
    height: 100
    content: FlowPanel {
        content: [
        TextField {
            editable: false
            value: "Hello, Text"
            width: 100
        },
        Button {
            text: "a"
            action: operation() {
                System.out.println("'a' clicked");
            }
        },
        Button {
            text: "b"
            action: operation() {
                System.out.println("'b' clicked");
            }
        },
        Button {
            text: "Clear"
            action: operation() {
                System.out.println("'clear' clicked");
            }
        },
        ]
    }
    visible: true // makes the frame show up.
    };

Adding an ActionListener is pretty straightforward. Define an operation() 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.

[edit] Binding a data model to a view

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

In JavaFX, a developer is free to define new classes and assign them to variables, just as we have been doing for the frames we have been creating in the above examples ("var frame = Frame..." 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 javafx.ui.*;
import java.lang.System;

class TextValue {
    attribute value: String;
    operation clear(); // note1
}

operation TextValue.clear() { // note1
    value = "";
}

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

var win = Frame {
    title: "Pat's JavaFX Attempt"
    width: 200
    height: 100
    content: FlowPanel {
        content: [
        TextField {
            editable: false
            value: bind model.value // note3
            width: 100
        },
        Button {
            text: "a"
            action: operation() {
                model.value = model.value.concat("a"); // note4
            }
        },
        Button {
            text: "b"
            action: operation() {
                model.value = model.value.concat("b"); // note4
            }
        },
        Button {
            text: "Clear"
            action: operation() {
                model.clear(); // note5
            }
        },
        ]
    }
    visible: true // makes the frame show up.
    };

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 (operations). 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.

[edit] 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 javafx.ui.*;
import java.lang.System;

class TextValue {
    attribute value: String;
    operation clear();
}

operation TextValue.clear() {
    value = "";
}

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

trigger on TextValue.value[oldValue] = newValue { // note1
    System.out.println("old:".concat(oldValue).concat("; new:").concat(newValue));
}

var win = Frame {
    title: "Pat's JavaFX Attempt"
    width: 200
    height: 100
    content: FlowPanel {
        content: [
        TextField {
            editable: false
            value: bind model.value
            width: 100
        },
        Button {
            text: "a"
            action: operation() {
                model.value = model.value.concat("a");
            }
        },
        Button {
            text: "b"
            action: operation() {
                model.value = model.value.concat("b");
            }
        },
        Button {
            text: "Clear"
            action: operation() {
                model.clear(); // note3
            }
        },
        ]
    }
    visible: true // makes the frame show up.
};

The only change here is at note1. The syntax for triggers is almost insane, and could quite possibly change. The first line says what we are watching (TextValue.value), the variable name to use for the old value (oldValue) and the variable name to use for the new value (newValue). The phrase [oldValue] is optional and can be left off if you won't be referencing the old value.

[edit] 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.ui.*;
import javafx.ui.canvas.*;
import javafx.ui.filter.*;

// Define a class for our data model.
//  classes have attributes and operations (methods)
// While operations are named in the class, the actual
//  method definitions are defined outside of it
class TextValue {
    attribute value: String;
    operation clear();
}

// Define an operation
operation TextValue.clear() {
    value = "";
}

// a var (a real value) creates an instance of a class, in this case,
//  the class defined above.  We can assign values to attributes
var model = TextValue {
    value: "nothing+"
};

// a trigger is like a listener, which is fired upon, in this case, a
//  a value change.  There are likely multiple kinds of triggers, and
//  there is likely a way to get the instance that triggered things.
// Also note we are listening to the changes of any instance of TextValue...
trigger on TextValue.value[oldValue] = newValue {
    System.out.println("old:".concat(oldValue).concat("; new:").concat(newValue));
}

// this is another variable declaration.  One difference: being a
//  JFrame instance (Frame is in javafx.ui and is a subclass of
//  JFrame), and having its visible attribute set to true, it
//  causes a frame to show up.
var win = Frame {
    title: "Pat's JavaFX Attempt" // generally the attributes you can set are simply
    //  their bean names.
    width: 200
    height: 100 // if you leave this off, note that the
    //  frame is not automatically packed, and
    //  thus only the title bar will show
    content: FlowPanel {
        content:
        [ // note the array start... xxPanels tend to take arrays of components.
        TextField
        {
            editable: false
            value: bind model.value
            width: 100
        },
        Button {
            text: "a"
            action: operation()
            {                     
                model.value = model.value.concat("a");
            }
        },
        Button {
            text: "b"
            action: operation()
            {
                model.value = model.value.concat("b");
            }
        },
        Button {
            text: "Clear"
            action: operation()
            {
                model.clear();
            }
        },
        ] // endarray
    } // end content
    visible: true // makes the frame show up.
};

[edit] 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.

[edit] 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.

Rate this article:
Share this article:
.