4

I am developing an application in JavaFx in which I've two tabs.

In first Tab I've ComboBox:

enter image description here

In Second Tab I've Gridpane like this:

enter image description here

What I want is when user choose let say 3 from Tab A's combobox like:

enter image description here

It should add 3 rows to the Gridpane of Tab B and each column with textfields, checkboxes and datepicker. Column A having Textfields, column B having Checkboxes and column C having DatePicker like this:

enter image description here

Please help me how can I achieve this and after achieving how can I access the data of each Textfield, checkboxes and datepicker.

Update: Trying to do @Yahya solution with FXML

Main.java

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            TabPane root = (TabPane)FXMLLoader.load(getClass().getResource("Sample.fxml"));
            Scene scene = new Scene(root,400,400);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

SampleController.java

public class SampleController {

    @FXML
    private TabPane root ;
    @FXML
    private Tab tabA ;
    @FXML
    private Tab tabB ;
    @FXML
    private ComboBox<Integer> comboBox ;
    @FXML
    private static GridPane gridPane ;
    @FXML
    private AnchorPane anchB ;


public void initialize() {

    // Create a comboBox, set its attributes and add it to container
    comboBox.getItems().addAll(1,2,3,4,5,6,7,8,9,10);
    comboBox.setValue(1);
    comboBox.setEditable(false);
   // anchA.getChildren().add(comboBox);

    // add listener to tabPane
    root.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>(){
         @Override
         public void changed(ObservableValue<? extends Tab> observable, Tab oldTab, Tab newTab){
              if(newTab == tabB) { // when tabB is selected
                  System.out.println(anchB.getChildren().size());
                  if(anchB.getChildren().size()<=0){ // if already contains a table

                      anchB.getChildren().add(table(comboBox.getValue())); 

                      table(comboBox.getValue());
                      System.out.println("hello");
                  }
                  else {
                  anchB.getChildren().remove(gridPane); // remove it
                  System.out.println("no");
                  }
            }
       }   
   });
}

//This static method shall create the table dynamically when it's called
// you can add, change and remove the attributes of the table components
// the colors and all other decoration(e.g. position) are for example
public static GridPane table(int rows){

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

        //add them to the GridPane
        gridPane.add(textField, 0, i+1); //  (child, columnIndex, rowIndex)
        gridPane.add(checkBox , 1, i+1);
        gridPane.add(datePicker,2, i+1);

        // margins are up to your preference
        GridPane.setMargin(textField, new Insets(5));
        GridPane.setMargin(checkBox, new Insets(5));
        GridPane.setMargin(datePicker, new Insets(5));
     }

    return gridPane;

}
}

Sample.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Text?>

<TabPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" tabClosingPolicy="UNAVAILABLE" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SampleController">
   <tabs>
      <Tab fx:id="tabA" text="Tab A">
         <content>
            <AnchorPane fx:id="anchA" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
               <children>
                  <ComboBox fx:id="comboBox" layoutX="225.0" layoutY="82.0" prefWidth="150.0" />
               </children>
            </AnchorPane>
         </content>
      </Tab>
      <Tab fx:id="tabB" text="Tab B">
         <content>
            <AnchorPane fx:id="anchB" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
               <children>
                  <GridPane fx:id="gridPane" gridLinesVisible="true" layoutX="150.0" layoutY="62.0">
                     <columnConstraints>
                        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                     </columnConstraints>
                     <rowConstraints>
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                     </rowConstraints>
                     <children>
                        <Text strokeType="OUTSIDE" strokeWidth="0.0" text="A" textAlignment="CENTER" wrappingWidth="91.67529296875" />
                        <Text strokeType="OUTSIDE" strokeWidth="0.0" text="B" textAlignment="CENTER" wrappingWidth="93.5986328125" GridPane.columnIndex="1" />
                        <Text strokeType="OUTSIDE" strokeWidth="0.0" text="C" textAlignment="CENTER" wrappingWidth="95.287109375" GridPane.columnIndex="2" />
                     </children>
                  </GridPane>
               </children>
            </AnchorPane>
         </content>
      </Tab>
   </tabs>
</TabPane>
Junaid
  • 664
  • 5
  • 18
  • 35

1 Answers1

7

You can do something like this:

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class DynamicTable extends Application{

    @Override
    public void start(Stage ps) throws Exception {

        // Create a Tab A, create a container and add it 
        Tab tabA = new Tab("Tab A");
        StackPane containerA = new StackPane();

        // note that the colors are for example
        containerA.setBackground(new Background(
                new BackgroundFill(Color.MAROON,null,null)));
        // Create a comboBox, set its attributes and add it to container
        ComboBox<Integer> comboBox = new ComboBox<Integer>();
        comboBox.getItems().addAll(1,2,3,4,5,6,7,8,9,10);
        comboBox.setValue(1);
        comboBox.setEditable(false);
        containerA.getChildren().add(comboBox);

        //add the container to the tabA
        tabA.setContent(containerA);

        // Create Tab B, create a container and add it 
        Tab tabB = new Tab("Tab B");
        StackPane containerB = new StackPane();

        containerB.setBackground(new Background(
                        new BackgroundFill(Color.DARKMAGENTA,null,null)));
        tabB.setContent(containerB);
        // create TabPane and add the Tabs to it
        // all other values need manipulation (i.e. up to your preference)
        TabPane tabPane = new TabPane();
         tabPane.getTabs().addAll(tabA, tabB);
        //set size and other attributes (if any), for example 
        tabPane.setMinWidth(500);
        tabPane.setMinHeight(500);

         // add listener to tabPane
         tabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>(){
              @Override
              public void changed(ObservableValue<? extends Tab> observable, Tab oldTab, Tab newTab){
                   if(newTab == tabB) { // when tabB is selected
                       if(containerB.getChildren().size()>0){ // if already contains a table
                           containerB.getChildren().remove(0); // remove it
                       }
                       containerB.getChildren().add(table(comboBox.getValue())); // create the table and add it   
                 }
            }   
        });

        // simple root to test 
        Pane root = new Pane();
        root.getChildren().add(tabPane);

        Scene scene = new Scene(root, 500,500);
        ps.setScene(scene);
        ps.setTitle("Dynamic Table In Tab");
        ps.show();
    }


    // This static method shall create the table dynamically when it's called
    // you can add, change and remove the attributes of the table components
    // the colors and all other decoration(e.g. position) are 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);
            CheckBox checkBox = new CheckBox("Check Box");
            checkBox.setTextFill(Color.WHITE);
            checkBox.setAlignment(Pos.CENTER);
            DatePicker datePicker = new DatePicker();

            //add them to the GridPane
            table.add(textField, 0, i); //  (child, columnIndex, rowIndex)
            table.add(checkBox , 1, i);
            table.add(datePicker,2, i);

            // margins are up to your preference
            GridPane.setMargin(textField, new Insets(5));
            GridPane.setMargin(checkBox, new Insets(5));
            GridPane.setMargin(datePicker, new Insets(5));
         }
        table.setAlignment(Pos.CENTER);

        return table;

    }

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

    }
}

Test

enter image description here

enter image description here

Update: If you want to get the values from the Table in TabB, you can do something like this:

// first create a method like this
// this static 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()) { // loop through every node in the table
        if(GridPane.getRowIndex(component) == row && 
                        GridPane.getColumnIndex(component) == column) {
            return component;
        }
    }

    return null;
}

// Then use the method like this
// The textfield always at Column 0, check box column 1 and Date picker 2
// and the GridPane is in a StackPane(container) at index 0
// Note that you may NEED to add a button and wrap the following code with its Action Listener (i.e. button.setOnAction())
if(containerB.getChildren().size()>0){ // that means it contains a table    
    GridPane table = (GridPane) containerB.getChildren().get(0);
    for(int i=0 ; i<comboBox.getValue(); i++){
        String Text = ((TextField)getComponent (i, 0, table)).getText();
        boolean selected = ((CheckBox)getComponent (i, 1, table)).isSelected();
        LocalDate date = ((DatePicker)getComponent (i, 2, table)).getValue();

        System.out.println(Text + " " + selected + " " + date);
        System.out.println("Next Row");
    }

}

UPDATE:

If you want your program to remove/create the table (GridPane) only if the user changed the value in ComboBox, then you need to add a ChangeListener to your ComboBoxto catch the changes in it, consequently you need for example a global boolean that changes from true to false and vice versa when the ComboBox value changes, and this boolean to be checked in the if-statementthat checks if the table already exists in containerB (in your updated question anchB), for example something like this:

boolean valueChanged = false; // its scope should be global and to be added to the ComboBox ChangeListener
if(containerB.getChildren().size()>0 && valueChanged){ // if already contains a table
    containerB.getChildren().remove(0); // remove it
}
Yahya
  • 13,349
  • 6
  • 30
  • 42
  • Thanks for your answer @Yahya, can you please tell me how can I achieve this if I've build UI using FXML ? – Junaid May 21 '17 at 23:50
  • 1
    @Junaid You should have mentioned that in your question, However, I created a whole program for your question, if it's of any help please accept it. Regarding the components in the `FXML`, give each of them an `id` and then use the lookup method http://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#lookup-java.lang.String- – Yahya May 22 '17 at 00:18
  • I'm able to to do it with FXML but problem is I'm not able to remove Gridpane it's throwing "Exception in thread "JavaFX Application Thread" java.lang.NullPointerException". – Junaid May 22 '17 at 11:16
  • `root.getSelectionModel().selectedItemProperty().addListener(new ChangeListener(){ @Override public void changed(ObservableValue extends Tab> observable, Tab oldTab, Tab newTab){ if(newTab == tabB) { // when tabB is selected System.out.println(anchB.getChildren().size()); if(anchB.getChildren().size()>0){ // if already contains a table anchB.getChildren().remove(root); } anchB.getChildren().add(table(comboBox.getValue())); table(comboBox.getValue()); } } });` – Junaid May 22 '17 at 11:19
  • @Junaid You're removing the `root` from `anchB` in the `root` listener! Please have a look at my answer, it contains everything you may need in details. Please copy-paste the above code to your IDE and try it yourself, you should better understand it though. – Yahya May 22 '17 at 12:29
  • sorry @Yahya I don't know why I was doing that, can you please now see I've updated my question using your solution – Junaid May 22 '17 at 12:42
  • @Junaid I've looked at your updated question, a lot of misconceptions your have, many things illogically changed from my answer. Please compare my answer with yours. For example but not limited to: `anchB.getChildren().size()<0`. Another thing, have you tried to test your `AnchorPanes` ? are they null?...etc? – Yahya May 22 '17 at 12:51
  • Okay i'll try to compare it again and yea this is my first ever JavaFx application which I'm doing. And Inside Anchorpane I've Gridpane. When first time I go to the next tab it's not adding elements in gridpane but when I click second time to next tab then it adds elements and another thing if I change value of Combobox let say from 5 to 3 it's not updating the gridpane. – Junaid May 22 '17 at 13:00
  • @Junaid Another thing, `private static GridPane gridPane ;` don't create `GridPane` in your `FXML` file, instead create it in the `table(int rows)` method as I did in my answer in order to be able to re-create it and remove it..etc. Please copy-paste my answer to your IDE and try to manipulate it for better understanding. – Yahya May 22 '17 at 13:10
  • Got it now it's working after removing Gridpane from FXML, and last thing can I do let say if user select number 3 from combobox and go to the next tab it will add 3 rows with corresponding elements (which is working now) and then again he return to first tab and without changing any value in combobox go again to the second tab so in that case I don't want to remove the Gridpane. I want to remove gridpane only if user after returning to tab 1 change the value in combobox. – Junaid May 22 '17 at 13:36
  • Can you please check my this question. @Yahya [Question](https://stackoverflow.com/questions/44256865/javafx-how-to-validate-mutilple-textfields-creating-at-run-time) – Junaid May 30 '17 at 10:45
  • Hello @Yahya I need your help I'm trying to implement the "remove/create the table (GridPane) only if the user changed the value in ComboBox" as you suggested in your updated answer, but It's not working correctly Idk why it's giving unexpected result. Continue next comment... – Junaid Jun 29 '17 at 20:39
  • global boolean variable `boolean valueChanged = false;` this is how attached listener with the `combobox comboBox().valueProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, Integer t, Integer t1) { if (t1 == t || t == t1) { valueChanged = false; } valueChanged = true; } });` and this is how I'm checking `if(containerB.getChildren().size()>0 && valueChanged){ // if already contains a table containerB.getChildren().remove(0); // remove it }` – Junaid Jun 29 '17 at 20:39
  • 1
    @Junaid, You can do something like this: `boolean valueChanged = false;` `comboBox().valueProperty().addListener((obs, oldV, newV)->{ if(newV != null){valueChanged = true;} });` `if(containerB.getChildren().size()>0 && valueChanged){ containerB.getChildren().remove(0); valueChanged = false; // change it back to false }` //////////////////////////////////// Or easier: `comboBox().valueProperty().addListener((obs, oldV, newV)->{ if(newV!=null && containerB.getChildren().size()>0){ containerB.getChildren().remove(0); } });` – Yahya Jun 29 '17 at 22:46
  • Please I really need your help on [this](https://stackoverflow.com/questions/45863584/javafx-how-to-add-serveral-series-dynimically-on-area-chart-and-delete-lineser) – Junaid Aug 24 '17 at 21:29
  • Sorry @Yahya I don't wanted to disturb you but I really need your help so when you've some time please have a look at [this question](https://stackoverflow.com/questions/45900852/javafx-how-to-deserialize-dynamically-create-area-chart-series?noredirect=1#comment78761201_45900852) – Junaid Aug 27 '17 at 21:57