How to make a JavaFX TableView cell editable without first pressing Enter? - javafx

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.

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();
}
}

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();
}
});

Related

How to set text and color of multiple ListView items when clicked

I know there are many related questions about this but maybe I'm missing something because I can't get the behavior I'm expecting, to work.
#FXML
private ListView<String> guiList;
void performAction(Actions action) {
try {
Task<String> task = new Task<>() {
#Override
public String call() {
String mySelection = Context.getInstance().getSelected();
ArrayList<String> selectedList = Context.getInstance().getItemsClicked();
if (selectedList == null) {
selectedList = new ArrayList<>();
}
selectedList.add(mySelection);
Context.getInstance().setItemsClicked(selectedList);
guiList.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> param) {
ListCell<String> cell = new ListCell<String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if(item != null && item.matches(mySelection)) {
setText(mySelection + " [" + action + "]");
setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, Font.getDefault().getSize()));
setStyle("-fx-text-fill: green;");
} else {
setText(item);
}
}
};
return cell;
}
});
return "";
}
};
} catch (Exception e) {
}
}
When I click in an item of guiList, the text is changed, gets bold and shows in green color but I don't understand why I need the else statement. If I don't use it, all the other items of the list disappear.
I ask this because I want to change ALL of the items I click and in the current behavior, the changes are only made in the last one clicked.
Here is on approach. Use an object that has a Boolean variable to keeps up with if the item has been selected.
KeyCode 1
lvMain.getSelectionModel().selectedItemProperty().addListener(((ov, t, t1) - > {
if (t1 != null) {
t1.setSelected(true);
}
}));
Key Code 2
lvMain.setCellFactory(lv - > new ListCell < MyItem > () {
#Override
public void updateItem(MyItem item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(item.getText());
if (item.isSelected()) {
setTextFill(Color.RED);
}
}
}
});
Main
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class App extends Application {
#Override
public void start(Stage primaryStage) {
ListView<MyItem> lvMain = new ListView();//Create ListView
lvMain.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);//Change ListView selection mode to multiple
ObservableList<MyItem> items = FXCollections.observableArrayList(new MyItem("Julia"), new MyItem("Ian"), new MyItem("Sue"), new MyItem("Matthew"), new MyItem("Hannah"));//ObseravableList that will be used to set the ListView
lvMain.setItems(items);//Set the ListView's items
lvMain.setCellFactory(lv -> new ListCell<MyItem>()
{
#Override
public void updateItem(MyItem item, boolean empty)
{
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
}
else {
setText(item.getText());
if(item.isSelected())
{
setTextFill(Color.RED);
}
}
}
});
lvMain.getSelectionModel().selectedItemProperty().addListener(((ov, t, t1) -> {
if(t1 != null)
{
t1.setSelected(true);
}
}));
VBox vbox = new VBox();
vbox.getChildren().addAll(lvMain);
StackPane root = new StackPane();
root.getChildren().add(vbox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
MyItem
/**
*
* #author Sed
*/
public class MyItem {
private String text;
private boolean selected;
public MyItem(String text) {
this.text = text;
this.selected = false;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean isSelected) {
this.selected = isSelected;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
Output
I think a better solution would be to use the ListView's built in multiple selection or have your cells have a ToggleButton. When the ToggleButton is on, change the color of the text. When it is off, change the color back to it's original state.

JavaFX TableView get row from button cell

Referring to question JavaFX TableView custom cell rendering split menu button, i'm able to render split menu button in every row. I've updated my code as suggested by James_D and in the answer by Keyur Bhanderi.
The question is about get value of the row where split menu is located without selecting row before click.
Update: Added images to view output
The images below show the output, every button i click.
Updated SplitMenuCellFactory.java
package com.example.splimenubtn;
import java.util.List;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public class SplitMenuCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private List<MenuItemFactory<T>> menuItems;
public SplitMenuCellFactory() {}
public SplitMenuCellFactory(List<MenuItemFactory<T>> items) {
menuItems = items;
}
#Override
public TableCell<S, T> call(TableColumn<S, T> param) {
return new TableCell<S, T>() {
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
if (getTableRow() != null) {
setGraphic(new SplitMenuButtonFactory<>(menuItems, getTableRow().getIndex()).buildButton());
}
}
}
};
}
}
Update, adding missing class
SplitMenuButtonFactory.java
package com.example.splimenubtn;
import java.util.List;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitMenuButton;
public class SplitMenuButtonFactory<T> {
private List<MenuItemFactory<T>> menuItems;
private int rIndex = 0;
public SplitMenuButtonFactory(List<MenuItemFactory<T>> items) {
menuItems = items;
}
public SplitMenuButtonFactory(List<MenuItemFactory<T>> items, int rI) {
menuItems = items;
rIndex = rI;
}
public SplitMenuButton buildButton() {
SplitMenuButton menuBtn = new SplitMenuButton();
// menuBtn.getItems().addAll(menuItems);
for (MenuItemFactory<?> mIF : menuItems) {
MenuItem btn = mIF.setRowIndex(rIndex).buildMenuItem();
if (mIF.isDefault()) {
menuBtn.setText(btn.getText());
menuBtn.setOnAction(btn.getOnAction());
}
menuBtn.getItems().add(btn);
}
return menuBtn;
}
}
MenuItemsFactory.java
package com.example.splimenubtn;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableView;
public class MenuItemFactory<S> {
private MenuItemActions itemType;
private String itemLbl;
private TableView<S> table;
private boolean defaultAction;
private int rIndex = 0;
public MenuItemFactory(MenuItemActions itemType, String itemLabel, boolean dA) {
this.itemType = itemType;
itemLbl = itemLabel;
defaultAction = dA;
}
public MenuItemFactory(MenuItemActions itemType, String itemLabel, TableView<S> t, boolean dA) {
this.itemType = itemType;
itemLbl = itemLabel;
defaultAction = dA;
table = t;
}
public MenuItemFactory<S> setDataList(TableView<S> t) {
table = t;
return this;
}
public boolean isDefault() {
return defaultAction;
}
public MenuItemFactory<S> setRowIndex(int rI) {
rIndex = rI;
return this;
}
public MenuItem buildMenuItem() {
MenuItem mI = new MenuItem();
switch (itemType) {
case DETAILS:
mI.setText(itemLbl);
mI.setOnAction(handleDetails());
break;
case EDIT:
mI.setText(itemLbl);
mI.setOnAction(handleEdit());
break;
case DELETE:
mI.setText(itemLbl);
mI.setOnAction(handleDelete());
break;
default:
break;
}
return mI;
}
private EventHandler<ActionEvent> handleDetails() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** DETAIL REQUEST ***");
}
};
}
private EventHandler<ActionEvent> handleEdit() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** EDIT REQUESTED ***");
table.getSelectionModel().select(rIndex);
System.out.println("*** " + table.getSelectionModel().getSelectedItem().toString() + " ***");
}
};
}
private EventHandler<ActionEvent> handleDelete() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** DELETE REQUESTED ***");
System.out.println("*** " + table.getSelectionModel().getSelectedItem().toString() + " ***");
}
};
}
}
But when i click on the button, i'm getting always last value.
How can i get the object in the row where button is?
Any help or suggestion that point me to right direction is appreciated.
Simply use the TableCell to retrieve the value in the onAction event handler (or whatever you use in the product of the SplitMenuButtonFactory you're not showing to us).
Simplified example
public static SplitMenuButton createSplitMenuButton(final TableCell cell) {
SplitMenuButton result = new SplitMenuButton();
result.setOnAction(evt -> {
TableRow row = cell.getTableRow();
System.out.println("row item: " +row.getItem());
});
return result;
}
Furthermore it's better to reuse the SplitMenuButton in the cell and updating it instead of recerating it every time the cell item changes.
#Override
public TableCell<S, T> call(TableColumn<S, T> param) {
return new TableCell<S, T>() {
private final SplitMenuButton button = createSplitMenuButton(this);
{
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
updateMenuButton(button, item); // placeholder for updating the button according to the new item
setGraphic(button);
}
}
};
}

Automatically updating/styling TreeView's TreeCells when BooleanProperty is true in seperate TableView

What's the easiest way to have a TreeView's cells auto-refresh with a new style when a condition is met in a separate TableView?
I'm currently setting the TreeCells' styles in the updateItem() method in the TreeView cell factory, but this only fires off if the user adds or removes something in the TreeView. I want to be able to change the style of a given TreeCell if I check off all 3 checkboxes in a separate dialog box.
I'm currently able to monitor the number of checked checkboxes with a BooleanProperty and an IntegerProperty, but I have no idea how I'm supposed to "auto-update" or call a TreeView refresh when a TreeItem's Object's BooleanProperty changes.
Any help is greatly appreciated.
You can set the style in the TreeCell whenever a boolean property on the value underlying the TreeCell is updated (via a binding).
return new TreeCell<Message>() {
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
styleProperty().unbind();
if (empty || item == null || item.getText() == null) {
setText(null);
styleProperty.set(null);
} else {
setText(item.getText());
styleProperty().bind(
Bindings.when(
item.readProperty()
).then("-fx-background-color: red;")
.otherwise("-fx-background-color: null;")
);
}
}
};
Full Sample
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TreeViewSample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
ObservableList<Message> messages = FXCollections.observableArrayList();
TreeItem<Message> rootItem = new TreeItem<> (new Message("Inbox"));
rootItem.setExpanded(true);
for (int i = 1; i < 6; i++) {
Message message = new Message("Message" + i);
messages.add(message);
TreeItem<Message> item = new TreeItem<> (message);
rootItem.getChildren().add(item);
}
TreeView<Message> tree = new TreeView<> (rootItem);
tree.setCellFactory(new Callback<TreeView<Message>, TreeCell<Message>>() {
#Override
public TreeCell<Message> call(TreeView<Message> param) {
return new TreeCell<Message>() {
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
styleProperty().unbind();
if (empty || item == null || item.getText() == null) {
setText(null);
styleProperty.set(null);
} else {
setText(item.getText());
styleProperty().bind(
Bindings.when(
item.readProperty()
).then("-fx-background-color: red;")
.otherwise("-fx-background-color: null;")
);
}
}
};
}
});
TableView<Message> tableView = new TableView<>();
tableView.setEditable(true);
TableColumn<Message, String> textCol = new TableColumn<>("Text");
textCol.setCellValueFactory(new PropertyValueFactory<>("text"));
tableView.getColumns().add(textCol);
TableColumn<Message, Boolean> readCol = new TableColumn<>("Read");
readCol.setCellValueFactory(new PropertyValueFactory<>("read"));
readCol.setCellFactory(CheckBoxTableCell.forTableColumn(readCol));
readCol.setEditable(true);
tableView.getColumns().add(readCol);
tableView.setItems(messages);
VBox root = new VBox(10, tree, tableView);
root.setPadding(new Insets(10));
stage.setScene(new Scene(root, 300, 250));
stage.show();
}
public class Message {
private StringProperty text = new SimpleStringProperty();
private BooleanProperty read = new SimpleBooleanProperty(false);
public Message(String msgText) {
text.set(msgText);
}
public String getText() {
return text.get();
}
public StringProperty textProperty() {
return text;
}
public void setText(String text) {
this.text.set(text);
}
public boolean isRead() {
return read.get();
}
public BooleanProperty readProperty() {
return read;
}
public void setRead(boolean read) {
this.read.set(read);
}
}
}
I'm trying to the bind the graphicProperty to the same BooleanProperty and change the image based on the value.
Example using a binding of an Image within an ImageView associated with the cell.
Image unreadImage = new Image("http://icons.iconarchive.com/icons/oxygen-icons.org/oxygen/16/Status-mail-unread-new-icon.png");
Image readImage = new Image("http://icons.iconarchive.com/icons/icons8/ios7/16/Messaging-Read-Message-icon.png");
. . .
return new TreeCell<Message>() {
ImageView imageView = new ImageView();
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
styleProperty().unbind();
imageView.imageProperty().unbind();
if (empty || item == null || item.getText() == null) {
setText(null);
setGraphic(null);
styleProperty().set(null);
} else {
setText(item.getText());
setGraphic(imageView);
imageView.imageProperty().bind(
Bindings.when(
item.readProperty()
).then(readImage)
.otherwise(unreadImage)
);
styleProperty().bind(
Bindings.when(
item.readProperty()
).then("-fx-background-color: red;")
.otherwise("-fx-background-color: null;")
);
}
}
};
An alternate (and possibly preferable) way to handle this from above is to instead get the style class or psuedoclass of the cell and update that based upon the boolean property. Then define the style in a separate CSS stylesheet. The output of the sample below is the same as the graphic based sample above.
mail.css
.readable:read {
-fx-background-color: red;
-fx-graphic: url(
"http://icons.iconarchive.com/icons/icons8/ios7/16/Messaging-Read-Message-icon.png"
);
}
.readable:unread {
-fx-graphic: url(
"http://icons.iconarchive.com/icons/oxygen-icons.org/oxygen/16/Status-mail-unread-new-icon.png"
);
}
Pseudo-class based code snippet:
PseudoClass READ_PSEUDO_CLASS = PseudoClass.getPseudoClass("read");
PseudoClass UNREAD_PSEUDO_CLASS = PseudoClass.getPseudoClass("unread");
tree.setCellFactory(new Callback<TreeView<Message>, TreeCell<Message>>() {
#Override
public TreeCell<Message> call(TreeView<Message> param) {
return new TreeCell<Message>() {
private ChangeListener<Boolean> readChangeListener = (observable, oldValue, newValue) -> {
pseudoClassStateChanged(READ_PSEUDO_CLASS, newValue);
pseudoClassStateChanged(UNREAD_PSEUDO_CLASS, !newValue);
};
Message priorItem = null;
{
getStyleClass().add("readable");
}
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
if (priorItem != null) {
priorItem.readProperty().removeListener(readChangeListener);
}
priorItem = item;
if (empty || item == null || item.getText() == null) {
setText(null);
pseudoClassStateChanged(READ_PSEUDO_CLASS, false);
pseudoClassStateChanged(UNREAD_PSEUDO_CLASS, false);
} else {
item.readProperty().addListener(readChangeListener);
setText(item.getText());
pseudoClassStateChanged(READ_PSEUDO_CLASS, item.isRead());
pseudoClassStateChanged(UNREAD_PSEUDO_CLASS, !item.isRead());
}
}
};
}
});

ContextMenu in JavaFX keeps getting duplicates of the same MenuItem

I use this class to give a TreeItem a textfield for editing (not relevant to the problem) and to set a ContextMenu on the TreeItem:
package domain;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public final class TextFieldTreeCellImpl extends TreeCell<MyNode> {
private TextField textField;
private ContextMenu cm = new ContextMenu();
String oldItem = "";
private Connection connection;
String url = "jdbc:sqlserver://localhost:1433;databaseName=HOGENT1415_11";
String user = "sa";
String password = "root";
Statement statement;
public TextFieldTreeCellImpl() throws SQLException {
connection = DriverManager.getConnection(url, user, password);
statement = connection.createStatement();
}
#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().value);
setGraphic(getTreeItem().getGraphic());
}
#Override
public void updateItem(MyNode 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(getTreeItem().getGraphic());
MenuItem cmItem1 = new MenuItem("Add continent");
cmItem1.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
System.out.println("Geklikt!");
}
});
cm.getItems().add(cmItem1);
setContextMenu(cm);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
String sql = "UPDATE Continents SET Name='" + textField.getText() + "' WHERE ContinentID=" + getItemId();
if (getItem().isCountry()) {
sql = "UPDATE Countries SET Name='" + textField.getText() + "' WHERE CountryID=" + getItemId();
}
if (getItem().isClimateChart()) {
sql = "UPDATE ClimateCharts SET Location='" + textField.getText() + "' WHERE ClimateChartID=" + getItemId();
}
try {
ResultSet result = statement.executeQuery(sql);
} catch (SQLException ex) {
Logger.getLogger(TextFieldTreeCellImpl.class.getName()).log(Level.SEVERE, null, ex);
}
commitEdit(new MyNode(textField.getText(), getType(), getItemId()));
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
private String getType() {
return getItem() == null ? "" : getItem().type;
}
private int getItemId() {
return getItem() == null ? null : getItem().id;
}
}
I instance this class in my Controller class using the following code:
selectionTreeView.setEditable(true);
selectionTreeView.setCellFactory(new Callback<TreeView<MyNode>, TreeCell<MyNode>>() {
#Override
public TreeCell<MyNode> call(TreeView<MyNode> p) {
try {
return new TextFieldTreeCellImpl();
} catch (SQLException ex) {
Logger.getLogger(MainPanel.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
});
However when I run the program and I right click on the items everything works fine, but if I keep clicking some more on other items there keep on getting items in the contextmenu.
To narrow it down, this happens everytime I double click on an item.
Check the screenshots:
I know that this is because ipdateItem keeps on getting called, but how can I prevent this?
Every time updateItem(...) is called, you add the menu item again (and you never remove it). So each time the cell is reused, it gets another copy of the menu item.
The most efficient approach is to create the menu item in the constructor and pass it to the context menu there. Note that the event handler can access the current item easily:
public TextFieldTreeCellImpl() throws SQLException {
connection = DriverManager.getConnection(url, user, password);
statement = connection.createStatement();
MenuItem cmItem1 = new MenuItem("Add continent");
cmItem1.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
MyNode item = getItem();
// ...
System.out.println("Geklikt!");
}
});
cm.getItems().add(cmItem1);
}
You probably also want to remove the context menu for empty cells:
#Override
public void updateItem(MyNode item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
setContextMenu(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(getTreeItem().getGraphic());
setContextMenu(cm);
}
}
}
You can also configure the menu items further in the updateItem(...) method if you need, by declaring them as fields, e.g.
public final class TextFieldTreeCellImpl extends TreeCell<MyNode> {
private TextField textField;
private ContextMenu cm = new ContextMenu();
private MenuItem cmItem1 ;
// ...
#Override
public void updateItem(MyNode item, boolean empty) {
super.updateItem(item, empty);
// ...
cmItem1.setText(...);
}
}
Finally, if you really need to completely restructure the context menu when the item changes, then you can do
#Override
public void updateItem(MyNode item, boolean empty) {
super.updateItem(item, empty);
// ...
cm.getItems().clear();
// Now create all menu items from scratch and add to the context menu
}

JavaFX 2: Save edit in TableCell

In my JavaFX-program I use a TableCell where you can edit a value. Like shown at the examples on the JavaFX-page "Example", I use this function to save the changes (function is set on TextField in edit Cell)
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override public void handle(KeyEvent t) {
if (combo.match(t)) {
commitEdit(textField.getText());
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
When using ENTER to leave the cell, the value is changed / saved, but how can I change / save the value, when leaving the cell by clicking in another cell? Actually the value is resetted.
Thanks
Basti
Listening to a change in focus on the TextField is one way.. I added a listener to the focusedProperty of the textField. The example from Oracle didn't include this.
[edit - here is a link to another question that has a different approach UITableView - Better Editing through Binding? ]
private void createTextField() {
textField = new TextField(getItem());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
// Detect a change in focus on the text field.. If we lose the focus we take appropriate action
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(!newValue.booleanValue())
commitEdit(textField.getText());
}
} );
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(textField.getText());
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Cell;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.util.StringConverter;
class CellGenerator {
/***************************************************************************
* * Private fields * *
**************************************************************************/
private final static StringConverter defaultStringConverter = new StringConverter<Object>() {
#Override
public String toString(Object t) {
return t == null ? null : t.toString();
}
#Override
public Object fromString(String string) {
return (Object) string;
}
};
static <T> StringConverter<T> defaultStringConverter() {
return (StringConverter<T>) defaultStringConverter;
}
private static <T> String getItemText(Cell<T> cell, StringConverter<T> converter) {
return converter == null ? cell.getItem() == null ? "" : cell.getItem().toString()
: converter.toString(cell.getItem());
}
/***************************************************************************
* * TextField convenience * *
**************************************************************************/
static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final TextField textField) {
updateItem(cell, converter, null, null, textField);
}
static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox,
final Node graphic, final TextField textField) {
if (cell.isEmpty()) {
cell.setText(null);
cell.setGraphic(null);
} else {
if (cell.isEditing()) {
if (textField != null) {
textField.setText(getItemText(cell, converter));
}
cell.setText(null);
if (graphic != null) {
hbox.getChildren().setAll(graphic, textField);
cell.setGraphic(hbox);
} else {
cell.setGraphic(textField);
}
} else {
cell.setText(getItemText(cell, converter));
cell.setGraphic(graphic);
}
}
}
static <T> void startEdit(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox,
final Node graphic, final TextField textField) {
if (textField != null) {
textField.setText(getItemText(cell, converter));
}
cell.setText(null);
if (graphic != null) {
hbox.getChildren().setAll(graphic, textField);
cell.setGraphic(hbox);
} else {
cell.setGraphic(textField);
}
textField.selectAll();
// requesting focus so that key input can immediately go into the
// TextField (see RT-28132)
textField.requestFocus();
}
static <T> void cancelEdit(Cell<T> cell, final StringConverter<T> converter, Node graphic) {
cell.setText(getItemText(cell, converter));
cell.setGraphic(graphic);
}
static <T> TextField createTextField(final Cell<T> cell, final StringConverter<T> converter) {
final TextField textField = new TextField(getItemText(cell, converter));
EdittingCell cellEdit=(EdittingCell)cell;
textField.setOnMouseExited(event -> {
if (converter == null) {
throw new IllegalStateException("Attempting to convert text input into Object, but provided "
+ "StringConverter is null. Be sure to set a StringConverter "
+ "in your cell factory.");
}
cell.commitEdit(converter.fromString(textField.getText()));
});
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
cell.cancelEdit();
event.consume();
} else if (event.getCode() == KeyCode.RIGHT) {
cellEdit.getTableView().getSelectionModel().selectRightCell();
event.consume();
} else if (event.getCode() == KeyCode.LEFT) {
cellEdit.getTableView().getSelectionModel().selectLeftCell();
event.consume();
} else if (event.getCode() == KeyCode.UP) {
cellEdit.getTableView().getSelectionModel().selectAboveCell();
event.consume();
} else if (event.getCode() == KeyCode.DOWN) {
cellEdit.getTableView().getSelectionModel().selectBelowCell();
event.consume();
} else if (event.getCode() == KeyCode.ENTER) {
if (converter == null) {
throw new IllegalStateException("Attempting to convert text input into Object, but provided "
+ "StringConverter is null. Be sure to set a StringConverter "
+ "in your cell factory.");
}
cell.commitEdit(converter.fromString(textField.getText()));
event.consume();
}
else if (event.getCode() == KeyCode.TAB) {
cell.commitEdit(converter.fromString(textField.getText()));
cellEdit.setNextColumn(event);
event.consume();
}
});
return textField;
}}
//the table cell
import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
public class EdittingCell<S, T> extends TableCell<S, T> {
public static <S> Callback<TableColumn<S, String>, TableCell<S, String>>
forTableColumn() {
return forTableColumn(new DefaultStringConverter());
}
public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>>
forTableColumn(
final StringConverter<T> converter) {
return new Callback<TableColumn<S, T>, TableCell<S, T>>() {
#Override
public TableCell<S, T> call(TableColumn<S, T> list) {
return new EdittingCell<S, T>(converter);
}
};
}
public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>>
forTableColumn(final StringConverter<T> converter,
final boolean isFieldEditable) {
return new Callback<TableColumn<S, T>, TableCell<S, T>>() {
#Override
public TableCell<S, T> call(TableColumn<S, T> list) {
return new EdittingCell<S, T>(converter, isFieldEditable);
}
};
}
/***************************************************************************
* * Fields * *
**************************************************************************/
public TextField textField;
private static int currentRow = -1;
private static int control = 0;
public EdittingCell() {
this(null);
textField = CellGenerator.createTextField(this, getConverter());
}
public EdittingCell(StringConverter<T> converter) {
this.getStyleClass().add("text-field-table-cell");
setConverter(converter);
textField = CellGenerator.createTextField(this, getConverter());
// textField.setEditable(false);
}
public EdittingCell(StringConverter<T> converter, boolean isFieldEditable) {
this.getStyleClass().add("text-field-table-cell");
setConverter(converter);
textField = CellGenerator.createTextField(this, getConverter());
textField.setEditable(isFieldEditable);
}
/***************************************************************************
* * Properties * *
**************************************************************************/
// --- converter
private ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<StringConverter<T>>(this,
"converter");
public final ObjectProperty<StringConverter<T>> converterProperty() {
return converter;
}
public TextField getTextFiedCell() {
return textField;
}
public final void setConverter(StringConverter<T> value) {
converterProperty().set(value);
}
public final StringConverter<T> getConverter() {
return converterProperty().get();
}
#Override
public void startEdit() {
if (!isEditable() || !getTableView().isEditable() || !getTableColumn().isEditable()) {
return;
}
super.startEdit();
if (isEditing()) {
CellGenerator.startEdit(this, getConverter(), null, null, textField);
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
CellGenerator.cancelEdit(this, getConverter(), null);
}
/** {#inheritDoc} */
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
CellGenerator.updateItem(this, getConverter(), null, null, textField);
// System.out.println("Silas");
}
public TableView<S> getContextTableView() {
return getTableView();
}
public void setNextColumn(KeyEvent event) {
TableColumn nextColumn = getNextColumn(!event.isShiftDown());
if (nextColumn != null) {
// Get Selected index to reset current editable row
int selectedRow = getTableRow().getIndex();
// Set row that serves as a control for tapping through
if (currentRow == -1) {
currentRow = getTableRow().getIndex();
}
// Reset editing upon selection change row
if (currentRow != selectedRow) {
currentRow = selectedRow;
}
int colSize = getTableView().getColumns().size();
int colindex = getTableView().getColumns().indexOf(nextColumn);
if (colindex == colSize - 1) {
control++;
}
if (control > 0 && colindex == 0) {
currentRow++;
}
if (getTableView().getItems().size() > currentRow) {
getTableView().edit(currentRow, nextColumn);
// getTableView().getSelectionModel().select(currentRow,
// nextColumn);
} else {
currentRow = 0;
// getTableView().getSelectionModel().select(currentRow,
// nextColumn);
getTableView().edit(currentRow, nextColumn);
}
}
}
private TableColumn<S, ?> getNextColumn(boolean forward) {
List<TableColumn<S, ?>> columns = new ArrayList<>();
for (TableColumn<S, ?> 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++;
if (nextIndex > columns.size() - 1) {
nextIndex = 0;
}
} else {
nextIndex--;
if (nextIndex < 0) {
nextIndex = columns.size() - 1;
}
}
return columns.get(nextIndex);
}
private ObservableList<TableColumn<S, ?>> getLeaves(TableColumn<S, ?> column2) {
ObservableList<TableColumn<S, ?>> columns = FXCollections.observableArrayList();
if (column2.getColumns().isEmpty()) {
// We only want the leaves that are editable.
if (column2.isEditable()) {
columns.addAll(column2);
}
return columns;
} else {
for (TableColumn<S, ?> column : column2.getColumns()) {
columns.addAll(getLeaves(column));
}
return columns;
}
}
}
//How to use this
TableColumn<NewInvoice, BigDecimal> quantityCol = new
TableColumn<NewInvoice, BigDecimal>("Quantity");
quantityCol.setCellValueFactory(cellData ->
cellData.getValue().quantityProperty());
quantityCol.setCellFactory(EdittingCell.forTableColumn( new
BigDecimalStringConverter()));
quantityCol.setStyle("-fx-alignment:CENTER-RIGHT;");
quantityCol.setOnEditCommit(new EventHandler<CellEditEvent<NewInvoice,
BigDecimal>>() {
#Override
public void handle(CellEditEvent<NewInvoice, BigDecimal> t) {
t.getTableView().getItems().get(t.getTablePosition().getRow()
).setQuantity(t.getNewValue());
}
});

Resources