6

I have a table column with datatype Integer. i want to make this column editable without changing the datatype to String anywhere. I used textfieldtablecell but it commits value only on pressing enter. So i want some other method. Please suggest something. To make it [possible I have done something like this. But my cell is not becoming editable.

public class EditCell implements initializable{

 @FXML
    private TableView<ResourceMaster> roletable;  
@FXML
    private TableColumn<ResourceMaster, Integer> loadedHrs;
@Override
    public void initialize(URL location, ResourceBundle resources) {

Callback<TableColumn<ResourceMaster,Integer>, TableCell<ResourceMaster,Integer>> txtCellFactory = 
                (TableColumn<ResourceMaster,Integer> p) -> {return new EditingCell();};

loadedHrs.setCellFactory(txtCellFactory);
} 

   public class EditingCell extends TableCell<ResourceMaster, Integer> {
        private TextField textField;
        @Override
        public void startEdit() {
            if (!isEmpty()) {
                super.startEdit();
                if (textField == null) {
                    createTextField();
                }
               // setText(null);
                commitEdit(Integer.valueOf((textField.getText())));
                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(Integer item, boolean empty) {
            super.updateItem(item, empty);
            if (empty) {
                //setText(null);
                setGraphic(null);
            } else {
                if (isEditing()) {
                    if (textField != null) {
                        textField.setText(String.valueOf(getString()));
                    }
                    setGraphic(textField);
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                } else {
                    setText(String.valueOf(getString()));
                    setContentDisplay(ContentDisplay.TEXT_ONLY);
                }
            }
        }

        private void createTextField() {
            textField = new TextField(String.valueOf(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(Integer.valueOf(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 Integer getString() {
            return getItem();
        }
}
}
Harshita Sethi
  • 2,035
  • 3
  • 24
  • 46
  • Have you tried adapting example 13-11 from the [tutorial](http://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm#CJAGAAEE)? (You should basically only need to convert the value before calling `commitEdit(...)`.) If so, please post your code and explain how it behaves differently to what you expect. – James_D Jan 12 '15 at 12:06
  • I tried this method but It is not solving my purpose. If I have integer value as 0 then on converting it to String will make it null and show nothing. So I want to work on integer itself and not convert it to String at any point of time. – Harshita Sethi Jan 13 '15 at 04:45
  • What control are you going to use to edit it? – James_D Jan 13 '15 at 04:46
  • Mouse Click. I have uploaded the code. You can check that. – Harshita Sethi Jan 13 '15 at 05:23
  • I meant what *control*. The point is if you use a text field, you have to represent the data as a string, at least during editing. I don't really understand your code - why are you calling `commitEdit(...)` from `startEdit()`. And I don't really know what the key listeners are for on the text field, especially the `keyReleased` handler. – James_D Jan 13 '15 at 05:36
  • Actually whenever I was editing anything in the cell, It was displaying only if I click somewhere in the same row otherwise the data was vanishing. My table has more than 100 rows. So to hold that data on clicking anywhere on the table I had to use key released and commit edit. – Harshita Sethi Jan 13 '15 at 07:56

2 Answers2

11

Just use a cell factory that creates a text field in the cell when editing. To commit the edit, just parse the text from the text field.

Example:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.regex.Pattern;

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class EditableTableColumnWithInteger extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Item> table = new TableView<>();
        table.setEditable(true);
        table.getItems().addAll(createData());

        TableColumn<Item, String> nameCol = new TableColumn<>("Name");
        nameCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());

        TableColumn<Item, Number> valueCol = new TableColumn<>("Value");
        valueCol.setCellValueFactory(cellData -> cellData.getValue().valueProperty());

        valueCol.setCellFactory(col -> new IntegerEditingCell());

        table.getColumns().add(nameCol);
        table.getColumns().add(valueCol);

        Button dataDumpButton = new Button("Dump data");
        dataDumpButton.setOnAction( e -> 
            table.getItems().stream().map(item -> item.getName()+":"+item.getValue()).forEach(System.out::println));
        HBox controls = new HBox(5, dataDumpButton);
        controls.setPadding(new Insets(10));
        controls.setAlignment(Pos.CENTER);

        primaryStage.setScene(new Scene(new BorderPane(table, null, null, controls, null), 600, 400));
        primaryStage.show();
    }

    public class IntegerEditingCell extends TableCell<Item, Number> {

        private final TextField textField = new TextField();
        private final Pattern intPattern = Pattern.compile("-?\\d+");

        public IntegerEditingCell() {
            textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
                if (! isNowFocused) {
                    processEdit();
                }
            });
            textField.setOnAction(event -> processEdit());
        }

        private void processEdit() {
            String text = textField.getText();
            if (intPattern.matcher(text).matches()) {
                commitEdit(Integer.parseInt(text));
            } else {
                cancelEdit();
            }
        }

        @Override
        public void updateItem(Number value, boolean empty) {
            super.updateItem(value, empty);
            if (empty) {
                setText(null);
                setGraphic(null);
            } else if (isEditing()) {
                setText(null);
                textField.setText(value.toString());
                setGraphic(textField);
            } else {
                setText(value.toString());
                setGraphic(null);
            }
        }

        @Override
        public void startEdit() {
            super.startEdit();
            Number value = getItem();
            if (value != null) {
                textField.setText(value.toString());
                setGraphic(textField);
                setText(null);
            }
        }

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

        // This seems necessary to persist the edit on loss of focus; not sure why:
        @Override
        public void commitEdit(Number value) {
            super.commitEdit(value);
            ((Item)this.getTableRow().getItem()).setValue(value.intValue());
        }
    }

    private List<Item> createData() {
        Random rng = new Random();
        List<Item> items = new ArrayList<>();
        for (int i=1; i<=20; i++) {
            items.add(new Item("Item "+i, rng.nextInt(20)));
        }
        return items ;
    }

    public static class Item {
        private final StringProperty name = new SimpleStringProperty();
        private final IntegerProperty value = new SimpleIntegerProperty();
        public Item(String name, int value) {
            this.setName(name);
            this.setValue(value);
        }
        public final StringProperty nameProperty() {
            return this.name;
        }
        public final java.lang.String getName() {
            return this.nameProperty().get();
        }
        public final void setName(final java.lang.String name) {
            this.nameProperty().set(name);
        }
        public final IntegerProperty valueProperty() {
            return this.value;
        }
        public final int getValue() {
            return this.valueProperty().get();
        }
        public final void setValue(final int value) {
            this.valueProperty().set(value);
        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • I used your exact code what you mentioned here. But it is not making my column editable. When I'm clicking on the cell, text field is not appearing. – Harshita Sethi Jan 13 '15 at 09:22
  • It works fine for me. Double-click one of the (non-empty) cells in the "Value" column and it switches to a text field. I tested on Java 8u20, 8u25, and 8u40 (ea). – James_D Jan 14 '15 at 12:46
  • This works for me but when I use it for a float datatype, I have to give a the number in decimal otherwise it wont accept the number. And how do I get the value from the edited cell then when its edited? – viper Dec 16 '15 at 17:23
  • The edited value is going to be in the `Item` that backs the table row. Can't answer the question about using this with a float, as I don't know what changes you made to the code. – James_D Dec 16 '15 at 17:36
  • This does not work properly and looks like a super ugly work around. I wonder why so many people accept this answer instead of filing a change request with JavaFX to get their act together. In 2001 David Gamma publicly already complained about the unnecessary complexity of the Java Swing JTable. The "new" Java FX TableView design is ridiculously complex again. – Wolfgang Fahl Jul 20 '17 at 19:14
  • @WolfgangFahl Explain "does not work properly" - I cannot fix it without knowing what is not working about it. In what sense is it a workaround? A workaround for what? (This is not really a suitable place for you to take out your frustrations with the library: we are all end-use programmers here who have to live with the languages and libraries we have; we are not, for the most part, the people who wrote the library.) – James_D Jul 20 '17 at 19:16
  • Exception in thread "JavaFX Application Thread" java.lang.NullPointerException at javafx.scene.control.TableColumn.lambda$new$32(TableColumn.java:281) at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86) ... at javafx.scene.control.TableCell.commitEdit(TableCell.java:349) – Wolfgang Fahl Jul 21 '17 at 03:38
  • @WolfgangFahl I can't reproduce that exception. Which line in the code I posted is appearing in the stack trace? When does the exception occur, relative to user input? – James_D Jul 21 '17 at 09:45
  • ((Item)this.getTableRow().getItem()).setValue(value.intValue()); - i split this line into two and made sure that Item is non null to try to fix the issue. The real "bug" seems to be something else. A changed value does not immediately appear but only after some other action e.g. scrolling. Only if i press enter can i be sure the value is committed. What was the designed behavior? Should a value be committed on changing focus? If yet the bug is that the value is commited but not immediately shown. – Wolfgang Fahl Jul 21 '17 at 11:08
  • @WolfgangFahl The aim was to commit on loss of focus. This is [notoriously difficult](https://stackoverflow.com/questions/24694616/how-to-enable-commit-on-focuslost-for-tableview-treetableview) with the current versions of JavaFX - there are supposed to be mechanisms introduced in JDK 9 for this (I haven't checked yet if they are actually there). This code works for me on the current version: which JDK version do you have? – James_D Jul 21 '17 at 13:25
1

You can create table cell with StringConverter, that will convert your object to cell presentation. You can realize own StringConverter for your class

cell.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<AnyClass>() {

        @Override
        public String toString(AnyClass object) {
            return null;
        }

        @Override
        public AnyClass fromString(String string) {
            return null;
        }
    }));

Also JavaFX contains some default converters. E.g. IntegerStringConverter for your case,

TextFieldTableCell.forTableColumn(new IntegerStringConverter())
Egor
  • 1,334
  • 8
  • 22
  • While this code snippet may solve the question, [including an explanation](//meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – rene Jun 09 '19 at 06:49
  • @rene thx, I edited my answer. I thought that this is not so necessary because the question is old. But really it can help the people who work with it now. – Egor Jun 09 '19 at 13:43