Creating a row index column in JavaFX

2020-03-31 08:38发布

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.

1条回答
做个烂人
2楼-- · 2020-03-31 09:24

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);
    }
}
查看更多
登录 后发表回答