JavaFX ListView and SelectionModel

To use ListView, you need to understand how it handles data. This section explains the basics and the SelectionModel that manages selected items.

Creating a ListView

We have already covered basic GUI controls, but JavaFX also includes more complex controls. Especially important are controls that handle data. These controls display and manipulate information based on prepared data.

A representative example is a list: a GUI that displays multiple items in a vertically scrollable list. JavaFX provides this as a control named ListView.

First, let’s create a ListView with FXML. A ListView is created with the <ListView> tag. The tag itself is simple, but by itself it creates an empty list. You must also define the contents to display inside it.

<ListView>
    <items>
        <FXCollections fx:factory = "data type">
            ...... data contents ......
        </FXCollections>
    </items>
</ListView>

If you write the data contents in FXML, it looks like this. The data for a ListView is provided with the <items> tag. Inside it, add an <FXCollections> tag and write the actual data. Usually you provide an fx:factory attribute and specify the type of data, or more precisely the class stored in FXCollections.

Many settings are possible, but for now specify observableArrayList. This is a subclass of ArrayList that holds objects. If you write text with tags such as <String> inside it, an ArrayList of String data is created and used as the displayed items.

The following sample displays several text items.

<?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?>
<BorderPane xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.tuyano.libro.AppController">
    <stylesheets>
        <URL value="@app.css" />
    </stylesheets>
    <top>
        <Label fx:id="label1" text="This is FXML!" />
    </top>
    <center>
        <ListView fx:id="list1">
            <items>
                <FXCollections fx:factory="observableArrayList">
                    <String fx:value="Windows" />
                    <String fx:value="Mac OS" />
                    <String fx:value="Linux" />
                </FXCollections>
            </items>
        </ListView>
    </center>
    <bottom>
        <Button text="Click" fx:id="btn1" />
    </bottom>
</BorderPane>

The data is prepared inside the <items> tag of <ListView> like this.

<FXCollections fx:factory="observableArrayList">

Then text to display is specified with <String> tags inside it. For simple text values, this is enough.

Creating ListView Items in Java Code

Next, let’s look at how to create the items displayed in ListView from Java source code instead of FXML.

The items displayed in ListView are managed by a property named items. This property manages all displayed items together, and an instance from the collection framework is set on it.

You can retrieve this items value with getItems or replace it with setItems. You can also manage each item stored in the items instance by using methods of the collection class.

The following is a sample source code.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ListView<String> list1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        list1.setItems(FXCollections.observableArrayList());
        list1.getItems().add("One");
        list1.getItems().add("Two");
        list1.getItems().add("Three");
    }
 
}

First, change the <ListView> tag in the previous FXML as follows.

<ListView fx:id="list1"></ListView>

No items are added in FXML now, but when you run it, you will see the items “One”, “Two”, and “Three” displayed in the list.

Here, the Initializable interface is used and initialization is done in the initialize method.

list1.setItems(FXCollections.observableArrayList());

This creates an instance of the collection class with FXCollections.observableArrayList and sets it with setItems. After that, you only need to add text to display to the items instance with add.

list1.getItems().add("One");
list1.getItems().add("Two");
list1.getItems().add ("Three");

Once you know the usage, creating a list is not very difficult.

Handling Click Events in ListView

What should you do when you want to run some processing after clicking an item displayed in ListView? ListView does not provide an action event, so you need to prepare another event for click handling.

Here, let’s use an event named MouseClick. As the name suggests, this event occurs when the mouse is clicked. You can set it with the setOnMouseClicked method.

listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {
        // processing to perform
    }
});

You can write it like this. However, in Java 8, lambda expressions are the standard way to write these event handlers, so it is usually written as follows.

listView.setOnMouseClicked ((MouseEvent) -> {
    // processing to perform
}

This is much simpler. The following example displays the selected list item text in Label1 when an item is clicked. With setOnMouseClicked, you can easily add simple click processing for list items.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
 
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ListView<String> list1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        list1.setItems(FXCollections.observableArrayList());
        list1.getItems().add("One");
        list1.getItems().add("Two");
        list1.getItems().add("Three");
         
        list1.setOnMouseClicked((MouseEvent)->{
            Object obj = list1.getSelectionModel().getSelectedItem();
            label1.setText(obj.toString());
        });
 
        btn1.setOnAction((AtionEvent)->{
            String obj = list1.getSelectionModel().getSelectedItem();
            label1.setText("you selected: \"" + obj + "\".");
        });
    }
 
}

When handling events for item selection in ListView, MouseClick is actually not the best choice. It is better to use the ChangeListener explained later.

SelectionModel

In the previous example, the following processing was needed to retrieve the selected item text with setOnMouseClick.

String obj = list1.getSelectionModel().getSelectedItem();

This calls getSelectionModel, then calls getSelectedItem on the returned instance. This retrieves the selected item object. To understand what this does, you need to understand SelectionModel in ListView.

SelectionModel is a model class that manages selected items. JavaFX introduces the concept of models to manage various kinds of data. A model is used to manage data that changes dynamically.

Selected items are managed with a model class named SelectionModel. By calling the getSelectionModel method of ListView, you can get the SelectionModel instance contained in that ListView.

The SelectionModel class provides various methods related to selected items. Here we use the getSelectedItem method. It returns the selected item instance. From that object, you can retrieve displayed text and other values.

Stored Values and Generic Types

ListView supports generic types. In the earlier sample, the field that stores the ListView value was written as follows.

@FXML ListView<String> list1;

By adding <String> like this, you can restrict stored values to String. The reason the value from getSelectedItem could be assigned directly to a String variable is that a generic type was used.

Setting a ChangeListener on SelectionModel

This SelectionModel has event handling for property changes. For example, SelectionModel has a property named SelectedItem, and the selected item could be retrieved with getSelectedItem.

You can detect and handle value changes by setting a ChangeListener event listener on this SelectedItem property. As the name suggests, ChangeListener is an event listener that handles events that occur when a value changes.

This can be set with the addListener method of the ReadOnlyObjectProperty class obtained from the selectedItemProperty method of SelectionModel. The basic form is as follows.

selectionModel.selectedItemProperty().addListener(new ChangeListener() {

    @Override
    public void changed(ObservableValue observable, Object oldVal, Object newVal) {
        // processing to perform
    }
});

ChangeListener has one method named changed. This method receives an ObservableValue instance, the previous value, and the new value as arguments.

You may remember the phrase “an interface with only one method.” In Java 8, interfaces with one method can be replaced by lambda expressions. If you set the event listener with addListener as a lambda expression, it becomes this.

selectionModel.selectedItemProperty().addListener (
    (ObservableValue observable, Object oldVal, Object newVal) -> {
        // processing to perform
    }
);

This is easier to read. By setting a ChangeListener this way, you can process the moment a value changes, meaning when the value of SelectedItem changes, or in other words, when the selection state changes.

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.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
 
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ListView list1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        list1.setItems(FXCollections.observableArrayList());
        list1.getItems().add("One");
        list1.getItems().add("Two");
        list1.getItems().add("Three");
         
        list1.getSelectionModel().selectedItemProperty().addListener(
            (ObservableValue observable, Object oldVal, Object newVal) -> {
                label1.setText(oldVal + " -> " + newVal);
            }
        );
         
        btn1.setOnAction((AtionEvent)->{
            Object obj = list1.getSelectionModel().getSelectedItem();
            label1.setText("you selected: \"" + obj.toString() + "\".");
        });
    }
 
}

When you select a list item, the change in value is displayed in label1 in the form “old value -> new value.” Isn’t this smarter than using MouseClick? MouseClick does not fire when selection changes through keyboard operations, but with ChangeListener, the event fires immediately whenever the selection state changes in any way, and the corresponding processing can run.