CSC1120: FXML

This reading covers JavaFX's FXML structure and how it functions within a JavaFX application, not only how the objects and their properties are instantiated and assigned, but how the information is stored in memory and accessed while an application is running.

Contents

Introduction

FXML is a declarative language provided by JavaFX to define the layout and appearance of user interface elements. It's an XML-based language, which means it uses tags to describe the UI components, much like how HTML describes web pages. Note that FXML is NOT Java source code. It is not able to be compiled. It is a way to describe how the components of a JavaFX GUI will appear and function. The FXML file will be read in and translated by an object in the start() method, the FXMLLoader, which will load all the components described in the FXML file into memory and link them to the Scene that is being displayed in the Stage.

There are several advantages to using FXML when creating a JavaFX application. First, the layout and appearance of the GUI is completely separated from the programming logic of the application, allowing for the development to be split amongst different teams so designers can focus on the look and feel of the application and developers can concentrate on implementing the behavior of the program in Java. Secondly, FXML makes it easier for non-programmers and people with experience in web design to work on JavaFX projects, as the syntax and structure of FXML is very similar to HTML, the default markup language for web pages. Thirdly, the split between the appearance and the logic of the program and the class that is used to connect the two sides, the Controller follow a well-known software design patter, the Model-View-Controller (MVC) pattern, whose benefits will be discussed in the next section. Finally, adding styling to the components using FXML is nearly identical to styling of web pages, namely Cascading Style Sheets (CSS), which are style directives that are overlaid onto the existing markup and can be easily swapped out or changed to suit a specfic look or feel.

The Model-View-Controller pattern

The Model-View-Controller (MVC) pattern is a design pattern often used in software development for organizing code in an application to separate concerns, thus making it more modular and maintainable. It divides the application into three interconnected components: Model, View, and Controller.

The Components of MVC

MVC Example

An example would be a to-do list application

Benefits of using MVC

There are many good reasons to use the MVC pattern both within a JavaFX application and elsewhere. Below is a non-exhaustive list of benefits that the MVC pattern provides software engineers

The FXML File

An FXML file is a plain text file that is structured hierarchically by using nested tags. A tag is a pair of opening <> and closing </>set of angle brackets. Inside the brackets are the descriptions of the component type and properties. Between the opening and closing brackets will be any information that is contained inside the component. Depending on the component, this could be some text, other components, or even other containers filled with more components. Below is an example of a Button tag in FXML that describes a Button that, when clicked, will open a selected file by calling the openFile method. Note that the properties are separated by a single whitespace.

  <Button onAction="#openFile text="Open"></Button>

When there are no components contained inside a given tag, we can use a shortcut that combines the opening and closing tag into a single tag:

  <Button onAction="#openFile text="Open" />

Here is an example of a container (in this case a Vertical Box, or VBox) that holds a few different Button objects. A VBox will store components vertically so they will appear on the screen from top to bottom. A Horizontal Box (HBox) would display they left to right on the screen. Note that the Button tags are inside the opening and closing tags for the VBox. Also note all of the layout information stored in the VBox tag. Most properties may be defined inside a tag of an object, but the most common properties, since FXML defines the View, are layout, location, and information the Controller needs to update the component, such as the type of listener, the handler method, and sometimes the variable name of the object in the Controller class (if needed).

<VBox alignment="TOP_CENTER" prefHeight="200.0" prefWidth="100.0" spacing="20.0">
  <Button onAction="#openFile text="Open" />
  <Button onAction="#closeFile text="Close" />
  <Label onMouseClicked="#formatHardDrive" text="DO NOT CLICK!!!" />
</VBox>

The top level tag of every FXML file is the Parent container and has some specific properties that are needed for the FXMLLoader to properly read the file. Here is what it would look like:

<BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="jones.Lab9Controller">

After the type of container, the top level tag must contain the following properties:

Here is an example of a complete FXML file. The first line is the header information and tells the FXMLLoader what type of file it is looking at. The import statements that follow are identical in use to Java import statements. They tell the FXMLLoader where to find the source code for the components used in the FXML file.

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>

<BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="jones.Lab9Controller">
   <right>
      <VBox alignment="TOP_CENTER" prefHeight="200.0" prefWidth="100.0" spacing="20.0" BorderPane.alignment="CENTER">
        <Button onAction="#load" prefWidth="87.0" text="Open" />
        <Button onAction="#save" prefWidth="87.0" text="Save" />
        <Button onAction="#reload" prefWidth="87.0" text="Reload" />
        <Button onAction="#grayscale" prefWidth="87.0" text="Grayscale" />
        <Button onAction="#negative" prefWidth="87.0" text="Negative" />
        <Button onAction="#red" prefWidth="87.0" text="Red" />
        <Button onAction="#redGray" prefWidth="87.0" text="Red-Gray" />
        <Button fx:id="filterButton" onAction="#filter" prefWidth="87.0" text="Show Filter" />
      </VBox>
   </right>
   <bottom>
      <Label fx:id="mouseColorLabel" BorderPane.alignment="CENTER" />
   </bottom>
</BorderPane>

The Controller Class

The Controller class is the bridge between the Model and View. It is a normal Java class that will be instantiated by the FXMLLoader when the program starts. When the Controller is instantiated, the FXMLLoader will look through the FXML file and look for any information needed to add to the Controller. This includes any variable names defined in the tags using an fx:id property and any event handler method names. For the program to compile, all handler methods and variable names defined in the FXML file MUST exist in the Controller. These variables and methods should use the @FXML annotation to denote to the compiler that these are defined in the FXML file. Here is an example of what a portion of the Controller for the above FXML file would look like.

@FXML
private Button filterButton;

@FXML
private void filter() {
    ...
}

Note that both the variable and handler method are private. As stated before, this is a normal Java object class. Instance variables should be private unless absolutely necessary, and the handler method, unless it will be called by some other class, should also remain private. Without the @FXML annotation, the FXMLLoader, which is in the start() method of the main class, would not have access to the variable and method and would not be able to instantiate the component or link the method.

It is important to note that the Controller class should only contain code directly related to interacting with the View and/or bridging between the Model and View. Any data or other program logic should be in another class related to the Model and passed into the Controller via a method. Private helper methods and variables are often necessary when an event handler has a complicated or multi-step task to perform. These methods and variables are normal Java source code and should NOT use the @FXML notation. Only information referencing the FXML file should use the annotation.

The FXMLLoader

The last piece of the FXML puzzle is the FXMLLoader. This is a Java class that reads the FXML file and instantiates all of the components defined in the FXML as well as the Controller class, storing everything in memory and linking the Parent object defined in the FXML file to the Scene that gets put into the Stage. The FXMLLoader is instantiated at the beginning of the start() method and is linked to the FXML file.

        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("MyProgram.fxml"));

Since the FXML file is plain text and not source code, we have to load it as a Resource object, which is what the getClass().getResource() code is doing. It is wrapping the text inside of a Java object so we can pass it as a parameter into the setLocation() method. A link to the FXML file is now stored as an instance variable inside the fxmlLoader object. At this point we are ready to load all the information from the FXML file into out program. We now will call the load() method of the FXMLLoader,

        Parent root = fxmlLoader.load();

which will take everything defined inside the FXML file and instantiate and link the contents to the rest of the program. The way this work is the loader will read the FXML file as if it were a Scanner reading one line at a time: