Channels ▼

Eric Bruno

Dr. Dobb's Bloggers

JavaFX 2.0 Binding

November 17, 2011

Binding in JavaFX 1.x with JavaFX Script was easy: You simply used the bind keyword and it just worked. Consider the following JavaFX Script example:


    // Make sure the width of the listview always equals the scene's width
    var listview: ListView = …
    listview.width = bind scene.width;

With this code, as you make your scene bigger (i.e. resizing the bounding window), your ListView control is resized to always occupy the full width of the window.

You can use the bind keyword on variables as well. The following example uses binding in many ways, including:

  1. binding double values to the converted input from text boxes
  2. calculating a total price based on the user's input for item price and tax rate
  3. displaying the total price as a GUI Label based on the calculated double value
    // Calc total based on price and tax
    var priceInput: TextBox; // user inputs price
    var taxRateInput: TextBox; // user inputs tax rate

    var tax: Double = bind new Double(taxRateInput.text);
    var price: Double = bind new Double(priceInput.text);

    var total: Double = bind price + (price * tax);

    // …
    var total: Label { text: bind "Total: ${total}"  };

Although many programmers decried the need to learn a new language, one advantage JavaFX Script had is its ability to accomplish a lot with very little code. Let's examine how we can accomplish all of this in JavaFX 2.0.

JavaFX 2.0 Binding

Binding in JavaFX 2.0 still exists, however it's quite different than JavaFX 1.x. This is mainly because version 2.0 uses a Java API, and bind is not a Java keyword. To make things apparently more confusing, JavaFX 2.0 supports different types of binding: via property listeners; the low-level binding API; and high-level binding via the Fluent API. Let's dive in by rewriting the example above.

Property Listeners

I commonly find myself needing to resize multiple components to match each other in some way, such as in height, width, or both. Take, for example, the full-featured JavaFX 2.0 TableView control. If, say, you'd like to create a spreadsheet-like view, you would create a TableView control, add columns and data, and then size it to match the Scene's width and height as such:

    public void start(Stage primaryStage) {
        primaryStage.setTitle("My Table");
        Group root = new Group();
        Scene scene = new Scene(root, 640, 480);
        final TableView tbl = createTable();
        tbl.setPrefWidth(scene.getWidth());
        tbl.setPrefHeight(scene.getHeight());
        root.getChildren().add(tbl);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

When the application is started it looks great, until you resize the application frame. As a result, there are gaps along the bottom and right edges of the window as shown here:

Screeen

To fix this, you can bind the TableView's width and and height to the Scene's width and height properties by adding the following code:


        scene.widthProperty().addListener( 
            new ChangeListener() {
                public void changed(ObservableValue observable, 
                                    Object oldValue, Object newValue) {
                    Double width = (Double)newValue;
                    tbl.setPrefWidth(width);
                }
            });

        scene.heightProperty().addListener(
            new ChangeListener() {
                public void changed(ObservableValue observable, 
                                    Object oldValue, Object newValue) {
                    Double height = (Double)newValue;
                    tbl.setPrefHeight(height);
                }
            });

There's seemingly a lot of code here, but there's a lot of information provided as well. Let's focus on the first observable property, scene.widthProperty. Since it implements the JavaFX 2.0 Observable interface, simply call addListener and provide your own JavaFX 2.0 Listener object. In this case, we're nesting a ChangeListener class to provide the implementation, with an implemented changed() method that provides a reference to the object being listened to, the property's previous value, and its new value as well.

For the Scene's width and height properties in this example, as the code is notified that either property value has changed, the TableView's width or height is changed to match. As a result, when you resize the application's window, the table resizes to match, leaving no gaps along the edges. You can find the full code for this example here.

High-Level Binding

Let's re-examine the JavaFX 1.3.1 example above, where the bind keyword helped calculate a total price based on a price and tax rate as entered by the user. In this case, we'll use the JavaFX high-level binding API to achieve this in JavaFX 2.0. To begin, we need to declare our own Observable properties, one for the price, and the other for the tax rate:


    DoubleProperty taxRate = new SimpleDoubleProperty();
    DoubleProperty price = new SimpleDoubleProperty();

Next, we need to write some code that uses binding to automatically calculate the total price when either the tax rate or price changes. To do this, the Fluent API's NumberBinding class provides all the functionality we need. First, calculate the raw tax using the price:


        NumberBinding tax = taxRate.multiply(price);

Next, add the tax to the item's price to get the total:


        NumberBinding total = price.add(tax);

We now have a total price that uses binding to calculate (and recalculate) the total price when either price or tax rate changes. What we need now is a way to enter the price and tax rate, and finally display the total value. The following code creates a text input box to enter the price, and uses binding to update the price Observable property:


        TextField priceInput = new TextField();
        priceInput.textProperty().addListener(
            new ChangeListener() {
                public void changed(ObservableValue observable, 
                                    Object oldValue, Object newValue) {
                    Double val = new Double((String)newValue);
                    price.setValue( val );
                }
            });

We follow the same pattern for the tax rate:


        TextField taxInput = new TextField();
        taxInput.textProperty().addListener(
            new ChangeListener() {
                public void changed(ObservableValue observable, 
                                    Object oldValue, Object newValue) {
                    try {
                        Double val = new Double((String)newValue);
                        taxRate.setValue( val );
                    } catch ( Exception e ) { }
                }
            });

At this point, as you enter price and tax rate, the total property is calculated automatically using the high-level binding API. All we need now is to display the total, again using binding. In this case, we bind to the total Observable property so that as it changes value, the UI is updated as well:


        final Label totalLbl = new Label();
        total.addListener(
            new ChangeListener() {
                public void changed(ObservableValue observable, 
                                    Object oldValue, Object newValue) {
                    totalLbl.setText( "Total: $"+newValue );
                }
            });

When you run the resulting application, you'll notice the total update as you enter a price and then a tax rate. Although it's not quite as easy as the JavaFX Script bind keyword, JavaFX 2.0 high-level binding is straightforward, and the code is very easy to read (albeit a bit more verbose). You can download the code for this example here.

Let's take a look at low-level binding in JavaFX 2.0, and how it differs from the examples so far.

Low-level Binding

For more flexibility, and to offer the best performance, JavaFX 2.0 supports what it calls low-level binding. Although not much different then the previous example, the actual calculation performed is done through code you provide in a computeValue method. For example, perhaps you'd like to take the various Observable properties that you've defined (i.e. tax rate and price), and apply your own complex calculation based on, say, shipping rates. We begin by defining the properties we need:


        final DoubleProperty taxRate = new SimpleDoubleProperty();
        final DoubleProperty price = new SimpleDoubleProperty();
        final IntegerProperty zip = new SimpleIntegerProperty();

Next, we provide a custom implementation of a Binding class, specifically DoubleBinding in this example, where we override the computeValue method, such as:


        DoubleBinding total = new DoubleBinding() {
            {
                super.bind(taxRate, price, zip);
            }
 
            @Override
            protected double computeValue() {
                double shipping = calcShipping( zip.get() );
                return ((price.get() * taxRate.get()) + price.get() * shipping);
            }
        };

The code above not only adds tax based on the rate and price, but also adds shipping charges based on the entered zip code. In case you're confused, the initializer block simply calls the DoubleBinding base class' constructor when created. Alternatively, I can create my own class that extends DoubleBinding (or any of the Binding classes) and call the super class' constructor in the subclass' constructor more conventionally.

Finally, whereas JavaFX 2.0 doesn't support the simple bind keyword, it does offer a lot of flexibility in how to apply binding in your Java code. The result is performance and readability, without requiring a change to the Java language.

Happy coding!
-EJB

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 


Video