-1

I have desktop app with side menu bar. Main window is BorderPane with InsertLeft containing VBox. I set Hbox buttons and their behaviour then I add them one by one to the VBox. InsertCenter has just Pane with alot of elements.

I've created 3 fxml files for each GUI layout.

  • sample.fxml - BorderPane: InsertLeft->Menu(VBox), InsertCenter->Empty Pane
  • tab1_content.fxml - Pane filled with ProgressBar, Labels and Buttons
  • tab2_content.fxml - Not yet implemented (Empty Pane)

Each of these fxml files has their controllers.

I would like to switch content of borderPane.center() inside sample.fxml on menu button click.

I've managed to fix some issues, but main problem is with loading data into .fxml views.

As I run my App it works perfectly, each fxml file has his FXMLLoader which will load content into borderPane right inside main Controller.

Problem occurs while I click on Buttons. It will switch panes, but actual content will reset to default state and Main.class initialization is completely ignored. Button listeners and label values are not initialized. It's just empty fxml layout. Every variable inside Main.class, what I want to access from Tab1Controller is returning NullPointerException - even methods.

Each controller extends AbstractController, which contains Main.class instance which is initialized inside start() method. So i should be able to have access to every Main.class method/variable at any time.

Issue Gif:

enter image description here

Some code samples:

My Main.class start() method:

    public Controller myController;

    @Override
        public void start(Stage primaryStage) throws Exception {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(getClass().getResource("/sample.fxml"));
            myController = new Controller();
            myController.setMainApp(this);
            loader.setController(myController);
            Parent root = loader.load();
            primaryStage.setTitle("Simple App");
            primaryStage.setScene(new Scene(root));
            primaryStage.show();

            <other stuff>
    }

public void setDefaultViewProperties(){
        currentScanningFileProperty = new SimpleStringProperty();
        myController.tab1Controller.actualPath.textProperty().bind(currentScanningFileProperty); //NullPointerException while called from Controller
        fileCounterProperty = new SimpleLongProperty();
        myController.tab1Controller.numOfScanned.textProperty().bind(fileCounterProperty.asString());
        maliciousFilesCounterProperty = new SimpleIntegerProperty();
        myController.tab1Controller.numOfMaliciousFiles.textProperty().bind(maliciousFilesCounterProperty.asString());

        myController.tab1Controller.fileChoiceBtn.setOnMouseClicked(event -> chooseFile());
        myController.tab1Controller.runScanBtn.setOnMouseClicked(event -> new Thread(() -> {
            try {
                resetValues();
                startFileWalking(chosenFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start());
    }

MainController:

package sample;

import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;

public class Controller extends AbstractController implements Initializable{
    public HBox sideMenu;
    public VBox mainMenu;
    public BorderPane borderPane;
    public Boolean isButton1Pressed = false;
    public Boolean isButton2Pressed = false;
    public static final String TAB_1 = "TAB-1";
    public static final String TAB_2 = "TAB-2";
    public Button malwareButton;
    public Button webShieldButton;
    public Tab1Controller tab1Controller;
    public Tab2Controller tab2Controller;


    @Override
    public void initialize(URL location, ResourceBundle resources) {

        createMenuButtons();
        setSideMenu();
        setMenuButtonsListeners();
    }

    private void setSideMenu(){
        mainMenu.getChildren().add(item(malwareButton));
        mainMenu.getChildren().add(item(webShieldButton));
        mainMenu.setStyle("-fx-background-color:#004D40");
    }

    private HBox item(Button menuButton){
        menuButton.setPrefSize(200, 50);
        menuButton.setStyle("-fx-background-color: transparent;");
        menuButton.setTextFill(Color.web("#E0F2F1"));
        menuButton.setPadding(Insets.EMPTY);
        sideMenu = new HBox(menuButton);
        return sideMenu;
    }

    public void setMenuButtonsListeners(){
        malwareButton.setOnMousePressed(event -> {
            setButtonStylePressed(malwareButton);
            setButtonStyleUnpressed(webShieldButton);
            isButton1Pressed = true;
            isButton2Pressed = false;
            loadTab1Content();
            main.setDefaultViewProperties();
        });

        webShieldButton.setOnMousePressed(event -> {
            setButtonStylePressed(webShieldButton);
            setButtonStyleUnpressed(malwareButton);
            isButton1Pressed = false;
            isButton2Pressed = true;
            loadTab2Content();
        });

        malwareButton.setOnMouseExited(event -> {
            if(!isButton1Pressed){
                setButtonStyleUnpressed(malwareButton);
            }
        });
        webShieldButton.setOnMouseExited(event -> {
            if(!isButton2Pressed){
                setButtonStyleUnpressed(webShieldButton);
            }
        });

        malwareButton.setOnMouseEntered(event -> setButtonStylePressed(malwareButton));
        webShieldButton.setOnMouseEntered(event -> setButtonStylePressed(webShieldButton));
    }

    public void setButtonStylePressed(Button btn){
        btn.setStyle("-fx-background-color: #E0F2F1");
        btn.setTextFill(Color.web("#004D40"));
    }

    public void setButtonStyleUnpressed(Button btn){
        btn.setStyle("-fx-background-color: transparent");
        btn.setTextFill(Color.web("#E0F2F1"));
    }

    private void loadTab1Content(){
        FXMLLoader tab1loader = new FXMLLoader();
        tab1loader.setLocation(getClass().getResource("/tab_1_content.fxml"));
        try {
            if (tab1Controller == null){
                tab1Controller = new Tab1Controller();
            }
            tab1loader.setController(tab1Controller);
            borderPane.setCenter(tab1loader.load());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void loadTab2Content(){
        FXMLLoader tab2loader = new FXMLLoader();
        tab2loader.setLocation(getClass().getResource("/tab_2_content.fxml"));
        try {
            if (tab2Controller == null){
                tab2Controller = new Tab2Controller();
            }
            tab2loader.setController(tab2Controller);
            borderPane.setCenter(tab2loader.load());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void createMenuButtons(){
        malwareButton = new Button();
        malwareButton.setText(TAB_1);
        webShieldButton = new Button();
        webShieldButton.setText(TAB_2);
    }
}

Tab1Controller:

package sample;

import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;

/**
 * Created by admin on 5. 5. 2018.
 */
public class Tab1Controller extends AbstractController implements Initializable {


    public ProgressBar progressBar;
    public Button runScanBtn;
    public Button fileChoiceBtn;
    public Label chosenPath;
    public Label actualPath;
    public Label numOfMaliciousFiles;
    public Label hashValue;
    public Label scanFinishedMsg;
    public Label numOfScanned;
    public Button showFoundMalwareButton;



    @Override
    public void initialize(URL location, ResourceBundle resources) {


        runScanBtn.setDisable(true);
        scanFinishedMsg.setVisible(false);
        showFoundMalwareButton.setVisible(false);
        showFoundMalwareButton.setOnAction(event -> showPopupWindow());
    }

Update#1 - Updating fxml values through Main.class after button click

I've finally managed to run app without exception. I had to create next Controller for pane fxml layout itself called Tab1Controller. When I initialized Controller, it instantly initialized Tab1Controller inside. So when I want to change Center BorderPane label i had to call myController.tab1Controller.tabLabel.setText()

I don't know if it's good approach to this problem. But now I'm back to my old problem. When I click on TAB-1 it will load content, but values are not initialized to default state.

For example I have couple of labels updated in real time. I binded some SimpleProperties into it with default values. It worked before, but as I have three controllers it will load data for a first time, but when I click TAB-1 button it will load just fxml content, but it will not set those labels.

So i made public method inside Main.class which I will call everytime I switch to TAB-1 from Controller.

public void setDefaultViewProperties(){
    myController.tab1Controller.actualPath.textProperty().bind(currentScanningFileProperty);
    myController.tab1Controller.numOfScanned.textProperty().bind(fileCounterProperty.asString());
    myController.tab1Controller.numOfMaliciousFiles.textProperty().bind(maliciousFilesCounterProperty.asString());
}

But now everytime I click on TAB-1 I've got

java.lang.NullPointerException: Cannot bind to null
martin1337
  • 2,384
  • 6
  • 38
  • 85
  • Why replace the button listeners? Just do something like `borderPane.setCenter(newContent);` Also there's no need to use fxml. You can use java code to create a scene too... – fabian May 04 '18 at 10:09
  • I tried to do borderPane.setCenter(fxml pane resource). It's working, but all of my views are not working. I have to reconnect my code in Main.class to this new pane. Because none of my views inside that new pane work. I will need to send something like context (used in Android development) to my Main.class. – martin1337 May 04 '18 at 10:16
  • No Idea what you're doing wrong. Maybe you need to do something from the answers here https://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml when loading the fxml. BTW: What's wrong with passing the fxml to use as parameter to the `menuDecorator` method instead of deciding based on some different string and hoping you never forget to adjust the string in the check when adjusting the button text? – fabian May 04 '18 at 10:21
  • It sounds like the problems are not in the code you posted. "All of my views are not working": what precisely does this mean? Also, it looks like you really want toggle buttons here, instead of regular buttons, and you can get rid of a lot of this code by moving the styles to CSS, which have appropriate selectors (e.g. `:hover`) that you can use instead of implementing handlers for entered, exited, etc. – James_D May 04 '18 at 12:03
  • "All of my views are not working" means, that when I push button and fxml content is loaded into the pane. That content does not work. When i click on buttons they dont respond to it, Label text is not updated (when I initialized that text inside Main.class method). It looks like that it will add fxml content, but its not connected with Main.class. – martin1337 May 04 '18 at 12:08
  • For example if i call myController.descriptionLabel.setText("this is label description") from my Main.class it will not affect that label (Label descriptionLabel is public in Controller.class – martin1337 May 04 '18 at 12:29
  • Please read https://stackoverflow.com/help/mcve and act accordingly. – kleopatra May 05 '18 at 08:47
  • you did read the help page I referenced in my last comment, didn't you ;) So why don't you provide that mcve as suggested? It's the only way to get reasonable help .. without, it's mostly guesswork and a waste of time for everybody .. – kleopatra May 05 '18 at 09:39

3 Answers3

0

You can make two pane and switch between them using setVisible() method

example:

void btn1Clicked() {
   pane1.setVisible(true);
   pane2.setVisible(false);
}

void btn2Clicked() {
   pane1.setVisible(false);
   pane2.setVisible(true);
}
Houari Zegai
  • 98
  • 1
  • 10
0

You could use a TabPane to achieve this behaviour: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TabPane.html

seb
  • 56
  • 6
0

Solved. I'm not sure how, but setDefaultViewProperties() are not throwing NullPointerException at the moment. I did not change anything inside the code:

malwareButton.setOnMousePressed(event -> {
                setButtonStylePressed(malwareButton);
                setButtonStyleUnpressed(webShieldButton);
                isButton1Pressed = true;
                isButton2Pressed = false;
                loadTab1Content();
                main.setDefaultViewProperties();
            });
martin1337
  • 2,384
  • 6
  • 38
  • 85