Question

I am learning JavaFX and implementing a TableView class. I'd like to make a cell editable without first pressing Enter or double clicking on it. I wonder if it's possible to start entering a new value without first hitting Enter? Thank you.

Was it helpful?

Solution

Looks like I've found a solution to the problem of missing first entered symbols. Data can be entered into a cell as soon as the cell is in focus. There is no necessity to press Enter first or double click on a cell before data input.

Class CellField

//Text box cell
public class CellField {
    private static StringBuffer text = new StringBuffer("");
    public static String getText() {
        return text.toString();
    }
    public static void setText(String text) {
        CellField.text = new StringBuffer(text);
    }
    //true, if the length of more than one character
    public static boolean isLessOrEqualOneSym(){
        return CellField.text.length() <= 1;
    }
    //add character to the end of line
    public static void addSymbol(String symbol){
        text.append(symbol);
    }
    public static void clearText() { 
        setText("");
    }
}

Class NewOrderCtrl(part of the code)

class public class NewOrderCtrl extends HBox implements Initializable {
    @FXML private TableView<OrderItem> catalogTable;
    @FXML private TableColumn<OrderItem, String> numCatalogColumn;
    public void initialize(URL url, ResourceBundle resourceBundle) {
            numCatalogColumn.setCellFactory(new Callback<TableColumn<OrderItem, String>, TableCell<OrderItem, String>>() {
            @Override
            public TableCell<OrderItem, String> call(TableColumn<OrderItem, String> orderItemStringTableColumn) {
                return new EditingCell();
            }
        });
            catalogTable.setOnKeyPressed(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent keyEvent) {
                KeyCode keyCode = keyEvent.getCode();
                if (keyCode == KeyCode.ENTER || keyCode == KeyCode.ESCAPE){
                    CellField.clearText();
                }
                if (keyCode.isDigitKey()) {
                    int row = catalogTable.getSelectionModel().getSelectedIndex();
                    catalogTable.edit(row, numCatalogColumn);
                }
            }
        });
    }

    @FXML
    private void onEditStart() {
        CellField.clearText();
    }
}

Class EditingCell

public class EditingCell extends TableCell<OrderItem, String> {
    private TextField textField;
    @Override
    public void startEdit() {
        if (!isEmpty()) {
            super.startEdit();
            if (textField == null) {
                createTextField();
            }
            setText(null);
            setGraphic(textField);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            textField.requestFocus();
        }
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setText(String.valueOf(getItem()));
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }

    @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());
                }
                setGraphic(textField);
                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            } else {
                setText(getString());
                setContentDisplay(ContentDisplay.TEXT_ONLY);
            }
        }
    }

    private void createTextField() {
        textField = new TextField(getString());
        textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
        textField.setOnKeyPressed(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent t) {
                if (t.getCode() == KeyCode.ENTER) {
                    commitEdit(textField.getText());
                    EditingCell.this.getTableView().requestFocus();//why does it lose focus??
                    EditingCell.this.getTableView().getSelectionModel().selectBelowCell();
                } else if (t.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                }
            }
        });

        textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent t) {
                if (t.getCode().isDigitKey()) {
                    if (CellField.isLessOrEqualOneSym()) {
                        CellField.addSymbol(t.getText());
                    } else {
                        CellField.setText(textField.getText());
                    }
                    textField.setText(CellField.getText());
                    textField.deselect();
                    textField.end();
                    textField.positionCaret(textField.getLength() + 2);//works sometimes

                }
            }
        });
    }

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

OTHER TIPS

I've finally got everything working how I like it. I've added some formatting stuff since I needed to test that. Users will have to enter some data and the closer it is to excel the easier it will be for most people to use.

Make a new javaFX project called TableTest in package easyedit and paste these files in the right class names.

TableTest.java

package easyedit;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TableTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        ObservableList<LineItem> items = FXCollections.observableArrayList();
        items.addAll(new LineItem("hello",123.45,6),
                     new LineItem("world",0.01,11));
        TableView table = new EasyEditTable().makeTable(items);

        Button focusableNode = new Button("Nada");

        VBox root = new VBox();
        root.getChildren().addAll(table, focusableNode);
        Scene scene = new Scene(root, 300, 250);
        //css to remove empty lines in table
        scene.getStylesheets().add(this.getClass().getResource("css.css").toExternalForm());

        primaryStage.setTitle("Easy edit table test");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

}

LineItem.java

package easyedit;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class LineItem {

        private final StringProperty desc = new SimpleStringProperty();
        private final DoubleProperty amount = new SimpleDoubleProperty();
        private final IntegerProperty sort = new SimpleIntegerProperty();

        public StringProperty descProperty() {return desc;}
        public DoubleProperty amountProperty() {return amount;}
        public IntegerProperty sortProperty() {return sort;}

        public LineItem(String dsc, double amt, int srt) {
            desc.set(dsc); amount.set(amt); sort.set(srt);
        }
}

EasyEditTable.java

package easyedit;

import java.text.NumberFormat;
import java.util.Stack;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;


public class EasyEditTable{
    private String lastKey = null;

    public TableView makeTable(ObservableList<LineItem> items) {
        TableView tv = new TableView(items);
        tv.setEditable(true);

        Stack<LineItem> deletedLines = new Stack<>();
        tv.setUserData(deletedLines);
        Callback<TableColumn<LineItem,String>, TableCell<LineItem,String>> txtCellFactory = 
                (TableColumn<LineItem,String> p) -> {return new EditingCell();};

        TableColumn<LineItem,String> descCol = new TableColumn<>("desc");
        descCol.setCellValueFactory(new PropertyValueFactory<>("desc"));
        descCol.setCellFactory(txtCellFactory);
        descCol.setOnEditCommit((TableColumn.CellEditEvent<LineItem, String> evt) -> {
            evt.getTableView().getItems().get(evt.getTablePosition().getRow())
                    .descProperty().setValue(evt.getNewValue());
        });


        final NumberFormat currFmt = NumberFormat.getCurrencyInstance();
        TableColumn<LineItem, String> amountCol = new TableColumn<>("amount");
        amountCol.setCellValueFactory((TableColumn.CellDataFeatures<LineItem, String> p) -> {
                return new SimpleStringProperty(currFmt.format(p.getValue().amountProperty().get()));
        });
        amountCol.setCellFactory(txtCellFactory);
        amountCol.setOnEditCommit((TableColumn.CellEditEvent<LineItem, String> evt) -> {
            try {
              evt.getTableView().getItems().get(evt.getTablePosition().getRow())
                        .amountProperty().setValue(Double.parseDouble(evt.getNewValue().replace("$","")));
            } catch (NumberFormatException nfe) {
                //handle error properly somehow
            }
        });
        amountCol.setComparator((String o1, String o2) -> {
            try {//only works in $ countries, use currFmt.parse() instead
                return Double.compare(Double.parseDouble(o1.replace("$", "")),
                                      Double.parseDouble(o2.replace("$", "")));
            } catch (NumberFormatException numberFormatException) {
                return 0;
            }
        });

        TableColumn<LineItem,String> sortCol = new TableColumn<>("sort");
        sortCol.setCellValueFactory(new PropertyValueFactory("sort"));
        sortCol.setCellFactory(txtCellFactory);
        sortCol.setOnEditCommit((TableColumn.CellEditEvent<LineItem, String> evt) -> {
            evt.getTableView().getItems().get(evt.getTablePosition().getRow())
                    .sortProperty().setValue(Integer.parseInt(evt.getNewValue()));//throws nfe
        });

        tv.getColumns().setAll(descCol, amountCol, sortCol);
        tv.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        tv.getSelectionModel().setCellSelectionEnabled(true);
        tv.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);

        tv.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent t) -> {
            if (tv.getEditingCell() == null && t.getCode() == KeyCode.ENTER) {
                if (t.isShiftDown()) {
                    tv.getSelectionModel().selectAboveCell();
                } else {
                    tv.getSelectionModel().selectBelowCell();
                }
                t.consume();
            }
            //I decided not to override the default tab behavior
            //using ctrl tab for cell traversal, but arrow keys are better
            if (t.isControlDown() && t.getCode() == KeyCode.TAB) {
                if (t.isShiftDown()) {
                    tv.getSelectionModel().selectLeftCell();
                } else {
                    tv.getSelectionModel().selectRightCell();
                }
                t.consume();
            }
        });

        tv.setOnKeyPressed((KeyEvent t) -> {
            TablePosition tp;
            if (!t.isControlDown() && 
               (t.getCode().isLetterKey() || t.getCode().isDigitKey())) {
                lastKey = t.getText();
                tp = tv.getFocusModel().getFocusedCell();
                tv.edit(tp.getRow(),tp.getTableColumn());
                lastKey = null;
            }
        });

        tv.setOnKeyReleased((KeyEvent t) -> {
            TablePosition tp;
            switch (t.getCode()) {
                case INSERT:
                    items.add(new LineItem("",0d,0));//maybe try adding at position
                    break;
                case DELETE:
                    tp = tv.getFocusModel().getFocusedCell();
                    if (tp.getTableColumn() == descCol) {
                        deletedLines.push(items.remove(tp.getRow()));
                    } else { //maybe delete cell value
                    }
                    break;
                case Z:
                    if (t.isControlDown()) {
                        if (!deletedLines.isEmpty()) {
                            items.add(deletedLines.pop());
                        }
                    }
            }
        });

        return tv;
    }

    private class EditingCell extends TableCell{

        private TextField textField;

        @Override
        public void startEdit() {
            if (!isEmpty()) {
                super.startEdit();
                createTextField();
                setText(null);
                setGraphic(textField);
                //setContentDisplay(ContentDisplay.GRAPHIC_ONLY); 
                Platform.runLater(() -> {//without this space erases text, f2 doesn't
                    textField.requestFocus();//also selects
                });
                if (lastKey != null) {
                    textField.setText(lastKey);
                    Platform.runLater(() -> {
                        textField.deselect();
                        textField.end();
                    });
                }
            }
        }

        public void commit(){
            commitEdit(textField.getText());
        }

        @Override
        public void cancelEdit() {
            super.cancelEdit();
            try {
                setText(getItem().toString());
            } catch (Exception e) {}
            setGraphic(null);
        }

        @Override
        public void updateItem(Object 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);
                if (getTableColumn().getText().equals("amount"))
                setAlignment(Pos.CENTER_RIGHT);
            }
        } 

        private void createTextField() {
            textField = new TextField(getString());

            //doesn't work if clicking a different cell, only focusing out of table
            textField.focusedProperty().addListener(
                    (ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) -> {
                if (!arg2) commitEdit(textField.getText());
            });

            textField.setOnKeyReleased((KeyEvent t) -> {
                if (t.getCode() == KeyCode.ENTER) {
                    commitEdit(textField.getText());
                    EditingCell.this.getTableView().getSelectionModel().selectBelowCell();
                }
                if (t.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                }
            });

            textField.addEventFilter(KeyEvent.KEY_RELEASED, (KeyEvent t) -> {
                if (t.getCode() == KeyCode.DELETE) {
                    t.consume();//stop from deleting line in table keyevent
                }
            });
        }

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

}

The css.css file if you want to use it. It goes in the same package.

.table-row-cell:empty {
    -fx-background-color: ivory;
}

.table-row-cell:empty .table-cell {
    -fx-border-width: 0px;
}

I don't have any problems with characters not showing up or blank cells. Just the blinking cursor is sometimes in the wrong place. Using 8-b127 on XP sp3. I don't like how the textField focusListener doesn't work very well, but it's a small issue.

For the TableView (named tv here) I do this

   tv.setOnKeyReleased((KeyEvent t) -> {
        TablePosition tp;
        switch (t.getCode()) {
            //other code cut out here
            case Z:
                if (t.isControlDown()) {
                    if (!deletedLines.isEmpty()) {
                        items.add(deletedLines.pop());
                    }
                    break; //don't break for regular Z
                }
            default: 
                if (t.getCode().isLetterKey() || t.getCode().isDigitKey()) {
                    lastKey = t.getText();
                    tp = tv.getFocusModel().getFocusedCell();
                    tv.edit(tp.getRow(), tp.getTableColumn());
                    lastKey = null;
                }
        }
    });

And then when I make the TextField editing cell

@Override
    public void startEdit() {
        if (!isEmpty()) {
            super.startEdit();
            createTextField();
            setText(null);
            setGraphic(textField);
            Platform.runLater(() -> {//without this space erases text, f2 doesn't
                textField.requestFocus();//also selects
            });
            if (lastKey != null) {
                textField.setText(lastKey);
                Platform.runLater(() -> {
                    textField.deselect();
                    textField.end();
                    textField.positionCaret(textField.getLength()+2);//works sometimes
                });
            }
        }
    }

Sometimes the blinking cursor shows up at the front of lastKey but when I keep typing the characters go at the end and the cursor moves to the correct position. If you type really fast, the second character gets ignored.

If you can make it better let me know. I'd like it to work more like excel. I also add this to the standard textField code.

    textField.setOnKeyReleased((KeyEvent t) -> {
        if (t.getCode() == KeyCode.ENTER) {
            commitEdit(textField.getText());
            EditingCell.this.getTableView().requestFocus();//why does it lose focus??
            EditingCell.this.getTableView().getSelectionModel().selectBelowCell();
        } else if (t.getCode() == KeyCode.ESCAPE) {
            cancelEdit();
        }
    });
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top