JavaFX Property Event Handling

JavaFX controls provide dedicated classes for properties that manage values, and you can set event listeners on them to process value changes. This section explains property event handling for ToggleGroup, ComboBox, and Slider.

ChangeListener Processing for ToggleGroup

Earlier, we created event handling for when the selection state changes in ListView. This was done by setting an event listener on the property that manages the selection state.

This pattern, “handle events with ChangeListener when a property changes,” is a basic event handling approach for JavaFX controls. Once you understand this basic idea, you can set up similar event handling for other controls as well.

First, let’s think about event handling when radio buttons are operated. Radio buttons are managed together with a group management class named ToggleGroup. When a radio button is selected, the property value that represents the selected state of this ToggleGroup changes.

This property is named selectedToggleProperty, and a ReadOnlyObjectProperty instance is set for it. By setting a ChangeListener with addListener, you can process selection changes.

Let’s create a simple sample.

<?xml version="1.0" encoding="UTF-8"?>
 
<?language javascript?>
<?import java.lang.*?>
<?import java.net.URL ?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.collections.FXCollections?>
<?import javafx.scene.layout.*?>
<BorderPane xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.devkuma.javafx.AppController">
    <stylesheets>
        <URL value="@app.css" />
    </stylesheets>
    <top>
        <Label fx:id="label1" text="This is FXML!" />
    </top>
    <center>
        <VBox>
            <fx:define>
                <ToggleGroup fx:id="group1" />
            </fx:define>
            <RadioButton text="Male" toggleGroup="$group1" userData="남자" selected="true" />
            <RadioButton text="Female" toggleGroup="$group1" userData="여자"/>
        </VBox>
    </center>
    <bottom>
    </bottom>
</BorderPane>

First, radio buttons are provided in FXML. As shown above, there are two radio buttons that share a ToggleGroup. Here, a property named userData is included. This gives each radio button its own data, and we write it because it will be used later. Apart from that, these are ordinary radio buttons with nothing special added.

Setting a ChangeListener on ToggleGroup

Now let’s read the FXML and create a controller class that includes event handling for ToggleGroup.

The following is a simple example.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ToggleGroup group1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
 
        group1.selectedToggleProperty().addListener((ObservableValue<? extends Toggle> 
            observ, Toggle oldVal, Toggle newVal)->{
            String oldStr = (String)oldVal.getUserData();
            String newStr = (String)newVal.getUserData();
            label1.setText(oldStr + "->" + newStr);
        });
    }
 
}

Run it and click a radio button. The label at the top of the window displays text such as “남자 -> 여자.” You can see that it displays the userData set on each RadioButton before and after the selection change.

Here, a listener is set on the ReadOnlyObjectProperty stored in selectedToggleProperty of ToggleGroup in the following form.

ReadOnlyObjectProperty.addListener((ObservableValue<? extends Toggle> observ,
    Toggle oldVal, Toggle newVal) -> {
    // processing to perform
});

This shows the setup with a lambda expression. ChangeListener defines only one method named changed. It has the following form.

void changed(ObservableValue<? extends T> observable, T oldValue, T newValue)

ObservableValue can have a generic type. When it is contained in selectedToggleProperty of ToggleGroup, ObservableValue is provided in a form that extends the Toggle class. oldValue and newValue are also passed as Toggle instances.

The Toggle class, or more precisely the instance, is a class for managing values handled as ON/OFF. From this Toggle, you retrieve information about the changed value. Here we use the getUserData method. This retrieves the value of the UserData property set on that Toggle. It is the value of the userData attribute added in FXML.

Classes such as ReadOnlyObjectProperty, ObservableValue, and Toggle may feel unfamiliar and difficult to understand, but basic property change event listener processing has a similar shape everywhere. Once you get used to this event handling style, you can handle many controls in the same way.

SelectionModel Event Handling for ComboBox

Next, let’s look at ComboBox. If you understand ListView, you can process it in almost the same way.

ComboBox also has a property with a model class that manages selection state. You can obtain the selection model class with getSelectionModel.

Then you can set an event listener with addListener on the property obtained from selectedItemProperty to process selection changes.

Let’s take a look. In the <center> tag from the earlier FXML, write the following.

<ComboBox fx:id="combo1">
<items>
    <FXCollections fx:factory="observableArrayList">
        <String fx:value="One" />
        <String fx:value="Two" />
        <String fx:value="Three" />
    </FXCollections>
</items>
</ComboBox>

This prepares a ComboBox with String values as items. Next, you only need to provide the event handling in the controller class.

Write the source code as follows.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
 
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ComboBox<String> combo1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
 
        combo1.getSelectionModel().selectedItemProperty().
            addListener((ObservableValue<? extends String> observ,
                    String oldVal, String newVal)->{
            label1.setText(oldVal + "->" + newVal);
        });
    }
}

Here, String is specified as the generic type of ComboBox, as shown below.

@FXML ComboBox<String> combo1;

This configures it as a control whose items are String values. The event listener is set as follows.

combo1.getSelectionModel().selectedItemProperty().addListener ......

Set addListener on selectedItemProperty of the selection model obtained with getSelectionModel. It is a little complicated. The event listener is defined in the following form.

addListener((ObservableValue<? extends String> observ, String oldVal, String newVal) -> {
    // event handling
});

The first argument is defined as ObservableValue<? extends String> observ. Because <String> is also specified in the definition of ComboBox, ObservableValue is defined with extends String. The previous and new values are then passed as String.

The idea that ObservableValue is written in a form that extends the stored value may feel strange. In practice, it comes from how the ObservableValue superclass is designed. It may be a little hard to grasp, but remember that ObservableValue is prepared by extending the class set in the selection model.

Handling Slider valueProperty Events

Slider value changes can be handled in a similar way. The value set in the Slider class is processed through a property named valueProperty.

This is managed by an instance of the DoubleProperty class. By setting an event listener with addListener on this DoubleProperty, you can process value changes.

Let’s create and use a simple sample. In the earlier FXML, write the <center> tag as follows.

<center>
    <Slider fx:id="slider1" min="0" max="100"/>
</center>

This displays one slider. Now set event handling so that the display updates in real time when it is operated.

Create the following simple example.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML Slider slider1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
 
        slider1.valueProperty().addListener((ObservableValue<? extends Number> 
                observ, Number oldVal, Number newVal)->{
            double oldnum = oldVal.doubleValue();
            double newnum = newVal.doubleValue();
            label1.setText(oldnum + "->" + newnum);
        });
    }
}

This sample retrieves and displays the value before and after the change. When you operate the slider, the previous and new values are shown in a form such as “12.34567 -> 98.7654.”

Here, the event listener for the slider is written as follows.

slider1.valueProperty().addListener ......

An event listener is set with addListener on the DoubleProperty instance obtained from valueProperty of the Slider class. It has the following form.

addListener((ObservableValue <? extends Number> observ, Number oldVal, Number newVal) -> {
    // event handling
});

ObservableValue is defined as a subclass of Number. The passed values are Number instances. After that, you only need to call methods on the passed Number values to retrieve the values you need.

double oldnum = oldVal.doubleValue();
double newnum = newVal.doubleValue();

Here, the values are retrieved as double values with doubleValue and displayed. If you want integers, use the intValue method.

You should now understand the relationship between event listeners and ObservableValue much better. Once you can handle ObservableValue well, you can consider property event handling mostly mastered.