JavaFx: How to update text of dynimically created

2019-03-01 16:19发布

I'm developing an app in JavaFx, in which which I'm dynamically creating TextFeids and CheckBoxes inside GridPane like this:

enter image description here

I'm adding numbers which user will enter in TextField 1 and TextField 2 and displaying in TextField 3 like this using listener:

enter image description here

Problem: What I want is when user will check the CheckBox it should add 10 to the value already present in the TextField 3 of (same row as of triggered CheckBox) and update the text of TextField 3, and when user will uncheck the CheckBox it should subtract 10 from the value present in the TextField 3 of (same row as of triggered CheckBox) and update the text of TextField 3. I tried to do it but it's not working (not adding and removing):

enter image description here

This is how I'm creating GridPane:

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

        for(int i=0; i<rows; i++){
            TextField textField = new TextField();
            textField.setAlignment(Pos.CENTER);
            TextField textField2 = new TextField();
            textField2.setAlignment(Pos.CENTER);
            CheckBox checkBox = new CheckBox("Check Box");
            checkBox.setTextFill(Color.WHITE);
            checkBox.setAlignment(Pos.CENTER);
            TextField textField3 = new TextField();
            textField3.setAlignment(Pos.CENTER);

            table.add(textField, 0, i);
            table.add(textField2, 1, i);
            table.add(checkBox , 2, i);
            table.add(textField3,3, i);

            GridPane.setMargin(textField, new Insets(5));
            GridPane.setMargin(textField2, new Insets(5));
            GridPane.setMargin(checkBox, new Insets(5));
            GridPane.setMargin(textField3, new Insets(5));
         }
        table.setAlignment(Pos.CENTER);

        return table;
    }

A method to return a component from Table at specific row and column

public static Node getComponent (int row, int column, GridPane table) {
     for (Node component : table.getChildren()) { 
         if(GridPane.getRowIndex(component) == row && 
                         GridPane.getColumnIndex(component) == column) {
             return component;
         }
     }

     return null;
 }

This is how I'm adding:

    public void add(GridPane table, int numRows){

       for(int i=0; i<numRows; i++){
           try {

                int valueA = Integer.parseInt(((TextField)(getComponent (i, 0, table))).getText());
                System.out.println(valueA);
                int valueB = Integer.parseInt(((TextField)(getComponent (i, 1, table))).getText());
                System.out.println(valueB);

                int add = valueA+valueB;

                String addToString = Integer.toString(add);

                ((TextField)(getComponent (i, 3, table))).setText(addToString);

                } catch (NullPointerException e) {
                  System.out.print("Caught the NullPointerException");
                    }
       }

    }

Problem: Addition and subtraction using CheckBox:

     public void addPause(GridPane table, int numRows){

                for(int i=0; i<numRows; i++){

                boolean pause = ((CheckBox)(getComponent (i, 2, table))).isSelected();
                int getTextValue = Integer.parseInt(((TextField)(getComponent (i, 3, table))).getText());

                int addPause = getTextValue+10;
                int removePause = addPause-10;

                String addPToString = Integer.toString(addPause);
                String removePToString = Integer.toString(removePause);

                if (pause) {
                    ((TextField)(getComponent (i, 3, table))).setText(addPToString);
                } else {
                    ((TextField)(getComponent (i, 3, table))).setText(removePToString);
                }
       }
    }

This is how I'm triggering using listeners:

        for(Node node : table.getChildren()){ 
          if(node instanceof TextField){
             ((TextField)node).textProperty().addListener((obs, old, newV)->{
               add(table, numRows);
           });
         }
          else if(node instanceof CheckBox){
              ((CheckBox)node).selectedProperty().addListener((obs, old, newV)->{ 
                addPause(table, numRows);
             });
           }
       }

2条回答
看我几分像从前
2楼-- · 2019-03-01 17:05

As I suggested in your previous question, just register the listeners for the controls at the point you create them.

You can actually just use a single listener (in each row) for the two text fields and check box, and update the third text box when any of those change. For example:

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

    for(int i=0; i<rows; i++){
        TextField textField = new TextField();
        textField.setAlignment(Pos.CENTER);
        TextField textField2 = new TextField();
        textField2.setAlignment(Pos.CENTER);
        CheckBox checkBox = new CheckBox("Check Box");
        checkBox.setTextFill(Color.WHITE);
        checkBox.setAlignment(Pos.CENTER);
        TextField textField3 = new TextField();
        textField3.setAlignment(Pos.CENTER);

        table.add(textField, 0, i);
        table.add(textField2, 1, i);
        table.add(checkBox , 2, i);
        table.add(textField3,3, i);

        ChangeListener<Object> listener = (obs, oldValue, newValue) -> 
            updateTotalField(textField.getText(), textField2.getText(), checkBox.isSelected(), textField3);

        textField.textProperty().addListener(listener);
        textField2.textProperty().addListener(listener);
        checkBox.selectedProperty().addListener(listener);

        GridPane.setMargin(textField, new Insets(5));
        GridPane.setMargin(textField2, new Insets(5));
        GridPane.setMargin(checkBox, new Insets(5));
        GridPane.setMargin(textField3, new Insets(5));
     }
    table.setAlignment(Pos.CENTER);

    return table;
}

private static void updateTotalField(String text1, String text2, boolean addPause, TextField output) {
    int value1 = parseText(text1);
    int value2 = parseText(text2);
    int total = value1 + value2 ;
    if (addPause) {
        total += 10 ;
    }
    output.setText(Integer.toString(total));
}

private static int parseText(String text) {
    // if text is a valid integer:
    if (text.matches("\\d+")) {
        return Integer.parseInt(text);
    } else {
        return 0 ;
    }
}

Now you can get rid of the add and addPause method, and the chunk of code you posted where you iterate through the child nodes of the grid pane and add listeners. You may be able to get rid of the getComponent() method to, unless you need it elsewhere.

Here is the code as a SSCCE

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class TableOfControlsInGridPane extends Application {

    @Override
    public void start(Stage primaryStage) {
        Scene scene = new Scene(table(10));
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

        for(int i=0; i<rows; i++){
            TextField textField = new TextField();
            textField.setAlignment(Pos.CENTER);
            TextField textField2 = new TextField();
            textField2.setAlignment(Pos.CENTER);
            CheckBox checkBox = new CheckBox("Check Box");
            checkBox.setTextFill(Color.WHITE);
            checkBox.setAlignment(Pos.CENTER);
            TextField textField3 = new TextField();
            textField3.setAlignment(Pos.CENTER);

            table.add(textField, 0, i);
            table.add(textField2, 1, i);
            table.add(checkBox , 2, i);
            table.add(textField3,3, i);

            ChangeListener<Object> listener = (obs, oldValue, newValue) -> 
                updateTotalField(textField.getText(), textField2.getText(), checkBox.isSelected(), textField3);

            textField.textProperty().addListener(listener);
            textField2.textProperty().addListener(listener);
            checkBox.selectedProperty().addListener(listener);

            GridPane.setMargin(textField, new Insets(5));
            GridPane.setMargin(textField2, new Insets(5));
            GridPane.setMargin(checkBox, new Insets(5));
            GridPane.setMargin(textField3, new Insets(5));
         }
        table.setAlignment(Pos.CENTER);

        return table;
    }

    private void updateTotalField(String text1, String text2, boolean addPause, TextField output) {
        int value1 = parseText(text1);
        int value2 = parseText(text2);
        int total = value1 + value2 ;
        if (addPause) {
            total += 10 ;
        }
        output.setText(Integer.toString(total));
    }

    private int parseText(String text) {
        // if text is a valid integer:
        if (text.matches("\\d+")) {
            return Integer.parseInt(text);
        } else {
            return 0 ;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

and a screenshot: enter image description here

Since Java is an object-oriented language, it would probably seem more appropriate to do this in an object-oriented way in the first place. I have no idea what data you are representing in this view, but you should define a class that encapsulates the data in a single row:

import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;

// Obviously use a more sensible name for the class:
public class RowData {

    private final IntegerProperty firstValue = new SimpleIntegerProperty();
    private final IntegerProperty secondValue = new SimpleIntegerProperty();
    private final BooleanProperty includePause = new SimpleBooleanProperty();
    private final ReadOnlyIntegerWrapper total = new ReadOnlyIntegerWrapper();

    public RowData() {
        total.bind(Bindings.createIntegerBinding(() -> {
            int total = getFirstValue() + getSecondValue() ;
            if (isIncludePause()) {
                total += 10 ;
            }
            return total ;
        }, firstValue, secondValue, includePause));
    }

    public final IntegerProperty firstValueProperty() {
        return this.firstValue;
    }


    public final int getFirstValue() {
        return this.firstValueProperty().get();
    }


    public final void setFirstValue(final int firstValue) {
        this.firstValueProperty().set(firstValue);
    }


    public final IntegerProperty secondValueProperty() {
        return this.secondValue;
    }


    public final int getSecondValue() {
        return this.secondValueProperty().get();
    }


    public final void setSecondValue(final int secondValue) {
        this.secondValueProperty().set(secondValue);
    }


    public final BooleanProperty includePauseProperty() {
        return this.includePause;
    }


    public final boolean isIncludePause() {
        return this.includePauseProperty().get();
    }


    public final void setIncludePause(final boolean includePause) {
        this.includePauseProperty().set(includePause);
    }


    public final ReadOnlyIntegerProperty totalProperty() {
        return this.total.getReadOnlyProperty();
    }


    public final int getTotal() {
        return this.totalProperty().get();
    }

}

Then a class to display those data:

import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;

public class RowDataView {

    private final TextField firstValueField ;
    private final TextField secondValueField ;
    private final CheckBox includePauseBox ;
    private final TextField totalField ;

    public RowDataView(RowData data) {

        firstValueField = new TextField();
        bindToStringProperty(data.firstValueProperty(), firstValueField.textProperty());

        secondValueField = new TextField();
        bindToStringProperty(data.secondValueProperty(), secondValueField.textProperty());

        includePauseBox = new CheckBox();
        data.includePauseProperty().bind(includePauseBox.selectedProperty());

        totalField = new TextField();
        totalField.setEditable(false);
        totalField.textProperty().bind(data.totalProperty().asString());
    }

    public void addToGridPane(GridPane pane, int row, int firstValueColumn, int secondValueColumn, int checkboxColumn, int totalColumn) {
        pane.add(firstValueField, firstValueColumn, row);
        pane.add(secondValueField, secondValueColumn, row);
        pane.add(includePauseBox, checkboxColumn, row);
        pane.add(totalField, totalColumn, row);
    }

    public void addToGridPane(GridPane pane, int row, int column) {
        addToGridPane(pane, row, column, column+1, column+2, column+3);
    }

    public void addToGridPane(GridPane pane, int row) {
        addToGridPane(pane, row, 0);
    }

    private void bindToStringProperty(IntegerProperty p, StringProperty s) {
        p.bind(Bindings.createIntegerBinding(
                () -> {
                    if (s.get().matches("\\d+")) {
                        return Integer.parseInt(s.get());
                    }
                    return 0 ;
                }, s));
    }
}

and here is some test code:

import java.util.ArrayList;
import java.util.List;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class TableOfControlsInGridPane extends Application {

    private final List<RowData> data = new ArrayList<>();

    @Override
    public void start(Stage primaryStage) {
        Scene scene = new Scene(table(10), 800, 800);
        scene.getStylesheets().add("style.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public GridPane table(int rows){
        GridPane table = new GridPane();
        table.getStyleClass().add("data-grid");

        data.clear();

        for(int i=0; i<rows; i++){

            RowData rowData = new RowData();
            data.add(rowData);

            RowDataView rowDataView = new RowDataView(rowData);
            rowDataView.addToGridPane(table, i);
         }
        table.setAlignment(Pos.CENTER);
        return table;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

I moved the styles to the style sheet (style.css):

.data-grid {
    -fx-background-color: purple ;
    -fx-hgap: 10 ;
    -fx-vgap: 10 ;  
}
.data-grid .text-field, .data-grid .check-box {
    -fx-alignment: center ;
}
.data-grid .check-box {
    -fx-text-fill: white ;
}
查看更多
▲ chillily
3楼-- · 2019-03-01 17:12

One way to achieve that is by adding a ChangeListener to every CheckBox in the Table(GridPane), something like this:

/**
* This method to change the current value of the TextField at
* column 3 in the Table(GridPane) by ten 
* @param table
*/
private static void changeByTen(GridPane table){
   // loop through every node in the GridPane
   for(Node node : table.getChildren()){
       // and for every CheckBox
       if(node instanceof CheckBox){
          // add change listener to the Selected Property 
          ((CheckBox)node).selectedProperty().addListener((obs, unSelected, selected)->{
              // get the TextField at third column that corresponds to this CehckBox
              TextField targetTextFeild = 
                      ((TextField)getComponent(GridPane.getRowIndex(node), 3, table));
              try{// then try to add/subtract
                  if(selected){// if checked add 10
                     targetTextFeild.setText(String.valueOf(
                         Integer.parseInt(targetTextFeild.getText())+10));
                  }
                  else if(!selected){ // if unchecked subtract 10
                          targetTextFeild.setText(String.valueOf(
                           Integer.parseInt(targetTextFeild.getText())-10));
                  }
               }catch(Exception e){// do nothing}
           });
        }
    }
}

Then add this method in the tabPane ChangeListener:

tabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>(){
    ......
    ......
    // I think you have here anchorPane not containerB in the original code
    changeByTen((GridPane) containerB.getChildren().get(0));
}

Test

Test

查看更多
登录 后发表回答