Dealing with null values in Cell

2019-06-09 07:11发布

I'm having serious problems getting a JavaFX 2 TableView to deal with null values in my data. So much trouble in fact I've put together a simple demo of the problem which is shown below.

Essentially the problem is that some of my data may be null and coercing the null to a value, such as an empty string, is not valid, it must be null. In the real code I'm working on I have a null date value, to keep the example simple I've shown a null string below.

Rows 1 and 2 of the table have null values. The two columns are different, the first shows the behaviour of the TextFieldTableCell the second my implementation of an editable cell. Both show the same incorrect behaviour.

The current behaviour is this:

  1. Click the cell to enter editing mode
  2. Enter a value in the cell
  3. Press enter to commit the edit
  4. Nothing happens

At step 4 I would expect the onEditCommit handler for the column to get called but it isn't. Having a look at the source for javax.scene.control.TableCell the commit isn't happening because of the first line of commitEdit is:

if (! isEditing()) return;

It seems that because the cell is null the editing property never gets set to true although I admit I've not yet traced through all the code to see why it never get's switched to true.

Thanks as always for any help.


Example

Main Application

package example;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.stage.Stage;
import javafx.util.Callback;

public class NullCellEditingExample extends Application {

    private TableView table = new TableView();
    private final ObservableList<Person> data =
            FXCollections.observableArrayList( new Person(null, "Smith"), new Person("Isabella", null), 
            new Person("Ethan", "Williams"), new Person("Emma", "Jones"), new Person("Michael", "Brown"));

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

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());

        TableColumn firstNameCol = createSimpleFirstNameColumn();
        TableColumn lastNameCol = createLastNameColumn();
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol);
        table.setEditable(true);

        ((Group) scene.getRoot()).getChildren().addAll(table);
        stage.setScene(scene);
        stage.show();
    }

    private TableColumn createSimpleFirstNameColumn() {
        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
        firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
        firstNameCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Person, String>>() {
            @Override
            public void handle(TableColumn.CellEditEvent<Person, String> t) {
                ((Person) t.getTableView().getItems().get(t.getTablePosition().getRow())).setFirstName(t.getNewValue());
            }
        });

        return firstNameCol;
    }

    private TableColumn createLastNameColumn() {
        Callback<TableColumn, TableCell> editableFactory = new Callback<TableColumn, TableCell>() {
            @Override
            public TableCell call(TableColumn p) {
                return new EditingCell();
            }
        };

        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
        lastNameCol.setCellFactory(editableFactory);
        lastNameCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Person, String>>() {
            @Override
            public void handle(TableColumn.CellEditEvent<Person, String> t) {
                t.getRowValue().setLastName(t.getNewValue());
            }
        });

        return lastNameCol;
    }   
}

Editing Cell

package example;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public class EditingCell extends TableCell<Person, String> {

    private TextField textField;

    public EditingCell() {
    }

    @Override
    public void startEdit() {
        super.startEdit();
        if( textField == null ) {
            createTextField();
        }
        setText(null);
        setGraphic(textField);
        textField.selectAll();
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setText((String) getItem());
        setGraphic(null);
    }

    @Override
    public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            if (isEditing()) {
                if (textField != null) {
                    textField.setText(getString());
                }
                setText(null);
                setGraphic(textField);
            } else {
                setText(getString());
                setGraphic(null);
            }
        }
    }

    private void createTextField() {
        textField = new TextField(getString());
        textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
        textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) {
                if (!arg2) { commitEdit(textField.getText()); }
            }
        });

        textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent t) {
                if (t.getCode() == KeyCode.ENTER) {
                    String value = textField.getText();
                    if (value != null) { commitEdit(value); } else { commitEdit(null); }
                } else if (t.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                }
            }
        });
    }

    private String getString() {
        return getItem() == null ? "" : getItem().toString();
    }
}

Person

package example;

import javafx.beans.property.SimpleStringProperty;

public class Person {

    private final SimpleStringProperty firstName;
    private final SimpleStringProperty lastName;

    public Person(String firstName, String lastName) {
        this.firstName = new SimpleStringProperty(firstName);
        this.lastName = new SimpleStringProperty(lastName);
    }

    public String getFirstName() {
        return firstName.get();
    }

    public void setFirstName(String firstName) {
        this.firstName.set(firstName);
    }

    public SimpleStringProperty firstNameProperty() {
        return firstName;
    }

    public String getLastName() {
        return lastName.get();
    }

    public void setLastName(String lastName) {
        this.lastName.set(lastName);
    }

    public SimpleStringProperty lastNameProperty() {
        return lastName;
    }
}

标签: javafx-2
1条回答
做自己的国王
2楼-- · 2019-06-09 07:29

Since posting I found another question that is basically identical. They took the approach of always just avoiding null values which is fine for strings (e.g. use an empty string) but not acceptable for dates or other data types where the is no obvious "empty" value.

The solution is to pass a value of false into the super.updateItem call in the EditingCell.updateItem method. I've put together a full write up of this if anyone is interested in the complete analysis.

查看更多
登录 后发表回答