JavaFx: How to compare values of dynamically creat

2019-02-20 06:11发布

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:

enter image description here

So what I want is if Column 1 TextFields values are less than Column 3 TextFields values, button should be enable like this:

enter image description here

But let say if any of Column 3 TextField value become less than Column 1 TextField value of same row, it should disable button and show that specific TextField border in red color and when hover mouse over that field should display some warning:

enter image description here

I'm creating TextField like this:

public static GridPane table(int rows){
            GridPane table = new GridPane();

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

            //add them to the GridPane
            table.add(textField1, 0, i+1);
            table.add(textField2, 1, i+1);
            table.add(textField3, 2, i+1);
         }
        return table;
    }

After that I'm creating another method to return component from table at specific row and column like this:

public static Node getComponent (int row, int column, GridPane table) {
         for (Node component : table.getChildren()) { // loop through every node in the table
             if(GridPane.getRowIndex(component) == row && 
                             GridPane.getColumnIndex(component) == column) {
                 return component;
             }
         }

         return null;
     }

I tried to do like this but it's not working (Here I'm converting values into string and comparing just for the check):

private boolean isTextEqual(GridPane table, Button button){

        for(Node node : table.getChildren()){ 
            if(node instanceof TextField){
                for(int i=1 ; i<=ComboBox().getValue(); i++){
                    String str = ((TextField)DynamicGridpanes.getComponent (i, 0, table)).getText();
                    ((TextField)DynamicGridpanes.getComponent (i, 2, table)).textProperty().addListener((obs, old, newV)->{ 
                    if(newV.toString()==str){
                        button.setDisable(false);
                    }
                    else{
                        button.setDisable(true);
                    }
                 });
                    }
                }
            }

        return true;
    }

2条回答
劳资没心,怎么记你
2楼-- · 2019-02-20 06:29

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));
查看更多
SAY GOODBYE
3楼-- · 2019-02-20 06:43

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

Test

查看更多
登录 后发表回答