I\'m developing an application using JavaFx in which I\'m creating dynamic TextFields inside a GridPane and there is a Button which is initially disabled like this:
Actually it's not that easy to do what you want, because the code you have needs to be refactored (the code is not meant to do such advanced requirements but it's fine for the basic requirements you have). However, you can do something like this:
First, define a global variable to be updated with the last row index
of the invalid TextField
(From here you shall conclude that this will change the border color for ONE invalid TextField
at a time):
public static int textFieldIndex = -1;
Now with the help of the method you already have getComponent (int row, int column, GridPane table)
, create another static method to check if ALL TextFields
have Valid Values at one time:
/**
* This method to check at run time with every change in any TextField
* if the corresponding TextField has a valid value(i.e contains number and
* the first TextField value is less than the second)
* @param table
* @param numRows
*/
private static boolean hasValidValue(GridPane table, int numRows){
// cycle through every row in the table
// and compare every two TextFields
for(int i=0; i<numRows; i++){
try{ // try because user may enters a non-number input (to avoid crash)
// the first TextField is always at column index 0 , the second at column index 3
if(Integer.parseInt(((TextField)(getComponent (i, 0, table))).getText())>
Integer.parseInt(((TextField)(getComponent (i, 3, table))).getText())){
// before returning false
textFieldIndex = i; // update at which row the TextField is less
return false;
}
}catch(NumberFormatException e){ // if it contains invalid input(non-digit)
return false;
}
}
return true;
}
Now you need to use the above method in the validateTable()
method and do some adjustments:
// pass the comboBox.getValue() to the third parameter
private void validateTable(GridPane table, Button button, int numRows) {
for(Node textField : table.getChildren()){
if(textField instanceof TextField){
((TextField)textField).textProperty().addListener((obs, old, newV)->{
// first of all remove the red border from the invalid TextField (if any)
// we know that via textFieldIndex which should be -1 if there is no lesser
// actually it's a pain
if(textFieldIndex!=-1){
((TextField) getComponent(textFieldIndex, 3, table)).setStyle("");
}
if(isAllFilled(table)){ // if all filled ( you already have this method)
if(hasValidValue(table,numRows)){ // check for validity
button.setDisable(false); // then make the button active again
}
else{// if it's not a valid value
// re-style the TextField which has lesser value
((TextField) getComponent(textFieldIndex, 3, table)).
setStyle("-fx-border-color: red;");
button.setDisable(true);
}
}
else{
button.setDisable(true);
}
});
}
}
}
Now in your tabPane ChangeListener
add the third para to the method (because you already have it you need just to add the value of ComboBox
:
tabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>(){
....
....
....
// I think you have here anchorPane not containerB in the original code
validateTable((GridPane) containerB.getChildren().get(0), test, comboBox.getValue());
}
Test
You can create the bindings that do the validation when you create the text fields. This will avoid the need to navigate through the grid pane's child nodes, which doesn't seem very robust.
Declare an array of boolean bindings (there will be one for each row):
private BooleanBinding[] rowValidationBindings ;
Then you can do
public static GridPane table(int rows){
GridPane table = new GridPane();
rowValidationBindings = new BooleanBinding[rows];
for(int i=0; i<rows; i++){
TextField textField1 = new JFXTextField();
textField1.setAlignment(Pos.CENTER);
TextField textField2 = new JFXTextField();
textField2.setAlignment(Pos.CENTER);
TextField textField3 = new JFXTextField();
textField3.setAlignment(Pos.CENTER);
rowValidationBindings[i] = Bindings.createBooleanBinding(
() -> {
if (textField1.getText().matches("\\d+") &&
textField3.getText().matches("\\d+")) {
int value1 = Integer.parseInt(textField1.getText());
int value3 = Integer.parseInt(textFIeld3.getText());
return value3 > value1 ;
} else {
return false ;
}
}, textField1.textProperty(), textField2.textProperty()
);
//add them to the GridPane
table.add(textField1, 0, i+1);
table.add(textField2, 1, i+1);
table.add(textField3, 2, i+1);
}
button.disableProperty().bind(Bindings.createBooleanBinding(
() -> ! Stream.of(rowValidationBindings).allMatch(BooleanBinding::get),
rowValidationBindings
));
return table;
}
You can also add the styling to the text field directly in the for
loop:
textField3.styleProperty().bind(Bindings
.when(rowValidationBindings[i])
.then("")
.otherwise("-fx-border-color: red")); // or whatever you are using for style
and for tooltips:
Tooltip tooltip = new Tooltip();
tooltip.textProperty().bind(Bindings.concat("Value must be greater than ",textField1.textProperty()));
textField3.tooltipProperty().bind(Bindings
.when(rowValidationBindings[i])
.then((Tooltip)null)
.otherwise(tooltip));