Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding JavaFX2 Controls dynamically

Tags:

fxml

javafx-2

I'm quite new to java and javafx and have a problem which i could not solve. I need to dynamically add new custom controlls to a javafx scene. Further i need interaction between the main control and the added controls. I found already some useful information in the web but could not put it together.

So i build a little example for explanation:

main class:

public class Test_TwoController extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("Fxml1.fxml"));
        Scene scene = new Scene(root);                  
        stage.setScene(scene);
        stage.show();
    }    
    public static void main(String[] args) {
        launch(args);
    }
}

The main fxml:

<AnchorPane id="fxml1_anchorpane_id" fx:id="fxml1_anchorpane" prefHeight="206.0" prefWidth="406.0" xmlns:fx="http://javafx.com/fxml" fx:controller="test_twocontroller.Fxml1Controller">
  <children>
    <HBox id="fxml1_hbox_id" fx:id="fxml1_hbox" prefHeight="200.0" prefWidth="400.0">
      <children>
        <Button id="fxml1_button_id" fx:id="fxml1_button" mnemonicParsing="false" onAction="#button_action" prefHeight="200.0" prefWidth="200.0" text="Button" />
      </children>
    </HBox>
  </children>
</AnchorPane>

and its controller:

public class Fxml1Controller implements Initializable {

    @FXML HBox hbox;
    @FXML Button button;

    @Override
    public void initialize(URL url, ResourceBundle rb) { }

    public void button_action(ActionEvent event) throws IOException {
        // 1. add an instance of Fxml2 to hbox
        // 2. change to tab2 in new Fxml2
        // or
        // notify Fxml2Controller to change to tab2 in Fxml2
    }
}

And now the control to dynamically add:

Its fxml:

<AnchorPane id="fxml2_anchorpane_id" fx:id="fxml2_anchorpane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="200.0" xmlns:fx="http://javafx.com/fxml" fx:controller="test_twocontroller.Fxml2Controller">
  <children>
    <TabPane id="fxml2_tabpane_id" fx:id="fxml2_tabpane" prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
      <tabs>
        <Tab id="fxml2_tab1_id" fx:id="fxml2_tab1" text="tab1">
          <content>
            <AnchorPane id="Content" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" />
          </content>
        </Tab>
        <Tab id="fxml2_tab2_id" fx:id="fxml2_tab2" onSelectionChanged="#onSelectionChanged" text="tab2">
          <content>
            <AnchorPane id="Content" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" />
          </content>
        </Tab>
      </tabs>
    </TabPane>
  </children>
</AnchorPane>

and the controler:

public class Fxml2Controller {

    @FXML TabPane tabpane;
    @FXML Tab tab1;
    @FXML Tab tab2;

    public Fxml2Controller() throws IOException {
        Parent root = FXMLLoader.load(getClass().getResource("Fxml2.fxml"));
        Scene scene = new Scene(root);        
        Stage stage = new Stage();
        stage.setScene(scene);        
    }    

    public void onSelectionChanged(Event e) throws IOException {

        FXMLLoader loader = new FXMLLoader();
        // how can i get the current Fxml1 anchorpane instance?
        AnchorPane root = (AnchorPane) loader.load(getClass().getResource("Fxml1.fxml").openStream());

        Button b = (Button)root.lookup("#fxml1_button_id");        
        b.setText("New Button Text"); // dont change the buttons text!!!               
}
}

The usage is: A fxml2 should be added to the hbox of fxml1. Then after a button click in fxml1 the tabs of fxml2 should change. You may have a look at that image http://s13.postimage.org/uyrmgylo7/two_controlls.png

So my questions are:

  • how can i add one or more of the fxml2 controller into the hbox of fxml1?
  • how can i access one control from another or communicate between controlls? See onSelectionChanged() method in Fxml2Controller for detail.

Thank you in advance, solarisx

like image 333
solarisx Avatar asked Nov 20 '12 01:11

solarisx


1 Answers

You seem to have mixed quite a few concepts together which are distinct. First of all, a Stage can be understood as a window on the screen. It has a Scene object which holds the actual SceneGraph. In your example, you are creating a new Stage and a new Scene that get filled which the content of your second fxml-file. This means that, if working, a second window will pop up containing your stuff. I don't think that this is what you want to achieve.

Moreover, when the FXMLLoader reads a file, it looks for the class that is specified as its controller and constructs an instance of it via reflection. This means that when you call the load method in the constructor of the controller of the fxml-file you are loading with it, you are causing an infinite loop.

The last thing to understand is that the object that load() returns is an arbitrary node which can be put into the SceneGraph of your application just like any other node.

So to make your concept work, you should make the following:

  1. Move the loading-code which is currently in the constructor of your second controller to the button_Action method of your first controller.
  2. Throw away the new-stage-new-scene code in the button_action and take the Node returned by the FXMLLoader and add it to the children of the HBox.
  3. For your second question, you can get the controller instance if you actually create an instance of an FXMLLoader instead of calling the static method, and use the load() method in it. After calling load() you can retrieve the controller and root object of the fxml-file via getController() and getRoot(). You can then use them just like any arbitrary object in your logic.
like image 159
zhujik Avatar answered Jan 04 '23 00:01

zhujik