Duck Duck - 3 months ago 20
Java Question

Calling cancelEdit to all cells in a TableView JavaFx

I am using a custom editable table cell factory that I have been working on for some time. I am currently implementing a functionality to allow single clicking to begin editing.

I have the single click starting the cell edit, however if you click another cell once, the previous doesn't close. My idea was to loop over all cells calling

cancelEdit()
just before opening the clicked cell to editing.

Below is the entire cell class I am using (that works fine with double-click to edit) The section I am working on is in the constructor.

public class EditingCell<S, T> extends TableCell<S, T> {
private TextField textField;

public EditingCell() {
TableView<S> table = this.getTableView();
addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
for(TableColumn<S, ?> col : table.getColumns()) {
// cancelEdit() on all cells here
}
startEdit();
}
});
}

public void commit(Object val) {
commit(val, this.getTableRow().getIndex(), getTableView().getColumns().indexOf(this.getTableColumn()));
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public void commit(Object val, int row, int col) {

// Get the table
TableView<S> t = this.getTableView();

// Get the selected row/column
S selectedRow = t.getItems().get(row);
if (selectedRow == null)
return;
TableColumn<S, ?> selectedColumn = t.getColumns().get(col);

// Get current property name
String propertyName = ((PropertyValueFactory) selectedColumn.getCellValueFactory()).getProperty();

// Create a method name conforming to java standards ( setProperty )
propertyName = ("" + propertyName.charAt(0)).toUpperCase() + propertyName.substring(1);

// Try to run the update
try {

// Type specific checks - could be done inside each
// setProperty() method
if (val instanceof Double) {
Method method = selectedRow.getClass().getMethod("set" + propertyName, double.class);
method.invoke(selectedRow, (double) val);
}
if (val instanceof String) {
Method method = selectedRow.getClass().getMethod("set" + propertyName, String.class);
method.invoke(selectedRow, (String) val);
}
if (val instanceof Integer) {
Method method = selectedRow.getClass().getMethod("set" + propertyName, int.class);
method.invoke(selectedRow, (int) val);
}

} catch (Exception e) {
e.printStackTrace();
}

// CommitEdit for good luck
commitEdit((T) val);

TableUtils.Refresh(t, t.getItems());

}

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

@Override
public void cancelEdit() {
super.cancelEdit();

String val = "0.0";
if (!textField.getText().equals(""))
val = "" + Double.parseDouble(textField.getText());

setText(NumberUtils.roundTo2(NumberUtils.parseDouble(val)) + "");
setGraphic(null);

}

@Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);

if (empty) {
setText("");
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(NumberUtils.roundTo3(NumberUtils.parseDouble(getString())) + "");
setGraphic(null);
}
}
}

@SuppressWarnings({ "rawtypes" })
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) {
if (textField.getText().equals(""))
commit(0.0);
else {
double val = Double.parseDouble(textField.getText());
commit(val);
}
}
}
});
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
textField.setText("" + getItem());
cancelEdit();
event.consume();
}
// Navigate up and down in table
else if (event.getCode() == KeyCode.ENTER) {

TableView<S> table = getTableView();
int row = table.getEditingCell().getRow();

// Commit changes
if (textField.getText().equals(""))
commit(0.0);
else {
double val = Double.parseDouble(textField.getText());
commit(val);
}

// Do move
if (event.isShiftDown())
table.edit(row - 1, getTableColumn());
else {
table.edit(row + 1, getTableColumn());
}

}

// Move left and right in table
else if (event.getCode() == KeyCode.TAB) {
TableView<S> table = getTableView();
int row = table.getEditingCell().getRow();

// Save changes
if (textField.getText().equals(""))
commit(0.0);
else {
double val = Double.parseDouble(textField.getText());
commit(val);
}

ArrayList<TableColumn<S, ?>> cols = new ArrayList<TableColumn<S, ?>>();
int index = 0;
for (TableColumn<S, ?> c : table.getColumns()) {
if (c.isVisible() && c.isEditable())
cols.add(c);
if (c == getTableColumn())
index = cols.size() - 1;
}

// Do move
if (event.isShiftDown()) {
try {
TableColumn<S, ?> prevCol = cols.get(index - 1);
table.edit(row, prevCol);
} catch (Exception e) {
}
} else {
try {
TableColumn<S, ?> nextCol = cols.get(index + 1);
table.edit(row, nextCol);
} catch (Exception e) {
}
}
}
});
textField.setTextFormatter(new TextFormatter<String>((Change c) -> {
String text = c.getText();

TableView<S> table = getTableView();
if (table.getSelectionModel().getSelectedCells().size() == 0) {
return c;
}
TablePosition pasteCellPosition = table.getSelectionModel().getSelectedCells().get(0);
int clipRow = 0;
int clipCol = 0;

String curCellText = text;

if (text.contains("\t") || text.contains("\n")) {

StringTokenizer row = new StringTokenizer(text, "\n");
boolean hasRun = false;

while (row.hasMoreTokens() || !hasRun) {
String r = row.nextToken();
StringTokenizer col;
if (r != null)
col = new StringTokenizer(r, "\t");
else
col = new StringTokenizer(text, "\t");

hasRun = true;
clipCol = 0;

while (col.hasMoreTokens()) {

String content = col.nextToken();

if (clipRow == 0 && clipCol == 0) {
curCellText = content;
}

// calculate the position in the table cell
int rowTable = pasteCellPosition.getRow() + clipRow;
int colTable = pasteCellPosition.getColumn() + clipCol;

// Skip hidden columns before current cell
for (int i = 0; i < colTable; i++)
if (!table.getColumns().get(i).isVisible())
colTable++;

// Skip hidden columns between current and goal
while (!table.getColumns().get(colTable).isVisible()) {
if (colTable >= table.getColumns().size())
break;
colTable++;
}

// Add row if we reach the end
if (rowTable >= table.getItems().size()) {
if (table.getItems().get(0) instanceof EditableTableRow)
((EditableTableRow) table.getItems().get(0)).addRowToTable(table);

// continue;
}
if (colTable >= table.getColumns().size()) {

continue;
}

try {
double val = Double.parseDouble(content);
commit(val, rowTable, colTable);

} catch (Exception e) {
try {
int val = Integer.parseInt(content);
commit(val, rowTable, colTable);
} catch (Exception e2) {
commit(content, rowTable, colTable);
}
}

clipCol++;
}

clipRow++;
}
}
c.setText(curCellText);

return c;
}));
textField.requestFocus();
textField.selectAll();
}

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

Answer

You can't iterate over all cells; you have no way of knowing what cells have been created, which represent actual items, etc.

You can call

getTableView().edit(-1, null);

which will have the effect of canceling editing. (See Javadocs.) It may actually be better just to call

getTableView().edit(getIndex(), getTableColumn());

instead of calling startEdit(). This will (should? I haven't tested...) invoke startEdit() for you. In other words, do

public EditingCell() {
    addEventFilter(MouseEvent.MOUSE_CLICKED, e -> 
        getTableView().edit(getIndex(), getTableColumn()));
}

As an aside, why do you go to all the trouble of making the cell implementation very general, but then assuming the cell value factory is an instance of PropertyValueFactory? Surely you can get the cell observable value with

ObservableValue<T> obs = getTableColumn().getCellObservableValue(getTableView().getItems().get(getIndex()));

and then just do

if (obs instanceof WritableValue) {
    ((WritableValue<?>) obs).setValue(val);
}
Comments