0

I have a tilepane inside the center of a borderpane. For every entry in a linked hashmap I want to add a button to the tilepane. This tilepane already exists in the fxml file and has the fx:id fieldContainer.

If I add a key-value pair one at a time, the buttons will appear one after the other. But when I try to add them all at once by importing the linked hashmap from a text file, the buttons won't appear. It will load one entry and then throw a nullpointerexception.

The methods for importing the text file and adding the entries to the map all seem to be working fine. I think the problem lies with the fieldContainer. I don't understand why it will only add buttons one by one and not all at once. Could someone please explain what's wrong with my code?

UPDATE: I fixed the nullpointerexception by assigning fieldContainer to a new TilePane at the top of the Controller class:

@FXML
private TilePane fieldContainer = new TilePane();

The error is gone, it prints the entire map, but it still won't show me the buttons. Anyone?

The 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 Register (with import method):

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 Controller (button creation):

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

Main:

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

Error:

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$2(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$9(LauncherImpl.java:919)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$11(PlatformImpl.java:449)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$9(PlatformImpl.java:418)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(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$3(WinApplication.java:175)
    ... 1 more
Exception running application sample.Main
Isoldhe
  • 300
  • 1
  • 7
  • 20
  • do you get any errors or does it just not put anything in the TilePane? – MMAdams Dec 05 '17 at 19:23
  • Sorry, forgot to add the error (I've been at it for too long XD). I added it to my question. – Isoldhe Dec 05 '17 at 19:29
  • looks like someone undid your edit by accident. You had a nullpointerexception in addProduct though, right? Line numbers don't mean much since you copied and pasted it here, what line was it on? – MMAdams Dec 05 '17 at 19:48
  • The line is on the fieldContainer in the Controller class: fieldContainer.getChildren().clear(); When I uncomment it, it throws a nullpointerexception on the line number at the next fieldcontainer in that method: fieldContainer.setAlignment(Pos.TOP_LEFT); In Register class it's this line in importFrom method: ctrl.addProduct(args[0], new BigDecimal(args[1])); And this line in Main: register.importFrom(new File("prices.txt")); – Isoldhe Dec 05 '17 at 19:49
  • So it only loads the first entry from the Map, because the println prints it and after that it throws the error. – Isoldhe Dec 05 '17 at 19:56
  • 2
    Possible duplicate of [What is a NullPointerException, and how do I fix it?](https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – user1803551 Dec 06 '17 at 00:23

1 Answers1

0

I fixed it myself. All buttons now load at once the moment you run Main. So here's what I did:

Instead of calling the Register's importFrom method in main, I called it from the Controller class like in the initialize method like this:

private Register mRegister;

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

@FXML
private void initialize() {
    mRegister.importFrom(new File("prices.txt"));
}
Isoldhe
  • 300
  • 1
  • 7
  • 20