在 JavaFX 中动态添加节点

Dynamically adding nodes in JavaFX

我正在尝试构建一个在 JavaFX 中实现群聊的聊天应用程序。我想在边框窗格内创建一个滚动窗格,它将包含用户所属的所有组。组图标 (ImageViews) 需要在用户加入时动态添加(不能在 Scene Builder 中完成)到滚动窗格(在 HBox 内部)。

目前我正在使用一个 SceneController class,它负责所有舞台和场景的变化。

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();
    }
}

我已经创建了一个 FXML 文件(使用场景生成器),其中包含边框窗格和一个滚动窗格作为顶部子项。

<?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>

我的计划是将所有组(在初始化时)填充到页面控制器中的滚动窗格。

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");
    }
}

我已经尝试使用更新后的边框窗格创建场景,但出现错误“borderPane 已设置为另一个场景的根”。我还尝试直接更改已存在场景的根,但在这种情况下我得到了 NullPointerException。

能否请您告知将节点动态添加到滚动窗格的最佳方式。如果您对项目结构提出意见,我将不胜感激。

你的问题和问题是不同的问题。

针对你的问题。动态添加您想要查看 ListView 的元素。 https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ListView.html

至于你的问题。场景只能有一个根节点。在此根目录中,您应该有边界窗格,其中应该是您的 ListView。

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

您需要引用 ScrollPane 的内容(在本例中为 HBox),以及向其中添加内容的方法:

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);
    }
}

加载 FXML 时,保留对控制器的引用:

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

然后您可以使用

向滚动窗格添加内容
controller.addGroup(...);

如另一个答案中所述,ListView 可能是更好的方法。

您可能还应该考虑 MVC 方法(例如,参见 ),其中模型使用 ObservableList<...> 来存储组列表。控制器可以观察该列表并在它更改时修改滚动窗格的内容。然后你所要做的就是通过模型向列表中添加一些东西。