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?
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()
inMainViewController.main(...)
, the FX toolkit is started, an instance of your application classMainViewController
is created, the FX Application Thread is started, and thestart()
method belonging to theMainViewController
instance is invoked on the FX Application Thread.When you call the
FXMLLoader
'sload()
method, it parses the FXML file at the specified location. When it sees thefx:controller
attribute, which I'm assuming isfx: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 theinitialize()
method is called on that instance.So notice now you actually have two instances of
MainViewController
: the one from whichFXMLLoader.load()
was called, and the one created by theFXMLLoader
. The@FXML
-annotated fields in the instance created by theFXMLLoader
are initialized, but the ones in the original instance remain set to the default value ofnull
. Conversely, themainPane
field is initialized in thestart
method in the originalMainViewController
instance, but is never initialized in the instance created by theFXMLLoader
. So when you press the button and invokesetCenterView()
(I assume this is how your FXML is set up), you end up callingmainPane.setCenter()
whenmainPane
is stillnull
.You could probably just about force it to work like this. Something like: remove the
fx:controller
attribute from MainView.fxml, and callloader.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.