JavaFX: How to update the center view of a borderp

2019-09-01 18:19发布

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?

1条回答
Juvenile、少年°
2楼-- · 2019-09-01 18:34

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.

查看更多
登录 后发表回答