I have a JavaFX TableView that I'm populating with an ObservableList of Tasks. I've been trying to create a column that displays the index of each row, which serves as the ID for the tasks in the table, but I've tried the method here and similar methods from other sources with little success.
My code for reference, which has no superficial errors (as far as Eclipse can tell):
@FXML private TableColumn<Task, String> taskIndexCol;
Callback<TableColumn<Task, String>, TableCell<Task, String>> cb =
new Callback<TableColumn<Task, String>, TableCell<Task, String>>(){
@Override
public TableCell<Task, String> call(TableColumn<Task, String> col) {
TableCell<Task, String> cell = new TableCell<Task, String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
setText("");
} else {
setText(getIndex()+"");
}
}
};
return cell;
}
};
taskIndexCol.setCellFactory(cb);
Currently, my code gives me a NullPointerException when I try to set the CellFactory of the column. I've tried it with a populated task list, but that didn't help. I've been stumped for a very long time -- and theoretically this should be pretty easy, since it's just numbering the rows? It feels like I'm jumping through a million hoops to do something frustratingly simple.
Edit: The last line gives me the NPE.
It's impossible to tell the cause of the Null pointer exception, because you haven't shown the stack trace, identified the line which throws the exception, or posted enough of your code (none of the code in your callback can throw a null pointer exception, so something is wrong somewhere else).
For your actual cell implementation, you didn't show if you had a cellValueFactory
set on the column. If you don't, then the item
will always be null
, and so you will never see any text in the cells in that column. You can check the empty
property (or method parameter) as a means to check if the cell is in an empty row or one with actual data. (Note this means the column really doesn't need to provide any data at all: it can be a TableColumn<Task, Void>
.)
Additionally, it's probably safer to rely on using updateIndex(...)
instead of updateItem(...)
. updateIndex
is guaranteed to be called when the index changes; if you implement updateItem
you are assuming the index is set before the item, which means you are relying on an implementation detail.
Your code is a lot shorter and easier to read if you use Java 8 lambda expressions:
taskIndexCol.setCellFactory(col -> new TableCell<Task, String>() {
@Override
protected void updateIndex(int index) {
super.updateIndex(index);
if (isEmpty() || index < 0) {
setText(null);
} else {
setText(Integer.toString(index));
}
}
});
or alternatively
taskIndexCol.setCellFactory(col -> {
TableCell<Task, String> cell = new TableCell<>();
cell.textProperty().bind(Bindings.when(cell.emptyProperty())
.then("")
.otherwise(cell.indexProperty().asString()));
return cell ;
});
Here is a SSCCE:
import java.util.function.Function;
import static javafx.beans.binding.Bindings.when ;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableViewWithIndexColumn extends Application {
@Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.setEditable(true);
table.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson",
"isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com"),
new Person("Michael", "Brown", "michael.brown@example.com"));
TableColumn<Person, String> firstNameCol = createColumn("First Name",
Person::firstNameProperty, 150);
TableColumn<Person, String> lastNameCol = createColumn("Last Name",
Person::lastNameProperty, 150);
TableColumn<Person, String> emailCol = createColumn("Email",
Person::emailProperty, 150);
// index column doesn't even need data...
TableColumn<Person, Void> indexCol = createColumn("Index", person -> null, 50);
// cell factory to display the index:
// indexCol.setCellFactory(col -> {
//
// // just a default table cell:
// TableCell<Person, Void> cell = new TableCell<>();
//
// cell.textProperty().bind(
// when(cell.emptyProperty())
// .then("")
// .otherwise(cell.indexProperty().asString()));
//
// return cell ;
// });
indexCol.setCellFactory(col -> new TableCell<Person, Void>() {
@Override
public void updateIndex(int index) {
super.updateIndex(index);
if (isEmpty() || index < 0) {
setText(null);
} else {
setText(Integer.toString(index));
}
}
});
table.getColumns().add(indexCol);
table.getColumns().add(firstNameCol);
table.getColumns().add(lastNameCol);
table.getColumns().add(emailCol);
primaryStage.setScene(new Scene(new BorderPane(table), 600, 400));
primaryStage.show();
}
private <S, T> TableColumn<S, T> createColumn(String title,
Function<S, ObservableValue<T>> property, double width) {
TableColumn<S, T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(width);
return col;
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty email = new SimpleStringProperty();
public Person() {
this("", "", "");
}
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final java.lang.String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final java.lang.String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final java.lang.String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final java.lang.String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final java.lang.String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final java.lang.String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}