I have Water
object with Integer
parameter value
. This parameter can be empty if not created yet, which equals null. Value will be stored as Integer
, but when rendered, converted to Decimal
and printed for user as String
with TextFormatter
applied. After user makes changes it should be converted back into appropriate Integer
and stored in database.
My Water
object:
private Integer value;
Code for table:
@FXML
private TableView<Water> waterTable;
@FXML
private TableColumn<Water, Integer> valueColumn;
@FXML
private void initialize(){
waterTable.setItems(FXCollections.observableArrayList(thisMonthWaterList));
//don't know how to implemeny here instead of this code cell with TextField
/*valueColumn.setCellValueFactory(cellData -> new
SimpleIntegerProperty(cellData.getValue().getAmount()).asObject());
valueColumn.setCellFactory(new Callback<TableColumn<Water,Integer>,
TableCell<Water,Integer>>() {
@Override
public TableCell<Water, Integer> call(TableColumn<Water, Integer> param) {
return new TableCell<Water, Integer>(){
@Override
protected void updateItem(Integer value, boolean empty){
super.updateItem(value, empty);
if(value==null || empty){
setText(null);
}else{
setText(value.toString());
}
}
};
}
});*/
}
This is the code for TextFormatter
:
TextField.setTextFormatter(new TextFormatter<>(change -> {
int maxLength = 10;
if (change.isAdded()) {
if(change.getControlNewText().length()<=maxLength){
if (change.getText().contains(",")) {
change.setText(change.getText().replaceAll(",", "."));
}
change = change.getControlNewText().matches("^\\d*(\\.\\d{0,1})?$") ? change : null;
}else{
if(change.getText().length()==1){
change = null;
}else{
int allowedLength = maxLength - change.getControlText().length();
change.setText(change.getText().substring(0, allowedLength));
}
}
}
return change;
}));
Code for Listener
:
TextField.focusedProperty().addListener(new FocusChangeListener(TextField, text -> {
if(text.isEmpty()){
TextField.setText(Water.getAreaFormat());
}else{
TextField.setText(Water.toString(TextField.getText()));
}
thisObject.setValue(Water.toInt(TextField.getText()));
WaterDA.update(thisObject);
}, text -> {
if(text.equals(Water.getWaterFormat())){
TextField.setText("");
}
}));
Actually you can handle both conversion and preventing invalid input using a TextFormatter
using a StringConverter
and a UnaryOperator
. The following code assumes you've got a ObjectProperty<Integer>
in your Water
class and the amountProperty()
method returns it.
If the Water.toString(int)
or Water.toString(Integer)
method does not exist, you need to implement the conversion from int
to string for the following code to work.
private static final StringConverter<Integer> VALUE_CONVERTER = new StringConverter<Integer>() {
@Override
public String toString(Integer object) {
return object == null ? Water.getAreaFormat() : Water.toString(object);
}
@Override
public Integer fromString(String string) {
return Water.toInt(string);
}
};
// filter copied unmodified from your code
private static final UnaryOperator<TextFormatter.Change> VALUE_FILTER = change -> {
int maxLength = 10;
if (change.isAdded()) {
if(change.getControlNewText().length() <= maxLength){
if (change.getText().contains(",")) {
change.setText(change.getText().replaceAll(",", "."));
}
change = change.getControlNewText().matches("^\\d*(\\.\\d{0,1})?$") ? change : null;
} else {
if (change.getText().length() == 1){
change = null;
} else {
int allowedLength = maxLength - change.getControlText().length();
change.setText(change.getText().substring(0, allowedLength));
}
}
}
return change;
};
@FXML
private TableColumn<Water, Integer> valueColumn;
@FXML
private void initialize(){
waterTable.setItems(FXCollections.observableArrayList(thisMonthWaterList));
valueColumn.setCellValueFactory(cellData -> cellData.getValue().amountProperty());
valueColumn.setCellFactory(new Callback<TableColumn<Water, Integer>, TableCell<Water, Integer>>() {
@Override
public TableCell<Water, Integer> call(TableColumn<Water, Integer> param) {
return new TableCell<Water, Integer>() {
private final TextFormatter<Integer> formatter;
private final TextField textField;
{
textField = new TextField();
formatter = new TextFormatter<>(VALUE_CONVERTER, null, VALUE_FILTER);
textField.setTextFormatter(formatter);
formatter.valueProperty().addListener((o, oldValue, newValue) -> {
Water water = (Water) getTableRow().getItem();
if (!Objects.equals(water.getAmount(), newValue)) {
// update item and db, if value was modified
water.setAmount(newValue);
WaterDA.update(water);
}
});
}
@Override
protected void updateItem(Integer value, boolean empty){
super.updateItem(value, empty);
if (empty){
setGraphic(null);
} else {
setGraphic(textField);
formatter.setValue(value);
}
}
};
}
});
}
This assumes your TableCell
s should always be in "editing state". If this is not the case, you need to implement the state change in the startEdit
/cancelEdit
and commitEdit
methods.