Skip to content

Dynamically adding nodes in JavaFX

I’m trying to build a chat application that implements Group chat in JavaFX. I want to make a Scroll Pane inside of a Border pane that will contain all Groups in which the User is member of. The Groups icons (ImageViews) need to be added dynamically(cannot be done in Scene Builder) to the Scroll Pane(inside of a HBox) as the User is joining them.

enter image description here

Currently I’m using a SceneController class that is responsible for all Stage and Scene changes.

package com.ong.Controllers;

import com.ong.Main;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;

import java.io.IOException;
import java.util.HashMap;

public class ScreenController {
    private static HashMap<String, Scene> screenMap = new HashMap<>();
    private static Stage main;

    public ScreenController(Stage _main){
        main = _main;
    }

    public static void addScreen(String name, FXMLLoader fxmlLoader){
        Scene scene = null;
        try {
            scene = new Scene(fxmlLoader.load());
        } catch (IOException e) {
            e.printStackTrace();
        }
        screenMap.put(name,scene);
    }

    public static void addScene(String name, Scene scene){
        screenMap.put(name,scene);
    }

    public static void removeScreen(String name){
        screenMap.remove(name);
    }

    public static void setMinimumDimensions(int width, int height){
        main.setMinWidth(width);
        main.setMinHeight(height);
    }

    public static void setMaximized(boolean full){
        main.setMaximized(full);
    }

    public static void activate(String name){
        main.setScene(screenMap.get(name));
        main.getIcons().add(new Image(Main.class.getResource("Images/not_logo.png").toString()));
        main.setTitle(name);
        main.show();
    }
}

I already created an FXML file(using scene builder) which contains Border Pane and a Scroll Pane as top child.

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

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<BorderPane fx:id="borderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/11.0.2" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.ong.Controllers.HomepageController">
   <top>
      <ScrollPane fx:id="groupsMenuScrollPane" hbarPolicy="NEVER" prefHeight="62.0" prefWidth="1920.0" stylesheets="@../CSS/groups_menu.css" vbarPolicy="NEVER" BorderPane.alignment="CENTER" />
   </top>
</BorderPane>

My plan is to populate all Groups(on initialization) to the Scroll Pane in the Page controller.

package com.ong.Controllers;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.shape.Circle;

import java.net.URL;
import java.util.ResourceBundle;

public class HomepageController implements Initializable {

    @FXML
    private ScrollPane groupsMenuScrollPane;

    @FXML
    private BorderPane borderPane;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        final ImageView imageView = new ImageView("com/ong/Images/not_logo.png");
        final Circle clip = new Circle(300, 200, 200);
        imageView.setClip(clip);

        HBox hbox = new HBox();
        hbox.getChildren().add(imageView);

        groupsMenuScrollPane.setContent(hbox);
        borderPane.setTop(groupsMenuScrollPane);

        Scene scene = new Scene(borderPane);

        ScreenController.addScene("Homepage", scene);
        ScreenController.activate("Homepage");
    }
}

I already tried to create a Scene with the updated Border Pane, but I get an error “borderPane is already set as root of another scene”. I also tried to directly change the root of the already existing scene, but I get a NullPointerException in that case.

Could you please advise which is the best way to add Nodes dynamically to a Scroll Pane. I will be grateful for comments about the project structure too.

Answer

Could you please advise which is the best way to add Nodes dynamically to a Scroll Pane.

You need a reference to the content of the ScrollPane (the HBox in this case), and a method to add things to it:

public class HomepageController implements Initializable {

    @FXML
    private ScrollPane groupsMenuScrollPane;

    @FXML
    private BorderPane borderPane;

    private HBox groupContainer ;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        final ImageView imageView = new ImageView("com/ong/Images/not_logo.png");
        final Circle clip = new Circle(300, 200, 200);
        imageView.setClip(clip);

        groupContainer = new HBox();
        groupContainer.getChildren().add(imageView);

        groupsMenuScrollPane.setContent(groupContainer);

        // this is not needed since it's already done in FXML:
        // borderPane.setTop(groupsMenuScrollPane);

        // ...
    }

    public void addGroup(...) {
        // not sure exactly what you want to add, but for example,
        // to add a new image view you would do:
        ImageView imageView = new ImageView(...);
        groupContainer.getChildren().add(imageView);
    }
}

When you load the FXML, keep a reference to the controller:

FXMLLoader loader = new FXMLLoader(getClass().getResource(...));
Parent root = loader.load();
HomepageController controller = loader.getController();

And then you can add things to the scroll pane with

controller.addGroup(...);

As mentioned in another answer, a ListView may be a better approach to this.

You should also probably consider a MVC approach (see, for example, Applying MVC With JavaFx), where the model uses an ObservableList<...> to store the list of groups. The controller can observe that list and modify the scroll pane content when it changes. Then all you have to do is add something to the list via the model.