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.