0

I am creating a program which has a row of buttons on the top, and a row of buttons on the side. The top buttons control the view, and the side buttons control what object to reference in the view.

My main/root view is a borderpane.

The point is to, as I click on any of these buttons, to change a value in my MainController, and then reload the center view with these new values. I thought it would be so simple as to write a method that would change the value and then set a new center according to these values.

However, as I test it, it can display the two values I have already asked it to display, but gives me a huge load of red error code whenever I run.

My MainViewController looks as follows:

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;



public class MainViewController extends Application {


@FXML
private Button cabin1;
@FXML
private Button cabin2;
@FXML
private Button tab1;
@FXML
private Button tab2;



@FXML
public void setCabinOne() throws IOException {
    cabinIndex=1;
    setCenterView();
}
@FXML
public void setCabinTwo() throws IOException {
    cabinIndex=2;
    setCenterView();
}

@FXML
public void setTabOne() throws IOException {
    tabIndex=1;
    setCenterView();
}

@FXML
public void setTabTwo() throws IOException {
    tabIndex=2;
    setCenterView();
}

public int getCabinIndex() {
    return cabinIndex;
}

public int getTabIndex() {
    return tabIndex;
}

private int tabIndex=0;
private int cabinIndex=1;


public Stage primaryStage;
private BorderPane mainPane;

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

}

@Override
public void start(Stage primaryStage) throws Exception {
    this.primaryStage=primaryStage;
    primaryStage.setTitle("Test");
    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(MainViewController.class.getResource("MainView.fxml"));
    mainPane = loader.load();
    setCenterView();
    Scene scene = new Scene(mainPane);
    primaryStage.setScene(scene);
    primaryStage.show();

}

public void setCenterView() throws IOException {
    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(MainViewController.class.getResource("TestView.fxml"));
    AnchorPane testPane = loader.load();
    TestViewController tvc = loader.<TestViewController>getController();
    tvc.changeLabel(getCabinIndex());
    tvc.changeIndex(getTabIndex());
    mainPane.setCenter(testPane);
}

}

and my TestViewController looks as follows:

import javafx.fxml.FXML;
import javafx.scene.control.Label;


public class TestViewController {

@FXML
private Label cabinIndex;
@FXML
private Label tabIndex;

public void initialise() {
    this.changeLabel(0);
    this.changeIndex(0);

}

public void changeLabel(int n) {
    cabinIndex.setText("Cabin "+Integer.toString(n));
}

public void changeIndex(int n) {
    tabIndex.setText("Tab "+Integer.toString(n));
}

}

What am I doing wrong?

Gustaf Svensson
  • 643
  • 1
  • 7
  • 11
  • Please try to remove all the clutter and remove it to a smaller example. Besides, the error messages would be very helpful. – rethab Oct 16 '14 at 14:08
  • What clutter are you talking about? I'll post the error messages once I have access to my computer again. – Gustaf Svensson Oct 16 '14 at 14:45
  • Welcome to StackOverflow. When you post an example, always try to create a [minimal, complete example](http://stackoverflow.com/help/mcve) that has just enough to reproduce the issue and no more. In your case, you can eliminate the `TestView.fxml` and `TestViewController` entirely, and eliminate everything in `MainViewController` except the `mainPane` and one button. Replace your implementation of `setCenterPane` with just `mainPane.setCenter(new Label("Hello"))` and you will still see the same stack trace. (This is the "clutter" @rethab is referring to.) – James_D Oct 17 '14 at 01:38
  • Additionally: when you get error messages, those error message usually include invaluable information as to what is wrong. It is always helpful to post them - it will both help others diagnose your problem and will also help them to show you how to diagnose them yourself in the future. – James_D Oct 17 '14 at 01:39

1 Answers1

1

Your application is basically structurally wrong: you are using the application subclass as the controller, and this simply won't work (at least, not easily). You need to refactor this with a startup class (subclass of Application) that is distinct from the controller. Virtually any complete example will work as a template for you, but the Oracle tutorial is a good place to start.

What is happening is as follows:

When you call Application.launch() in MainViewController.main(...), the FX toolkit is started, an instance of your application class MainViewController is created, the FX Application Thread is started, and the start() method belonging to the MainViewController instance is invoked on the FX Application Thread.

When you call the FXMLLoader's load() method, it parses the FXML file at the specified location. When it sees the fx:controller attribute, which I'm assuming is fx:controller="MainViewController", it creates an instance of the specified controller class. Once the FXML is parsed, any matching @FXML-annotated fields (belonging to that instance) are initialized with the corresponding objects from the FXML file, and then the initialize() method is called on that instance.

So notice now you actually have two instances of MainViewController: the one from which FXMLLoader.load() was called, and the one created by the FXMLLoader. The @FXML-annotated fields in the instance created by the FXMLLoader are initialized, but the ones in the original instance remain set to the default value of null. Conversely, the mainPane field is initialized in the start method in the original MainViewController instance, but is never initialized in the instance created by the FXMLLoader. So when you press the button and invoke setCenterView() (I assume this is how your FXML is set up), you end up calling mainPane.setCenter() when mainPane is still null.

You could probably just about force it to work like this. Something like: remove the fx:controller attribute from MainView.fxml, and call loader.setController(this). But when you stray that far from the usual patterns used by a framework, your code becomes hard to maintain and debug. I would recommend following the design intended by the API.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • I really appreciate you looking at this. I have now changed the structure so that I have a Main class that just runs the program and starts the views. My problem now is how to be able to make the MainViewController change values in the Main class. Take the value cabinIndex. I initialise it as 0. When I set the center view, I ask it to load the TestView.fxml, create a TestViewController and I ask the TVC to change its label to "Cabin 0". This works fine. What I want to be able to do is click a button in the MainView, and have it change the cabinIndex. How do I reference this correctly? – Gustaf Svensson Oct 17 '14 at 11:29
  • Don't put application logic in your main class: just move those properties (`cabinindex`, etc) to the controller. (In a larger application, you might have a separate class encapsulating all such properties - a model - and pass a reference to it to your controller, but for now I would just keep it simple and define those values in the controller.) – James_D Oct 17 '14 at 11:40
  • Ok, so I have moved the cabinIndex to the MainViewController. My question is now: I want to click Button 1 in MainViewController, and have it change the cabinIndex to 1, then reload the TestView with the new cabinIndex. How can I do this? I'm thinking that I should create some instance of the MainViewController in the TestViewController, and then be able to use its methods there. How can I ensure that it will always reference the current MainViewController? – Gustaf Svensson Oct 17 '14 at 11:46
  • Have a look at http://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml/14190310#14190310 and see if that helps. If not, post a new question as this is now substantially different to your original question. – James_D Oct 17 '14 at 11:47