Search code examples
tabsjavafx-8fxmlsubcontroller

JavaFX start method everytime included tabcontent is called


How can I start a method every time a tab is called? I have a Main.fxml with a tabpane and two tabs, I've included a separate fxml for each tab (tab1.fxml, tab2.fxml).

Main.fxml

<?xml version="1.0" encoding="UTF-8"?>    
<?import javafx.scene.control.*?>    
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="434.0" prefWidth="428.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.MainController">
   <children>
  <TabPane prefHeight="434.0" prefWidth="428.0" tabClosingPolicy="UNAVAILABLE" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
    <tabs>
      <Tab fx:id="tab1" text="Tab 1">
           <content>
              <fx:include source="tab1.fxml" />
           </content>
      </Tab>
      <Tab fx:id="tab2" onSelectionChanged="#addView" text="Tab 2">
           <content>
              <fx:include source="tab2.fxml" />
           </content>
      </Tab>
    </tabs>
  </TabPane>
 </children>
</AnchorPane>      

MainController.java

public class MainController implements Initializable{

    @FXML private Tab tab1;
    @FXML private Tab tab2;

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {

    }


    @FXML public void addView(){

    }
}

Each FXML has a Label which should show how often the tab(content) was called. So if I click on tab ("tab2") the counter label should show "1" and increment by +1 every time I call this tab again. This should happen by using a method within the tab controllers.

tab1.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="249.0" prefWidth="257.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.tab1Controller">
   <children>
     <Label fx:id="lbl_1" layoutX="92.0" layoutY="53.0" text="not clicked" />
   </children>
</AnchorPane>

tab1Controller.java

public class tab1Controller implements Initializable{


     @FXML public  Label lbl_1;

     private static int counter=0;

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {


    }

    public void addViewCounter(){
        lbl_1.setText(""+counter);
        counter++;
    }
}

Tab2.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="249.0" prefWidth="257.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.tab2Controller">
   <children>
     <Label fx:id="lbl_2" layoutX="92.0" layoutY="53.0" text="not clicked" />
   </children>
</AnchorPane>

tab2Controller.java

public class tab1Controller implements Initializable{


     @FXML public  Label lbl_2;

     private static int counter=0;

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {


    }

    public void addViewCounter(){
        lbl_2.setText(""+counter);
        counter++;
    }
}

I've already tried to solve this problem by using static methods and labels but i get NullPointerExceptions, seems like this doesn't work anymore with java 8. Also I've tried to get the controller by using a FXMLloader ... this way I also get a NullPointerExceptions.

Isn't there an onCall method for the included fmxl or the anchorpane? Or maybe something that makes the controller initialize again.

Any other solutions or ideas?


Solution

  • Firstly, your counters should not be static. They belong to the controller instances, not the controller class.

    You need three things:

    1. A reference to the tabPane in the main controller
    2. A reference to each of the tab controllers in the main controller.
    3. A listener on the tabPane's selected tab, so that you can call a method when the selected tab changes

    For the first, just do:

    <TabPane fx:id="tabPane" prefHeight="434.0" ... >
    

    in Main.fxml, and

    @FXML private TabPane tabPane ;
    

    in MainController.java.

    For the second, you can use the "Nested Controller" technique.

    You need to add an fx:id attribute to each of the fx:includes, and then just add references for the controllers in MainController.java. The rule is that if your fx:include has fx:id="x", then the controller from the corresponding FXML file can be injected into a variable with name xController. Here I have changed the class names for the controllers to Tab1Controller and Tab2Controller to follow the standard conventions (and avoid confusion).

    In Main.fxml, change the fx:includes to include an fx:id:

    <fx:include fx:id="tab1Content" source="tab1.fxml" />
    
    <fx:include fx:id="tab2Content" source="tab2.fxml" />
    

    In MainController.java:

    @FXML private Tab1Controller tab1ContentController ;
    @FXML private Tab2Controller tab2ContentController ;
    

    Now in MainController's initialize() method just set up the listener:

    public void initialize() {
        tabPane.getSelectionModel().selectedItemProperty()
            .addListener((obs, oldTab, newTab) -> {
                if (newTab == tab1) {
                    tab1ContentController.addViewCounter();
                } else if (newTab == tab2) {
                    tab2ContentController.addViewCounter();
                }
            });
    }