# TableView EditingCell reagiert komisch



## Goldfish (25. Feb 2014)

Hi Leute, 
ich habe mir nach Vorlage des Tutorials für editierbare Zellen von hier folgende Klasse gebastelt und diese in meiner TableView eingebunden.

Ich hab die EditingCell Klasse an meine entsprechenden Bedürfnisse angepasst


```
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
/**
 *
 * @author Graham Smith
 */
public class EditingCell<T, String> extends TableCell<T, String> {

    private TextField textField;

    public EditingCell() {
        this.setId("editingCell");
    }


    @Override
    public void startEdit() {
        System.out.println("edit");
        super.startEdit();
        if (textField == null) {
            createTextField();
        }
        setGraphic(textField);
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                textField.requestFocus();
                textField.selectAll();
            }
        });
    }

    @Override
    public void cancelEdit() {
        System.out.println("cancel");
        super.cancelEdit();
        setText((java.lang.String) getItem());
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }

    @Override
    public void updateItem(String item, boolean empty) {
        System.out.println("update");
        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((String) textField.getText());
                } else if (t.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                } else if (t.getCode() == KeyCode.TAB) {
                    commitEdit((String) textField.getText());
                    TableColumn nextColumn = getNextEditableColumn(!t.isShiftDown());
                    if (nextColumn != null) {
                        getTableView().edit(getTableRow().getIndex(), nextColumn);
                    }
                }
            }
        });
        textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if (!newValue && textField != null) {
//                    commitEdit((String) textField.getText());
                }
            }
        });
    }

    private java.lang.String getString() {
        return getItem() == null ? "" : getItem().toString();
    }
    /**
     *
     * @param forward true gets the column to the right, false the column to the left of the current column
     * @return
     */
    private TableColumn<T, ?>getNextEditableColumn(boolean forward) {
        List<TableColumn<T, ?>> columns = new ArrayList();
        for (TableColumn<T, ?> column : getTableView().getColumns()) {
            columns.addAll(getLeaves(column));
        }
        //There is no other column that supports editing.
        if (columns.size() < 2) {
            return null;
        }
        int currentIndex = columns.indexOf(getTableColumn());
        int nextIndex = currentIndex;
        if (forward) {
            nextIndex++;
        } else {
            nextIndex--;
        }
        if (nextIndex < 0 || nextIndex >= columns.size()) {
            return null;
        } else {
            int x = 0;
            for (Node editingCell: getTableRow().lookupAll("#editingCell")) {
                if (editingCell instanceof EditingCell && x >= nextIndex) {
                    return columns.get(x);
                }
                x++;
            }
        }
        return null;
    }

    private List<TableColumn<T, ?>> getLeaves(TableColumn<T, ?> root) {
        List<TableColumn<T, ?>> columns = new ArrayList();
        if (root.getColumns().isEmpty()) {
            //We only want the leaves that are editable.
            if (root.isEditable()) {
                columns.add(root);
            }
            return columns;
        } else {
            for (TableColumn<T, ?> column : root.getColumns()) {
                columns.addAll(getLeaves(column));
            }
            return columns;
        }
    }
}
```

Eingesetzt hab ich das in meinen Code folgendermaßen:


```
@FXML
        TableView overview;

        ...

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

        TableColumn column = overview.getColumns().get(0);
        column.setCellFactory(cellFactory);

        column = overview.getColumns().get(2);
        column.setCellFactory(cellFactory);
```

Was hier passieren soll, ist folgendes:
Wenn ich eine der editierbaren Zellen in meinem Table anklicke, will ich auf Tastendruck "Tab" in die nächste editierbare Zelle springen. Das hatte ich als echt harte Nuss rausgestellt, aber das klappt soweit. (Aber wenn jemand eine schönere Lösung sieht, bitte immer her damit, da ich mit dieser ziemlich unzufrieden bin...)

Das Problem was ich jetzt habe ist, dass sich die Werte in meinen Zellen nicht zuverlässig ändern. Das erste mal klappt es den Wert zu ändern. Ab dem zweiten Versuch klappt es nur noch sporadisch und in der Spalte mit Index 2 klappt es gar nicht, obwohls ja genau dasselbe ist... Wenn ich die Zelle editiere, will ich einfach auf Enter drücken und zack wird mein Wert angezeigt. Aber diese TableView ist so dermaßen widerwillig, dass ich langsam echt die Motivation verliere... 

Wäre super, wenn mir da jemand aus der Patsche helfen könnte.


----------



## dzim (25. Feb 2014)

Ich schau mal morgen rein. Ich hab auch mal einen eigenen Cell Editor geschrieben, aber das "Tab"-Szenario hat für mich dabei keine Rolle gespielt. Wenn ich was hinbekommen sollte, würde ich mich noch einmal melden.

Grüsse


----------



## Goldfish (26. Feb 2014)

ich muss noch eine Info hinzufügen. Ich sitz da gerade wieder dran und habe folgendes festgestellt:

Wenn ich meinem Model Werte zuweise, bevor es in der TableView angezeigt wird, funktioniert die Zelle, so wie sie es soll.
Sind die Felder leer (Ich verwende im Model nur SimpleStringProperties), sind folgende Bedingungen gegeben:

 Bei Klick auf die entsprechende Zelle wird wie gewohnt die startEdit-Methode von TableCell aufgerufen.
 Wenn ich aus der Zelle herausklicke, wird weder die update-Methode noch die cancel-Methode von TableCell aufgerufen...
 ich habe dem TextFeld, das ich in die Zelle eingebaut habe, einen Focus-Listener gegeben und zwinge den commit in diesem Falle herbei... zumindest sollte es so sein, der call der commit-Methode wird ignoriert...


mir ist gerade aufgefallen, dass auch die Methode getItem() in meiner TableCell lediglich immer nur null zurückgibt. Entsprechend betrachtet er das ganze wohl nicht als ObservableValue... aber die SimpleStringProperty Objekte sind alle initialisert... wieso geben diese trotzdem null zurück?


----------



## Goldfish (27. Feb 2014)

habs gelöst bekommen.
Für alle die am Code interessiert sind, geb ich den hier mal raus.

Ich hab jetzt eine editierbare Zelle geschrieben, welche mittels CellFactory an jede beliebige Spalte übergeben werden kann.
Auf der TableView ist einfache Navigation mittels Pfeiltasten machbar, man kann durch die editierbaren Zellen durchtabben (dabei werden nur die Zellen selektiert, die auch wirklich vom gleichen Typ sind, wie die, die ich hier anbiete. Zellen anderer Form werden übersprungen.)

Der Grund letztlich dafür, dass das nicht geklappt hatte, war halt wirklich der, dass ich quasi null-Values an die TableView übergeben hatte, indem ich meine Variablen im Konstruktor des Models nicht initialisiert habe. Oder bei SimpleStringProperty keinen leeren String als Value eingegeben hatte.

Also für eine editierbare TableView, kann ich folgendes anbieten. HAt bisher alle meine Tests bestanden. Ich hoffe, dass da keine anderen Bugs drinnen versteckt sind XD


```
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

import java.util.*;
import java.util.List;

/**
 * user: pknueppe
 * created at: 26.02.14.
 */
public class EditableCell<T, S> extends TableCell<T, S> {

    private final static String idName = "privateFormattedTableCell";

    private TextField textField;
    private Coordinate coordinate;

    public EditableCell() {
        setId(idName);
    }

    @Override
    public void startEdit() {
        super.startEdit();
        if (textField == null) {
            createTextField();
        }
        setGraphic(textField);
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                textField.requestFocus();
                textField.selectAll();
            }
        });
    }

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

    @Override
    public void updateItem(S item, boolean empty) {
        // Wann immer ein update eintritt, soll die Position mit angepasst werden.
        setCoordinates();

        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 setCoordinates() {
        int row = getTableRow().getIndex();
        int column = getTableView().getColumns().indexOf(getTableColumn());
        coordinate = new Coordinate(row, column);
    }

    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) {
                TableView.TableViewSelectionModel selectionModel = getTableView().getSelectionModel();
                TableColumn column = getTableColumn();
                int row = getTableRow().getIndex();

                if (t.getCode() == KeyCode.ENTER) {
                    commitEdit((S) textField.getText());
                    getTableView().getSelectionModel().select(row, column);
                } else if (t.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                    TablePosition position = (TablePosition) selectionModel.getSelectedCells().get(0);
                    getTableView().edit(position.getRow(), getTableColumn());
                } else if (t.getCode() == KeyCode.TAB) {
                    commitEdit((S) textField.getText());
                    selectNextEditableCell();
                } else if (t.getCode() == KeyCode.UP) {
                    selectionModel.selectAboveCell();
                    TablePosition position = (TablePosition) selectionModel.getSelectedCells().get(0);
                    getTableView().edit(position.getRow(), getTableColumn());
                } else if (t.getCode() == KeyCode.DOWN) {
                    selectionModel.selectBelowCell();
                    TablePosition position = (TablePosition) selectionModel.getSelectedCells().get(0);
                    getTableView().edit(position.getRow(), getTableColumn());
                } else if (t.getCode() == KeyCode.LEFT) {
                    selectionModel.selectLeftCell();
                    TablePosition position = (TablePosition) selectionModel.getSelectedCells().get(0);
                    column = getTableView().getColumns().get(position.getColumn());
                    getTableView().edit(position.getRow(), column);
                } else if (t.getCode() == KeyCode.RIGHT) {
                    selectionModel.selectRightCell();
                    TablePosition position = (TablePosition) selectionModel.getSelectedCells().get(0);
                    column = getTableView().getColumns().get(position.getColumn());
                    getTableView().edit(position.getRow(), column);
                }
            }
        });
        textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if (!newValue && textField != null) {
                    commitEdit((S) textField.getText());
                }
            }
        });
    }

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

    private void selectNextEditableCell() {
        TableView.TableViewSelectionModel selectionModel = getTableView().getSelectionModel();

        if (selectionModel.getSelectionMode() == SelectionMode.SINGLE ||
            selectionModel.getSelectedCells().size() == 1) {
            TablePosition tablePosition = getTableView().getSelectionModel().getSelectedCells().get(0);
            selectEditableCellInRow(tablePosition.getColumn() + 1);
        }
    }

    private boolean hasDoneRecursion = false;
    private boolean selectEditableCellInRow(int columnStartIndex) {
        TableView.TableViewSelectionModel selectionModel = getTableView().getSelectionModel();

        TableRow tableRow = getTableRow();
        ArrayList<Node> set = new ArrayList<Node>();
        set.addAll(tableRow.lookupAll("#" + idName));

        int newSelection = columnStartIndex * 2;

        for (int y = newSelection; y < set.size(); y += 2) {
            if (set.get(y) instanceof EditableCell) {
                EditableCell editingCell = (EditableCell) set.get(y);
                TableColumn tableColumn = getTableView().getColumns().get(editingCell.getCoordinate().getColumn());
                if (hasDoneRecursion) {
                    selectionModel.select(editingCell.getCoordinate().getRow() + 1, tableColumn);
                    getTableView().edit(editingCell.getCoordinate().getRow() + 1, tableColumn);
                    hasDoneRecursion = false;
                    return true;
                } else {
                    selectionModel.select(editingCell.getCoordinate().getRow(), tableColumn);
                    getTableView().edit(editingCell.getCoordinate().getRow(), tableColumn);
                    hasDoneRecursion = false;
                    return true;
                }
            }
        }

        int row = tableRow.getIndex();
        if (row+1 < getTableView().getItems().size()) {
            hasDoneRecursion = true;
            return selectEditableCellInRow(0);
        }else {
            hasDoneRecursion = false;
        }
        return false;
    }

    public Coordinate getCoordinate() {
        return coordinate;
    }

    @Override
    public String toString() {
        return getCoordinate() + " - " + getItem();
    }

    private class Coordinate {
        private int row;
        private int column;

        public Coordinate(int row, int column) {
            this.row = row;
            this.column = column;
        }

        public int getRow() {
            return row;
        }

        public int getColumn() {
            return column;
        }

        @Override
        public String toString() {
            return "Coordinate:[" + row + ", " + column + "]";
        }
    }
}
```


----------



## dzim (27. Feb 2014)

Danke für den Code.

Kannst du aber noch bitte den Thread als Erledigt markieren (unter dem letzten Post, zweiter Button von links)?


----------

