Javarevisited
Published in

Javarevisited

Experimenting with Java Records, DatePicker, and TableView in JavaFX

Upgrading my JavaFX ToDoList from ListView to TableView

Upgrading my JavaFX ToDoList application

In the first iterations of my Java ToDoList, I used a JavaFX ListView component. In the very first iteration, I was simply keeping a List of Strings in the ToDo List. You can see the results of my first iteration in the following blog.

I decided I wanted to allow my ToDo List to keep track of to do items by date. So I needed to hold onto something a little more complex than just a String in my ToDo List.

Creating a ToDoItem as a record

Since I’m working on this application in Java 17, I made my ToDoItem a Java record. Java has had support for records since Java 14.

This was the first iteration of my ToDoItem.

public record ToDoItem(String name, LocalDate date) {}

I defined this ToDoItem record inside the TodoController class. A ToDoItem has a name and a date. I instinctively used LocalDate from Java Time for the date type, and hoped that JavaFX would magically have support for this.

Adding a DatePicker

JavaFX has a component named DatePicker, and it works the way I hoped. I added DatePicker next to the TextField for my ToDoItems.

<HBox id="HBox1" alignment="CENTER_LEFT" spacing="5.0">
<Label text="Item: " />
<TextField fx:id="todoItem" />
<Label text="Date: " />
<DatePicker fx:id="todoDate" />
</HBox>

I bound the DatePicker to a variable named todoDate and added it to my TodoListController. Now the nice surprise was what happened when I changed the onAddButtonClick code in TodoListController.

@FXML
protected void onAddButtonClick()
{
ToDoItem item = new ToDoItem(
this.todoItem.getText(),
this.todoDate.getValue());

this.todoList.getItems().add(item);
}

The code to get the date value from aDatePicker instance returns a LocalDate! Holy cow! I was not expecting that, but I was pleasantly surprised.

Converting ListView to TableView

My goal when converting from ListView to TableView was to keep as much of the UI code in FXML as possible. I was expecting to have to do more things programmatically because I was using Java Records to define my ToDoItem class. I assumed JavaFX would work more easily with regular Java Beans. I figured in the worst case, I could just add getter methods to my ToDoItem record.

This is what I wound up with for my TableView definition looked like in the FXML file.

<TableView fx:id="todoList">
<columns>
<TableColumn text="Name" minWidth="75.0" sortable="true">
<cellValueFactory>
<PropertyValueFactory property="name" />
</cellValueFactory>
</TableColumn>
<TableColumn text="Date" minWidth="50.0" sortable="true">
<cellValueFactory>
<PropertyValueFactory property="date" />
</cellValueFactory>
</TableColumn>
</columns>
</TableView>

I did have to add getters (getName and getDate) to my ToDoItem record in order to use PropertyValueFactory. I learned I could have used a lambda with setCellValueFactory but this would have required me to put more code in my controller class and I didn’t want to do that yet.

Finally, as you can see in the FXML snippet above, I was able to simply set an attribute named sortable to true for both columns. This made my ToDo List sortable.

So far, so good

Not much else had to change in my ToDo List app. Here’s the code for my FXML, application, and controller files.

todolist-view.xml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="example.todolist.TodoListController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>

<HBox id="HBox1" alignment="CENTER_LEFT" spacing="5.0">
<Label text="Item: " />
<TextField fx:id="todoItem" />
<Label text="Date: " />
<DatePicker fx:id="todoDate" />
</HBox>
<TableView fx:id="todoList">
<columns>
<TableColumn text="Name" minWidth="75.0" sortable="true">
<cellValueFactory>
<PropertyValueFactory property="name" />
</cellValueFactory>
</TableColumn>
<TableColumn text="Date" minWidth="50.0" sortable="true">
<cellValueFactory>
<PropertyValueFactory property="date" />
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
<HBox id="HBox2" alignment="CENTER" spacing="5.0">
<Button text="Add" onAction="#onAddButtonClick" alignment="BOTTOM_LEFT" />
<Button text="Remove" onAction="#onRemoveButtonClick" alignment="BOTTOM_RIGHT" />
</HBox>
</VBox>

TodoListApplication class

package example.todolist;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class TodoListApplication extends Application
{
@Override
public void start(Stage stage) throws IOException
{
FXMLLoader fxmlLoader = new FXMLLoader(
TodoListApplication.class.getResource("todolist-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 640, 480);
stage.setTitle("Todo List");
stage.setScene(scene);
stage.show();
}

public static void main(String[] args)
{
TodoListApplication.launch();
}
}

TodoListController class

package example.todolist;

import java.time.LocalDate;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.MutableList;

public class TodoListController
{
@FXML
private TextField todoItem;

@FXML
private DatePicker todoDate;

@FXML
private TableView<ToDoItem> todoList;

@FXML
protected void initialize()
{
this.todoList.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
MutableList<ToDoItem> items = Lists.mutable.empty();
ObservableList<ToDoItem> list = FXCollections.observableList(items);
this.todoList.setItems(list);
}

@FXML
protected void onAddButtonClick()
{
ToDoItem item = new ToDoItem(
this.todoItem.getText(),
this.todoDate.getValue());

this.todoList.getItems().add(item);
}

@FXML
protected void onRemoveButtonClick()
{
int indexToRemove = this.todoList.getSelectionModel().getSelectedIndex();
this.todoList.getItems().remove(indexToRemove);
}

public record ToDoItem(String name, LocalDate date) {

public String getName()
{
return this.name;
}
public String getDate()
{
return this.date.toString();
}
}
}

Next Steps

My next step will be to make my ToDo List persistent. I might see if I can use the Jackson support for Eclipse Collections to write out and read in the data from the ToDo List. I used a MutableList from Eclipse Collections and wrapped it in an ObservableList using the JavaFX utility class named FXCollections. I wouldn’t be a much of an Eclipse Collections advocate if I didn’t use Eclipse Collections in my ToDo List application. 😀

Thank you for reading!

I am the creator of and a Committer for the Eclipse Collections OSS project which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Donald Raab

Java Champion. Creator of the Eclipse Collections OSS Java library (http://www.eclipse.org/collections/). Inspired by Smalltalk. Opinions are my own.