JavaFX editable table cell having display errors - javafx

Editing the question as per Brian's suggestion:
I have now trimmed it down to just a single integer column.
I am simply not able to find the problem in the code. THe values show up only when the columns are selected once for edit.
Please suggest of i am making some mistake in the EditingCell class.
The single classfile code that i am trying is as follows.
package javafxtest;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
//import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
* FXML Controller class
*
* #author balz
*/
public class UploadTemplateController extends Application {
#Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
MasterUploadParameter objMasterUploadParameter=new MasterUploadParameter();
MasterUploadParameter objMasterUploadParameter1=new MasterUploadParameter();
MasterUploadParameter objMasterUploadParameter2=new MasterUploadParameter();
final ObservableList<MasterUploadParameter> tableContent =
FXCollections.observableArrayList
(
objMasterUploadParameter,
objMasterUploadParameter1,
objMasterUploadParameter2
);
TableColumn ColumnID = new TableColumn();
ColumnID.setText("columnId");
ColumnID.setCellValueFactory(new PropertyValueFactory("columnId"));
Callback<TableColumn, TableCell> cellFactory =
new Callback<TableColumn, TableCell>() {
public TableCell call(TableColumn p) {
return new EditingCell();
}
};
ColumnID.setCellFactory(cellFactory);
TableView tableView = new TableView();
tableView.setItems(tableContent);
//Enabling editing
tableView.setEditable(true);
tableView.getColumns().addAll(ColumnID);
root.getChildren().add(tableView);
}
private void updateObservableListProperties(TableColumn<MasterUploadParameter, Integer> ColumnFieldNo, TableColumn<MasterUploadParameter, Boolean> ColumnLPad, TableColumn<MasterUploadParameter, Integer> ColumnStarting, TableColumn<MasterUploadParameter, Integer> ColumnEnding, TableColumn<MasterUploadParameter, String> ColumnSheetID, TableColumn<MasterUploadParameter, Integer> ColumnID, TableColumn<MasterUploadParameter, Integer> ColumnStartingRow, TableColumn<MasterUploadParameter, Integer> ColumnEnding0) {
ColumnFieldNo.setOnEditCommit(new EventHandler<CellEditEvent<MasterUploadParameter, Integer>>() {
#Override public void handle(CellEditEvent<MasterUploadParameter, Integer> t) {
((MasterUploadParameter) t.getTableView().getItems().get(
t.getTablePosition().getRow())).setcolumnId(new SimpleIntegerProperty(t.getNewValue()));
}
});
}
public static class MasterUploadParameter {
private SimpleIntegerProperty columnId;
public SimpleIntegerProperty columnIdProperty() {return columnId;}
public void setcolumnId(SimpleIntegerProperty columnId) {this.columnId = columnId;}
public MasterUploadParameter() {
this.columnId=new SimpleIntegerProperty(0);
}
}
// EditingCell - for editing capability in a TableCell
public static class EditingCell extends TableCell<MasterUploadParameter, Integer> {
private TextField textField;
public EditingCell() {
System.out.println("find value of textField: "+textField);
}
#Override public void startEdit() {
super.startEdit();
System.out.println("find value of textField: "+textField);
if (textField == null) {
createTextField();
}
setText(null);
setGraphic(textField);
textField.selectAll();
}
#Override public void cancelEdit() {
super.cancelEdit();
setText(String.valueOf(getItem()));
setGraphic(null);
}
public void updateItem(Integer item, boolean empty) {
System.out.println("find value of update: "+empty+item);
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);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(Integer.parseInt(textField.getText()));
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
}

You don't need separate editing cell types. You can use the col.setOnEditCommit method
Just use public static class EditingCell extends TableCell{ with no type information. (I guess that's why you left out #override before.)
Change the updateItem to #Override public void updateItem(Object item, boolean empty) {
Then whenever you call commitEdit use a string (all textfields have strings anyway). commitEdit(textField.getText())
Then for each column depending, on the type you can commit the edit however you want.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.Platform;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
//import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
* FXML Controller class
*
* #author balz
*/
public class UploadTemplateController extends Application {
#Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
MasterUploadParameter objMasterUploadParameter=new MasterUploadParameter();
MasterUploadParameter objMasterUploadParameter1=new MasterUploadParameter();
MasterUploadParameter objMasterUploadParameter2=new MasterUploadParameter();
final ObservableList<MasterUploadParameter> tableContent =
FXCollections.observableArrayList
(
objMasterUploadParameter,
objMasterUploadParameter1,
objMasterUploadParameter2
);
Callback<TableColumn, TableCell> cellFactory = new Callback<TableColumn, TableCell>() {
public TableCell call(TableColumn p) {
return new EditingCell();
}
};
TableColumn ColumnID = new TableColumn();
ColumnID.setText("columnId");
ColumnID.setCellValueFactory(new PropertyValueFactory("columnId"));
ColumnID.setCellFactory(cellFactory);
ColumnID.setOnEditCommit(new EventHandler<CellEditEvent<MasterUploadParameter, String>>() {
#Override
public void handle(CellEditEvent<MasterUploadParameter, String> evt) {
try {
evt.getTableView().getItems().get(evt.getTablePosition().getRow())
.columnId.set(Integer.parseInt(evt.getNewValue()));
} catch (NumberFormatException numberFormatException) {
//do whatever if a non int is entered
}
}
});
TableColumn columnDesc = new TableColumn("Desc");
columnDesc.setCellValueFactory(new PropertyValueFactory("desc"));
columnDesc.setCellFactory(cellFactory);
columnDesc.setOnEditCommit(new EventHandler<CellEditEvent<MasterUploadParameter, String>>() {
#Override
public void handle(CellEditEvent<MasterUploadParameter, String> evt) {
evt.getTableView().getItems().get(evt.getTablePosition().getRow())
.desc.set(evt.getNewValue());
}
});
TableView tableView = new TableView();
tableView.setItems(tableContent);
//Enabling editing
tableView.setEditable(true);
tableView.getColumns().addAll(ColumnID, columnDesc);
root.getChildren().add(tableView);
}
public static class MasterUploadParameter {
private SimpleIntegerProperty columnId;
public SimpleIntegerProperty columnIdProperty() {return columnId;}
private StringProperty desc;
public StringProperty descProperty() {return desc;}
public MasterUploadParameter() {
this.columnId=new SimpleIntegerProperty(0);
this.desc = new SimpleStringProperty("hi");
}
}
// EditingCell - for editing capability in a TableCell
public static class EditingCell extends TableCell{
private TextField textField;
#Override public void startEdit() {
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.selectAll();
Platform.runLater(new Runnable() {
#Override
public void run() {
textField.requestFocus();
}
});
}
}
#Override public void cancelEdit() {
super.cancelEdit();
try {
setText(getItem().toString());
} catch (Exception e) {
}
setGraphic(null);
}
#Override public void updateItem(Object item, boolean empty) {
System.out.println("find value of update: "+empty+item);
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);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
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();
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
}

Related

JavaFx TableCellEditor

I have JavaFx TableView and I permit user to enter some data in the table and retrieve what a user entered in the table for this reason I create also ArrayList of TextFields and use the following code but the size of the ArrayList should be 3 in my case but I found the size 7, what's wrong?
Edit the full code here
import java.util.ArrayList;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
public class TestTableView extends Application
{
private final TableView <ProductOpeningBalance> tableProductOpeningBalance = new TableView();
private final Scene scene = new Scene(tableProductOpeningBalance, 400, 200);
private final TableColumn productQuantity = new TableColumn("Product Quantity");
private final ObservableList <ProductOpeningBalance> data = FXCollections.observableArrayList(
new ProductOpeningBalance("0"),
new ProductOpeningBalance("0"));
private final ArrayList <TextField> txtProductQuantity = new ArrayList <> ();
#Override
public void start(Stage stage)
{
productQuantity.impl_setReorderable(false);
productQuantity.setEditable(true);
productQuantity.setCellValueFactory(new PropertyValueFactory("ProductQuantity"));
productQuantity.setCellFactory(column -> new TableCell()
{
#Override
public void startEdit()
{
if(!isEmpty())
{
super.startEdit();
createTextField();
setText(null);
setGraphic(txtProductQuantity.get(txtProductQuantity.size() - 1));
txtProductQuantity.get(txtProductQuantity.size() - 1).selectAll();
}
}
#Override
public void cancelEdit()
{
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
public void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if(empty)
{
setText(null);
setGraphic(null);
}
else
{
if(isEditing())
{
if(txtProductQuantity.get(txtProductQuantity.size() - 1) != null)
{
txtProductQuantity.get(txtProductQuantity.size() - 1).setText(getString());
}
setText(null);
setGraphic(txtProductQuantity.get(txtProductQuantity.size() - 1));
}
else
{
setText(getString());
setGraphic(null);
}
}
}
private void createTextField()
{
txtProductQuantity.add(new TextField(getString()));
txtProductQuantity.get(txtProductQuantity.size() - 1).
setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
txtProductQuantity.get(txtProductQuantity.size() - 1).setAlignment(Pos.BASELINE_RIGHT);
txtProductQuantity.get(txtProductQuantity.size() - 1).focusedProperty().
addListener((ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) ->
{
if(!arg2)
{
commitEdit(txtProductQuantity.get(txtProductQuantity.size() - 1).getText());
}
});
}
private String getString()
{
return getItem() == null ? "" : getItem().toString();
}
});
tableProductOpeningBalance.setEditable(true);
tableProductOpeningBalance.getColumns().addAll(productQuantity);
tableProductOpeningBalance.setItems(data);
stage.setScene(scene);
stage.show();
}
public class ProductOpeningBalance
{
private final SimpleStringProperty ProductQuantity;
public ProductOpeningBalance(String productQuantity)
{
this.ProductQuantity = new SimpleStringProperty(productQuantity);
}
public void setProductQuantity(String productQuantity)
{
ProductQuantity.set(productQuantity);
}
public String getProductQuantity()
{
return ProductQuantity.get();
}
}
}
Solution finally:
this code help me to find what I need after spent a lot of time in searching and trying a lot of methods
purchaseItemPrice.setCellFactory(column -> new TableCell()
{
#Override
public void startEdit()
{
if(!isEmpty())
{
if(txtPurchaseItemPrice.size() < data.size() && getGraphic() == null)
{
super.startEdit();
txtPurchaseItemPrice.add(new TextField());
txtPurchaseItemPrice.get(txtPurchaseItemPrice.size() - 1).setAlignment(Pos.BASELINE_RIGHT);
setGraphic(txtPurchaseItemPrice.get(txtPurchaseItemPrice.size() - 1));
}
}
}
public void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if(!empty)
{
if(isEditing())
{
setGraphic(txtPurchaseItemPrice.get(txtPurchaseItemPrice.size() - 1));
}
}
}
});

JavaFX TableView custom cell rendering split menu button

i've a problem with a custom cell render in a java fx table view component. I'm able to render the split menu button, but is only rendered from second row of table.
Below i put the code created to generate that image.
SplitMenuButtonApp.java
package com.example.splimenubtn;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class SplitMenuButtonApp extends Application {
private class Contact {
private StringProperty firstName;
private StringProperty lastName;
public Contact() {}
public Contact(String fName, String lName) {
firstName = new SimpleStringProperty(fName);
lastName = new SimpleStringProperty(lName);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public StringProperty firstName() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lName) {
lastName.set(lName);
}
public StringProperty lastName() {
return lastName;
}
}
private ObservableList<Contact> data;
protected List<MenuItemFactory<Contact>> menuItemsList;
private TableView<Contact> table;
#Override
public void start(Stage primaryStage) throws Exception {
// Init data list
data = FXCollections.observableArrayList();
data.add(new Contact("Mickey", "Mouse"));
data.add(new Contact("Donald", "Duck"));
data.add(new Contact("Fantasy", "Name"));
initMenuButton();
SplitMenuButtonFactory<Contact> sMBtn = new SplitMenuButtonFactory<>();
sMBtn.setMenuItems(menuItemsList);
SplitMenuButton actions = sMBtn.buildButton();
// Build the list
table = new TableView<>();
TableColumn<Contact, String> col = new TableColumn<>("First Name");
col.setCellValueFactory(c -> c.getValue().firstName);
table.getColumns().add(col);
col = new TableColumn<>("Last Name");
col.setCellValueFactory(c -> c.getValue().lastName);
table.getColumns().add(col);
TableColumn<Contact, SplitMenuButton> aCol = new TableColumn<>("Action");
aCol.setCellValueFactory(new PropertyValueFactory<>(""));
aCol.setCellFactory(new ButtonCellFactory<>(actions));
table.getColumns().add(aCol);
table.setItems(data);
AnchorPane root = new AnchorPane();
AnchorPane.setTopAnchor(table, 5.0);
AnchorPane.setRightAnchor(table, 5.0);
AnchorPane.setBottomAnchor(table, 5.0);
AnchorPane.setLeftAnchor(table, 5.0);
root.getChildren().add(table);
Scene s = new Scene(root, 600d, 300d);
primaryStage.setScene(s);
primaryStage.setTitle("Split menu button on table row");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void initMenuButton() {
if (menuItemsList == null) {
menuItemsList = new ArrayList<>();
menuItemsList.add(new MenuItemFactory<Contact>(MenuItemActions.EDIT, "Edit", true).setDataList(table));
menuItemsList.add(new MenuItemFactory<Contact>(MenuItemActions.DELETE, "Delete", false).setDataList(table));
}
}
}
MenuItemActions.java
package com.example.splimenubtn;
public enum MenuItemActions {
/**
* Detail item
*/
DETAILS,
/**
* Edit/Update item
*/
EDIT,
/**
* Delete item
*/
DELETE;
}
MenuItemFactory.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;
public MenuItemFactory() {}
public MenuItemFactory(MenuItemActions itemType, String itemLabel, boolean dA) {
this.itemType = itemType;
itemLbl = itemLabel;
defaultAction = dA;
}
public MenuItemFactory<S> setDataList(TableView<S> t) {
table = t;
return this;
}
public boolean isDefault() {
return defaultAction;
}
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 REQUESTED ***");
}
};
}
private EventHandler<ActionEvent> handleEdit() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** EDIT REQUESTED ***");
}
};
}
private EventHandler<ActionEvent> handleDelete() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** DELETE REQUESTED ***");
}
};
}
}
ButtonCellFactory.java
package com.example.splimenubtn;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public class ButtonCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private SplitMenuButton btn;
public ButtonCellFactory() {}
public ButtonCellFactory(SplitMenuButton b) {
btn = b;
}
#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);
setGraphic(btn);
}
}
};
}
}
SpliMenuButtonFactory.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;
public SplitMenuButtonFactory() {}
public SplitMenuButtonFactory<T> setMenuItems(List<MenuItemFactory<T>> items) {
menuItems = items;
return this;
}
public SplitMenuButton buildButton() {
SplitMenuButton menuBtn = new SplitMenuButton();
for (MenuItemFactory<?> mIF : menuItems) {
MenuItem btn = mIF.buildMenuItem();
if (mIF.isDefault()) {
menuBtn.setText(btn.getText());
menuBtn.setOnAction(btn.getOnAction());
}
menuBtn.getItems().add(btn);
}
return menuBtn;
}
}
As you see in the image, with this code i'm able to create the spli menu button and add it to ta table, but is only rendered on last row.
I need suggestion to render the split menu button in the other row, any help is appreciated.
Cause you have use the same button in every cell, So it's set a button only last of the cell Value.
Remove this line in SplitMenuButtonApp class
SplitMenuButton actions = sMBtn.buildButton();
And replace this line
aCol.setCellFactory(new ButtonCellFactory<>(actions));
To below code
Callback<TableColumn<Contact, SplitMenuButton>, TableCell<Contact, SplitMenuButton>> actionsCol = new Callback<TableColumn<Contact, SplitMenuButton>, TableCell<Contact, SplitMenuButton>>() {
#Override
public TableCell call(final TableColumn<Contact, SplitMenuButton> param) {
final TableCell<Contact, SplitMenuButton> cell = new TableCell<Contact, SplitMenuButton>() {
SplitMenuButton actions = sMBtn.buildButton();
#Override
public void updateItem(SplitMenuButton item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
setGraphic(actions);
setText(null);
}
}
};
return cell;
}
};
aCol.setCellFactory(actionsCol);
I hope this code is working for you:)

Adding currency symbol to tableview but remove on edit cell

I already know how I have to manipulate the table cell using a table cell factory callback. I added a currency symbol to the cell to make it look neat. (i.e. € 5,00 instead of 5,00) The thing is, when i double click on the cell i want that symbol to be removed. But for the heck of it, I'm unable to find how i am able to manipulate the textfield again to remove that currency symbol and bring it back in when the user committed the edit. Basically what I try to do is something similar when editing a cell in Excel :).
Any chance someone can help me out with a little basic example? Do I need to use the OnEditStart event?
Any time you want to configure how an item in a cell is displayed, without changing the actual data, you should use a custom TableCell. Here is an example that exhibits the behavior you want:
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.function.UnaryOperator;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;
public class CurrencyCell<T> extends TableCell<T, Double> {
private final TextField textField ;
private final NumberFormat format = DecimalFormat.getCurrencyInstance();
private final DecimalFormat textFieldFormat = new DecimalFormat("0.00");
public CurrencyCell() {
this.textField = new TextField();
StringConverter<Double> converter = new StringConverter<Double>() {
#Override
public String toString(Double object) {
return object == null ? "" : textFieldFormat.format(object) ;
}
#Override
public Double fromString(String string) {
try {
return string.isEmpty() ? 0.0 : textFieldFormat.parse(string).doubleValue();
} catch (ParseException e) {
e.printStackTrace();
return 0.0 ;
}
}
};
UnaryOperator<Change> filter = (Change change) -> {
String newText = change.getControlNewText() ;
if (newText.isEmpty()) {
return change ;
}
try {
textFieldFormat.parse(newText);
return change ;
} catch (ParseException exc) {
return null ;
}
};
TextFormatter<Double> textFormatter = new TextFormatter<Double>(converter, 0.0, filter);
textField.setTextFormatter(textFormatter);
textField.setOnAction(e -> commitEdit(converter.fromString(textField.getText())));
textField.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
setGraphic(textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setContentDisplay(ContentDisplay.TEXT_ONLY);
} else if (isEditing()) {
textField.setText(item.toString());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(format.format(item));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
#Override
public void startEdit() {
super.startEdit();
textField.setText(textFieldFormat.format(getItem()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(format.format(getItem()));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
public void commitEdit(Double newValue) {
super.commitEdit(newValue);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
And here's an example using it:
import java.util.Locale ;
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class CurrencyCellTest extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
table.getColumns().add(column("Item", Item::nameProperty));
TableColumn<Item, Double> priceCol = column("Price", item -> item.priceProperty().asObject());
table.getColumns().add(priceCol);
priceCol.setCellFactory(tc -> new CurrencyCell<>());
Random rng = new Random();
for (int i = 1; i <= 100; i++) {
table.getItems().add(new Item("Item "+i, rng.nextInt(10000)/100.0));
}
primaryStage.setScene(new Scene(table, 600, 600));
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String title, Function<S, Property<T>> property) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final DoubleProperty price = new SimpleDoubleProperty();
public Item(String name, double price) {
setName(name);
setPrice(price);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final DoubleProperty priceProperty() {
return this.price;
}
public final double getPrice() {
return this.priceProperty().get();
}
public final void setPrice(final double price) {
this.priceProperty().set(price);
}
}
public static void main(String[] args) {
// for testing:
Locale.setDefault(new Locale("NL", "nl"));
launch(args);
}
}

Syntax error on token ">>", Expression expected after this token

I'm getting Syntax error on token “>>”, Expression expected after this token when calling setCellFactory below but do not understand why.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableVieww extends Application {
private static TableView<TableData> table;
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unchecked")
public void start(Stage primaryStage) throws Exception {
Stage s = primaryStage;
s.setTitle("The Title");
TableColumn<TableData, String> column1 = new TableColumn<>("String");
column1.setMinWidth(150);
column1.setCellValueFactory(new PropertyValueFactory<TableData, String>("s"));
column1.setEditable(true);
column1.setCellFactory(TextFieldTableCell.forTableColumn());
column1.setOnEditCommit(e -> {
});
TableColumn<TableData, Double> column2 = new TableColumn<>("Double");
column2.setMinWidth(50);
column2.setCellValueFactory(new PropertyValueFactory<TableData, Double>("d"));
column2.setEditable(true);
// Error within this line (The line below)
column2.setCellFactory(TextFieldTableCell.forTableColumn());
column2.setOnEditCommit(e -> {
});
// set up a table
table = new TableView<>();
table.setItems(getData());
table.getColumns().addAll(column1, column2);
table.setEditable(true);
// setting up a layout
VBox layout = new VBox(10);
layout.getChildren().add(table);
// Setting up a scene
Scene scene = new Scene(layout, 500, 200);
s.setScene(scene);
s.show();
}
private static ObservableList<TableData> getData(){
ObservableList<TableData> list = FXCollections.observableArrayList();
TableVieww view = new TableVieww();
list.add(view.new TableData("BlaBlaBla", 10));
list.add(view.new TableData("TraTraTra", 5.1));
return list;
}
public class TableData{
double d;
String s;
public TableData(String s, double d) {
this.d = d;
this.s = s;
}
public double getD() {
return d;
}
public void setD(double d) {
this.d = d;
}
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
}
}
What is wrong? Thanks for your help in advance!
This is not entirely flawless, but it works. You will probaly need to adjust it too fit your needs a lot, but...it works.
public class SpinnerTableCell<S, T> extends TableCell<S, T> {
private Spinner spinner;
private String text;
public SpinnerTableCell(int min, int max) {
this.spinner = new Spinner(min, max, 0.0);
spinner.focusedProperty().addListener((observable, oldBoolean, newBoolean)->{
if(!newBoolean){
setText(spinner.getValue().toString());
text = getText();
setGraphic(null);
}
});
}
public void startEdit() {
if (this.isEditable() && this.getTableView().isEditable() && this.getTableColumn().isEditable()) {
super.startEdit();
if (this.isEditing()) {
setText("");
setGraphic(spinner);
}
}
}
public void cancelEdit() {
super.cancelEdit();
setText(text);
setGraphic(null);
}
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? "" : item.toString());
spinner.getValueFactory().setValue(empty ? 0.0 : item);
}
}
You can set the factory with tableCell.setCellFactory((e)->new SpinnerTableCell(min, max));

Custom ListCell implements InvalidationListener, repaint components

I have a custom ListCell implemented that contains a BorderPane layout with some components.
The cell registers itself to the item. So when the duration of the item changes the invalidated method is called.
In this method I set the text of the duration label. My problem is now the method is called but the label is not repainted.
I think if setText is called the cell should repaint. It is possible to manually repaint the cell or the Label.?
public static class ListItemCell extends ListCell<MusicListItem> implements InvalidationListener{
private AnchorPane listItem;
private Label artist;
private Label title;
private Label duration;
private BorderPane borderPane;
private FlowPane flowPane;
public ListItemCell() {
initCellLayout();
}
public ListItemCell(final LogicInterfaceFX logic) {
...
}
public void initCellLayout() {
try {
this.listItem = (AnchorPane) FXMLLoader.load(getClass().getResource("/de/roth/jsona/view/themes/" + Config.getInstance().THEME + "/" + "layout_list_item.fxml"));
} catch (Exception e) {
e.printStackTrace();
}
this.borderPane = (BorderPane) listItem.getChildren().get(0);
this.flowPane = (FlowPane) borderPane.getLeft();
this.artist = (Label) flowPane.getChildren().get(0);
this.artist.getStyleClass().add(defaultTextClass);
this.title = (Label) flowPane.getChildren().get(1);
this.title.getStyleClass().add(defaultTextClass);
this.duration = (Label) borderPane.getRight();
this.duration.getStyleClass().add(defaultTextClass);
this.setGraphic(listItem);
}
#Override
public void updateItem(MusicListItem item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
item.addListener(this);
item.durationProperty().addListener(this);
// Duration
this.duration.setText(item.getDuration());
// Artist / Title
if (item.getArtist() != null) {
this.artist.setText(item.getArtist());
this.title.setText(" - " + item.getTitle());
} else {
this.artist.setText("");
this.title.setText(item.getFile().getName());
}
} else {
this.artist.setText("");
this.title.setText("");
this.duration.setText("");
}
}
#Override
public void invalidated(Observable observable) {
System.out.println("INVALIDATE!!!" + getItem().getFile().getAbsolutePath());
this.duration.setText(getItem().getDuration());
}
}
You have a bug in there: you need to make sure you remove listeners from old items when the item is updated. Remember that ListCells are reused, so updateItem(...) is called multiple times during the lifespan of your ListView.
I don't know if that's what is causing it to fail to update. This works for me:
import java.util.Random;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewUpdatableProperties extends Application {
#Override
public void start(Stage primaryStage) {
final ListView<Item> listView = new ListView<>();
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
final Random rng = new Random();
for (int i=1; i<=20; i++) {
listView.getItems().add(new Item("Item "+i, rng.nextInt(100)));
}
BorderPane root = new BorderPane();
root.setCenter(listView);
listView.setCellFactory(new Callback<ListView<Item>, ListCell<Item>>() {
#Override
public ListCell<Item> call(ListView<Item> param) {
return new ItemListCell();
}
});
HBox controls = new HBox();
controls.setPadding(new Insets(5));
Button incButton = new Button("Increment selected");
incButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (Item item : listView.getSelectionModel().getSelectedItems()) {
item.increment();
}
}
});
controls.getChildren().add(incButton);
root.setBottom(controls);
Scene scene = new Scene(root, 250, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class ItemListCell extends ListCell<Item> implements InvalidationListener {
private final HBox hbox ;
private final Label nameLabel ;
private final Label valueLabel ;
public ItemListCell() {
hbox = new HBox(5);
nameLabel = new Label();
valueLabel = new Label();
hbox.getChildren().addAll(nameLabel, valueLabel);
setGraphic(hbox);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
public void updateItem(Item item, boolean empty) {
Item oldItem = getItem();
if (oldItem != null) {
oldItem.valueProperty().removeListener(this);
}
super.updateItem(item, empty);
if (item != null) {
nameLabel.setText(item.getName());
valueLabel.setText(String.valueOf(item.getValue()));
item.valueProperty().addListener(this);
} else {
nameLabel.setText("");
valueLabel.setText("");
}
}
#Override
public void invalidated(Observable observable) {
final int value = getItem().getValue();
System.out.println("Invalidated: item is "+getItem().getName() + " with value "+value);
valueLabel.setText(String.valueOf(value));
}
}
public static class Item {
public Item(String name, int value) {
setName(name);
setValue(value);
}
private final StringProperty name = new SimpleStringProperty(this, "name");
public StringProperty nameProperty() {
return name ;
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
private final IntegerProperty value = new SimpleIntegerProperty(this, "value");
public IntegerProperty valueProperty() {
return value ;
}
public int getValue() {
return value.get();
}
public void setValue(int value) {
this.value.set(value);
}
public void increment() {
value.set(value.get()+1);
}
}
public static void main(String[] args) {
launch(args);
}
}
As stated in the other answer, there is no repaint() method in JavaFX. If you wire things up correctly, when the properties are invalidated, it will know to repaint.
JavaFX uses a "retained mode" rendering model whereas Swing uses an "immediate mode".
See: Retained Mode Versus Immediate Mode (Windows)
So, you don't have a direct repaint() method.

Resources