How to understand FXMLLoader of JavaFX?

Joe

Thành viên VIP
21/1/13
3,065
1,342
113
Hi
Again I found this question "Thắc mắc về Java FX " on a popular Vietnamese forum for "developers" of all kinds. The questioner has developed a MVC JavaFX app which combines App or Model, View and Controller. The View is usually a FXML file and the Controller is the processing of the view while the Model is the starting program. However, it seems to me that the questioner does NOT understand the fundamental MVC and the basic Object Oriented Programming (here: JavaFX). If he does not understand how JFXMLLoader works then how can he develop a functioning MVC app based on JavaFX? And the gurus who advised and gave him the solution which is so amusing and so wrong that I have to laugh =))

JavaFX is usually a MVC application. As mentioned above, the Model is the starting app which can be very small or quite big and complex. Example:
Java:
public class MyMVC extends Application {
        public void start(Stage stage) throws Exception {
            stage.setTitle("It's an example");
            // create an JFXMLLoader instance with the VIEW fxml file
            FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml));
            // load the VIEW and get the returned ROOT (defined in the VIEW)
            AnchorPane root = (AnchorPane)loader.load();
            // fetch the CONTROLLER only after the VIEW is loaded
            myController mCon = loader.<MyController>getController();
            // set up the SCENE and define the exit scenario
            stage.setScene(new Scene(root));
            stage.setOnCloseRequest(e -> mCon.exit()); // neccessary  if stop() is not implemented
            // and show the SCENE
            stage.show();
        }
        // necessary only if CONTROLLER won't do the clean up.
        public void stop() {
            // do the clean up
        }
}
The VIEW always contains the VIEW components (e.g. TextField, ComboBox, Button, etc.) and its CONTROLLER (see fx:controller) and could have the styling sheet (not a must, see <stylesheets>) and is usually as following:
Java:
<AnchorPane id="AnchorPane" fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity"
     minHeight="-Infinity" minWidth="-Infinity" prefHeight="575.0" prefWidth="475.0"
     xmlns:fx="http://Congdongjava.com/fxml" fx:controller="MyController">
   <children>
     <GridPane alignment="center" hgap="10" vgap="20">
        <padding><Insets top="10" right="20" bottom="20" left="20"/></padding>
        <Text fx:id="myText" text = "My own text"
              ....

        </HBox>
        <HBox spacing="25"  alignment="bottom_center"
              ....
              <children>
                <Button fx:id="search" onAction="#searchIt" text="SEARCH"
                        prefHeight="40.0" prefWidth="120.0" />
                <Button fx:id="save" onAction="#saveIt"  text="SAVE"
                        prefHeight="40.0" prefWidth="120.0" />
              </children>
        </HBox>
        ...
   </children>
   <stylesheets>
       <URL value="@MyFileCSS.css" />
   </stylesheets>
</AnchorPane>
and the CONTROLLER which is instantiated and run by FXMLLoader via the VIEW
Java:
public class MyController implements Initializable {
    @FXML Text myText;  // defined in the VIEW
    @FXML Button search, save; // defined in the View
    // method define in VIEW
    @FXML public void searchIt() {
        // my codes
    }
    // method define in VIEW
    @FXML public void saveIt() {
        // my codes
    }
    // Initialization if needed
    public void initialize(URL location, ResourceBundle resources) {
      // do some initialization of DB connection, etc.
    }
    public void exit() {
        // do DB saving
        Platform.exit();
    }
}
The Model depicts the processing sequence of an application which is on all computers and Operating Systems the same: instantiate
  • the View (FXMLLoader) and
  • the Controller.
The ROOT for the SHOW has to be retrieved from the VIEW which could be the returned object of either load() or getRoot().

How FXMLLoader works?
FXMLLoader loads (method load ) the source VIEW (fxml file) and instantiates all the VIEW components, then the Controller (fx:controller) BEFORE any access or processing with the components of the View or the Controller and its methods can be made. Otherwise NULL pointer exception is arisen. For the presentation of the app JavaFX requires a STAGE and a SCENE. Both have to be started within the MODEL.

The mentioned "developer" asked:
Cho mình hỏi vì sao mình không thể set controller trước lệnh Parent roor = … vậy .! ( Vì mình thấy nó cx không liên quan lắm mà chạy cứ bị NuLL Pointer). Mình cảm ơn
And the replies were very vague, voodoo and WRONG. Funnily he accepted one of the answers as the solution.
Vấn đề không phải ở đoạn Parent root mà nằm ở fxmlLoader.load(). Khi bạn khai báo new FXMLLoader(...) thì nội dung từ tập tin fxml chưa được nạp lên, giao diện chỉ là 1 vùng rỗng (null), bạn phải gọi đến load để nạp giao diện lên và giá trị trả về là khung ngoài cùng bao tất cả (parent).
Nói cho đúng thì trước khi gọi đến load thì root đang bằng null.
Java:
FXMLLoader fxmlLoader = new FXMLLoader(...);
Parent p;
p = (Parent)fxmlLoader.getRoot();
print(p); // null
fxmlLoader.load(); // p = (Parent)fxmlLoader.load();
p = (Parent)fxmlLoader.getRoot();
print(p); // non-null object
NO. As said the FXMLLoader instantiates the VIEW only after the method load() is invoked. Then the VIEW components are created together with the CONTROLLER. Therefore method load() must be invoked BEFORE any access to the Controller or to the Root. The following explanation is WRONG.
Java:
FXMLLoader fxmlLoader = new FXMLLoader(...); // OK
Parent p; // superfluous
p = (Parent)fxmlLoader.getRoot() // WRONG method. Method load() must be invoked to load and to instantiate the VIEW and the CONTROLLER
print(p); // null
and superflous:
Java:
FXMLLoader fxmlLoader = new FXMLLoader(...);
Parent p;
...
fxmlLoader.load(); // p = (Parent)fxmlLoader.load(); <--It's correct! Why commented HERE and do a superfluous getRoot()?
p = (Parent)fxmlLoader.getRoot(); // totally superfluous
print(p); // non-null object
The NULL Pointer problem that the mentioned "developer" got was the invocation of two Controllers PRIOR the VIEW is instatiated by the statement Parent root = (Parent)fxmlLoader.load();

The so-called Gurus didn't see that. They seem to be unclear and confused about MVC sequential working procedure in general and JavaFX in particular.
 
Sửa lần cuối: