如何将多个按钮添加到 tilepane?

How to add multiple buttons to a tilepane?

我在边框中央有一个方块。对于链接的哈希图中的每个条目,我想向 tilepane 添加一个按钮。此 tilepane 已存在于 fxml 文件中并具有 fx:id fieldContainer。

如果我一次添加一个键值对,按钮将一个接一个地出现。但是,当我尝试通过从文本文件中导入链接的哈希图来一次添加它们时,按钮不会出现。它将加载一个条目,然后抛出空指针异常。

导入文本文件和将条目添加到地图的方法似乎都可以正常工作。我认为问题在于 fieldContainer。我不明白为什么它只会一个一个地添加按钮而不是一次添加所有按钮。有人可以解释一下我的代码有什么问题吗?

更新:我通过将 fieldContainer 分配给控制器顶部的新 TilePane 修复了 nullpointerexception class:

@FXML
private TilePane fieldContainer = new TilePane();

错误消失了,它打印了整个地图,但仍然不显示按钮。有人吗?

FXML:

<BorderPane fx:id="container" stylesheets="/css/main.css" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<top>
// Some code
</top>

<center>
    <TilePane fx:id="fieldContainer" prefColumns="2" prefTileHeight="100.0" prefTileWidth="135.0">
    </TilePane>
</center>

<bottom>
// Some code
</bottom>

Class注册(导入方式):

public class Register {
    private Controller ctrl;

    public Register(Controller ctrl) {
        this.ctrl = ctrl;
    }

    public void exportTo(File file) {
        LinkedHashMap<String, BigDecimal> pMap = ctrl.getProductMap();
        try (
                FileOutputStream fos = new FileOutputStream(file);
                PrintWriter writer = new PrintWriter(fos);
        ) {
            for(Map.Entry<String, BigDecimal> entry : pMap.entrySet()) {
                writer.printf("%s|%s%n",
                        entry.getKey(),
                        entry.getValue());
            }
        } catch(IOException ioe) {
            System.out.printf("Problem saving %s %n", file);
            ioe.printStackTrace();
        }
    }

    public void importFrom(File file) {
        try (
                FileInputStream fis = new FileInputStream(file);
                BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
        ) {
            String line;
            while((line = reader.readLine()) != null) {
                String[] args = line.split("\|");
                ctrl.addProduct(args[0], new BigDecimal(args[1]));
            }
        } catch(IOException ioe) {
            System.out.printf("Problems loading %s %n", file);
            ioe.printStackTrace();
        }
    }
}

Class 控制器(按钮创建):

public class Controller {
    private static LinkedHashMap<String, BigDecimal> mProductMap = new LinkedHashMap<>();

    @FXML
    private TilePane fieldContainer;

    public LinkedHashMap<String, BigDecimal> getProductMap() {
        return mProductMap;
    }

    // Puts key and value to the Map and creates a button for each entry
    public void addProduct(String product, BigDecimal price) {
        mProductMap.put(product, price);
        System.out.println(mProductMap);

        fieldContainer.getChildren().clear();

        for (Map.Entry<String, BigDecimal> entry : mProductMap.entrySet()) {
            StackPane newField = new StackPane();
            Button main = new Button();
            Button multiply = new Button("X");
            Button subtract = new Button("-");
            main.setText(entry.getKey() + "\n" + entry.getValue());
            multiply.getStyleClass().add("inner-button");
            subtract.getStyleClass().add("inner-button");
            StackPane.setAlignment(multiply, Pos.BOTTOM_LEFT);
            StackPane.setAlignment(subtract, Pos.BOTTOM_CENTER);
            StackPane.setMargin(multiply, new Insets(0, 0, 8, 8));
            StackPane.setMargin(subtract, new Insets(0, 0, 8, 16));
            // Three buttons on top of each other
            newField.getChildren().add(main);
            newField.getChildren().add(multiply);
            newField.getChildren().add(subtract);

            fieldContainer.setAlignment(Pos.TOP_LEFT);
            // this should add a button for each entry, 
            // but it only works when you add them one by one
            fieldContainer.getChildren().add(newField);
        }
    }
}

主要:

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setScene(new Scene(root, 300, 500));
        primaryStage.show();

        Controller controller = new Controller();
        Register register = new Register(controller);
        register.importFrom(new File("prices.txt"));
    }

    @Override
    public void stop(){
        Controller controller = new Controller();
        Register register = new Register(controller);
        register.exportTo(new File("prices.txt"));
    }

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

错误:

Exception in Application start method
{Coffee=1.50}  // the first entry from the Map prints
java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:473)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:372)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:945)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:973)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication(LauncherImpl.java:198)
    at java.base/java.lang.Thread.run(Thread.java:844)
Caused by: java.lang.NullPointerException
    at sample.Controller.addProduct(Controller.java:54) // fieldContainer.getChildren().clear();
    at sample.Register.importFrom(Register.java:40) // ctrl.addProduct(args[0], new BigDecimal(args[1]));
    at sample.Main.start(Main.java:23) // register.importFrom(new File("prices.txt"));
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1(LauncherImpl.java:919)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait(PlatformImpl.java:449)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater(PlatformImpl.java:418)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater(PlatformImpl.java:417)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop(WinApplication.java:175)
    ... 1 more
Exception running application sample.Main

我自己修好了。现在,所有按钮都会在您 运行 Main 时立即加载。所以这就是我所做的:

我没有在 main 中调用 Register 的 importFrom 方法,而是从 Controller class 中调用它,就像在初始化方法中这样:

private Register mRegister;

public Controller() {
    this.mRegister = new Register(this);
}

@FXML
private void initialize() {
    mRegister.importFrom(new File("prices.txt"));
}