How to get the controller of an included FXML?

不羁岁月 提交于 2020-01-05 07:11:30

问题


I have a simple two tab application build around JavaFXML and scenebuilder. The tabs do nothing at present because I cannot get past a nullpointer exception when trying to load them.

The java and fxml files are arranged in a Netbeans project like this:

Main Application: The application main class MainApp.java sets the scene and is declared as follows:

package tabpane;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;


public class MainApp extends Application {

  private static Stage primaryStage;
  private AnchorPane rootLayout;  


  public MainApp() {}

  @Override
  public void start(Stage primaryStage) {
    primaryStage.initStyle(StageStyle.UNDECORATED);
    MainApp.primaryStage = primaryStage;
    MainApp.primaryStage.setTitle("Account Names");
    initRootLayout();
  }

  public void initRootLayout() {
    try {
      FXMLLoader loader = new FXMLLoader();
      loader.setLocation(MainApp.class.getResource("view/MainView.fxml"));
      rootLayout = (AnchorPane) loader.load(); 
      Scene scene = new Scene(rootLayout);
      primaryStage.setScene(scene);
      primaryStage.show();

    } catch (IOException e) {
        Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, e);
        System.out.println("MainApp: IOException in method: initRootLayout" + e.getMessage());
        System.exit(0);
    }
  }

  public Stage getPrimaryStage() {
    return primaryStage;
  }

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

The MainController class declares the two tabs, the Input tab and Account tab, for the application and an exit button for closing the program. This is the MainView. fxml file:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tabpane1.view.MainController">
   <children>
      <TabPane fx:id="tabPane" prefHeight="345.0" prefWidth="600.0" style="-fx-background-color: blue;" tabClosingPolicy="UNAVAILABLE">
        <tabs>
          <Tab fx:id="inputTab" closable="false" text="Input">
            <content>
                <fx:include source="InputView.fxml" />
            </content>
          </Tab>
          <Tab fx:id="detailTab" closable="false" text="Detail">
            <content>
               <fx:include source="DetailView.fxml" />
            </content>
          </Tab>
        </tabs>
      </TabPane>
      <Button fx:id="exitBtn" layoutX="529.0" layoutY="355.0" mnemonicParsing="false" onAction="#handleExitBtn" text="Exit" />
   </children>
</AnchorPane>

This is the java controller class for the main view contained in MainController.java:

package tabpane1.view;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;

public class MainController implements Initializable    {
    @FXML public TabPane tabPane;
    @FXML public Tab inputTab;
    @FXML public Tab accountTab;
    @FXML private Button exitBtn;

    public DetailController detailController;      
    public InputController inputController;

    @Override
    public void initialize(URL location, ResourceBundle resources) { 
      detailController = new FXMLLoader(getClass().getResource("DetailView.fxml")).getController(); 
//      System.out.println(detailController.getClass());
      inputController = new FXMLLoader(getClass().getResource("InputView.fxml")).getController(); 
//      System.out.println(inputController.getClass());
    }

    @FXML private void handleExitBtn() {
        System.exit(0);
    }
}

The two println statements are simply trying to get the references to the two tabs, which are detailController and inputController, to do something. However, when these are uncommented and run, the following dump is output:

MainApp: IOException in method: initRootLayout
SEVERE: null
file:/G:/J2EE/TabPane_1/dist/run1314937364/TabPane_1.jar!/tabpane1/view/MainView.fxml
javafx.fxml.LoadException: 

file:/G:/J2EE/TabPane_1/dist/run1314937364/TabPane_1.jar!/tabpane1/view/MainView.fxml

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
    at tabpane1.MainApp.initRootLayout(MainApp.java:43)
    at tabpane1.MainApp.start(MainApp.java:33)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NullPointerException
    at tabpane1.view.MainController.initialize(MainController.java:24)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2548)
    ... 13 more

When the printlns are commented out, the outcome is as expected, that is the tab pane is correctly displayed:

As mentioned the detail and input controllers are empty, but for completeness they are as follows:

InputView.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tabpane1.view.InputController">

</AnchorPane>

The controller InputController.java:

package tabpane1.view;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.Initializable;

public class InputController implements Initializable   {

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

DetailView.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: transparent;" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tabpane1.view.DetailController" />

The controller DetailController.java:

package tabpane1.view;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.Initializable;

public class InputController implements Initializable   {

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

回答1:


The way to get reference to the controller of an embedded FXML is described in this article.

You simply have to give an fx:id to the included resource:

   <fx:include fx:id="IncludedView" source="InputView.fxml" />

Get a reference to IncludedView in the MainController:

@FXML private Parent IncludedView;

Get a reference to its controller simply by appending the word Controller in addition to the variable name of the embedded element:

@FXML private InputController IncludedViewController;

That is all.

Test it with:

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Parent;

public class MainController implements Initializable    {

    @FXML private Parent IncludedView;
    @FXML private InputController IncludedViewController; // $IncludedView;+Controller

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

        System.out.println(IncludedView.getClass());
        System.out.println(IncludedViewController.getClass()    );
    }

    @FXML private void handleExitBtn() {
        System.exit(0);
    }
}

An MRE for the sake of future readers:


import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class GetEmbeddedController extends Application {

    @Override
    public void start(Stage primaryStage) {
        try {
            Pane root = (Pane) FXMLLoader.load(getClass().getResource("MainView.fxml"));
            primaryStage.setScene(new Scene(root));
            primaryStage.show();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

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

MainView.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/10.0.1" 
  xmlns:fx="http://javafx.com/fxml/1" fx:controller="MainController">
   <children>  
      <fx:include fx:id="includedView" source="IncludedView.fxml" />         
      <Button layoutX="401.0" layoutY="354.0" onAction="#handleTestBtn" text="Test Included Controller" />
   </children>
</AnchorPane>

MainController.java

import javafx.fxml.FXML;
import javafx.scene.Parent;

public class MainController{

    @FXML private Parent includedView; //not used in this demo
    /*
     * Get a reference to IncludedView controller simply by appending
     * the word Controller in addition to the variable name of the embedded
     * element:$IncludedView;+Controller
     */
    @FXML private IncludedViewController includedViewController;

    @FXML private void handleTestBtn() {
        includedViewController.showAlert();
    }
}

IncludedView.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Text?>

<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/10.0.1" 
xmlns:fx="http://javafx.com/fxml/1" fx:controller="IncludedViewController">
   <children>
      <Text layoutX="277.0" layoutY="196.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Included Fxml" />
   </children>
</AnchorPane>

IncludedViewController.java

import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;

public class IncludedViewController{

    void showAlert(){
        Alert alert = new Alert(AlertType.INFORMATION);
        alert.setContentText("Alert invoked by IncludedViewController ");
        alert.show();
    }
}


来源:https://stackoverflow.com/questions/59551896/how-to-get-the-controller-of-an-included-fxml

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!