Expressing the UI for Enterprise Applications with JavaFX 2.0 FXML - Part Two

By James L. Weaver

Leverage the power of the FX Markup Language to define the UI for enterprise applications.

Published September 2012

Downloads:

Download: Java FX

Download: NetBeans IDE

Download: Sample Project (Zip)

JavaFX 2.0 is an API and runtime for creating Rich Internet Applications (RIAs). JavaFX was introduced in 2007, and version 2.0 was released in October 2011. One of the advantages of JavaFX 2.0 is that the code can be written in the Java language using mature and familiar tools.

This article, which is the second in a two-part series, focuses on using the capabilities of FX Markup Language (FXML), a facility that comes with JavaFX 2.0, to quickly define the user interface for enterprise applications.

The purpose of FXML is to enable the expression of the UI using XML. Classes that contain FXML functionality are located in the javafx.fxml package, and they include FXMLLoader, JavaFXBuilderFactory, and an interface named Initializable. This article builds upon Part One of this series by extending the SearchDemoFXML example to include more concepts and techniques for creating an enterprise application using FXML.

Overview of the SearchDemoFXML Application

In Part One of this series, an example application named SearchDemoFXML was employed to help you learn how to leverage FXML in JavaFX 2.0 applications. In this article, we’re going to give the application an alternative UI, expressing it in a new FXML document, without making any modifications to the controller class, SearchDemoController.java.

As shown in Figure 1 and Figure 2, this example contains some of the same UI components as the original example: 

  • A TextField and Button for searching media located in iTunes
  • A TableView for displaying the results of the search
  • An ImageView for viewing the cover of the selected album
  • A Button that opens a browser tab for previewing an audio/video clip from the selected title

However, additional components have been added and the layout and navigation have been modified. Also, Cascading Style Sheets (CSS) and localization have been implemented in this FXML example. The SearchDemoFXML project that you'll download in the next section contains the code for this example, portions of which we’ll highlight during the course of this article.

Figure 1: Screen Capture of the SearchDemoFXML Application

When the application starts up, if the locale of the user’s machine matches a locale supported by the SearchDemoFXML application, the text on the UI controls will be localized. The screen shot in Figure 1 shows the appearance of the application when running on a machine whose locale is en_US. When you enter a title or artist in the text field and click the button, as shown in Figure 1, the table is populated with a list of songs returned by the query from the iTunes server.

Selecting a song from the table and selecting the Detail tab reveals the album cover of the selected song, as shown in Figure 2. Clicking the Preview button opens up a new tab in the default browser with a video or audio clip.

Figure 2: Screen Capture of SearchDemoFXML During a Search

Obtaining and Running the SearchDemoFXML Project

  • Download the NetBeans project file, which includes the SearchDemoFXML project.
  • Expand the project into a directory of your choice.
  • Start NetBeans, and select File -> Open Project.
  • From the Open Project dialog box, navigate to your chosen directory and open the SearchDemoFXML project, as shown in Figure 3. If you receive a message dialog stating that the jfxrt.jar file can't be found, click the Resolve button and navigate to the rt/lib folder subordinate to where you installed the JavaFX 2.0 SDK.

Note: You can obtain the NetBeans IDE from the NetBeans site.

Figure 3: Opening the SearchDemoFXML Project in NetBeans

  • To run the application, click the Run Project icon on the toolbar, or press the F6 key. The Run Project icon looks like the Play button on a media (for example, DVD) player, as shown in Figure 4.

Figure 4: Running the SearchDemoFXML Application in NetBeans

The SearchDemoFXML application should appear in a window, as shown previously in Figure 1. Go ahead and play with the application, searching for song titles, album titles, and artists. When you’re ready, we’ll walk through the FXML and related features added to this version of the program.

Analyzing the SearchDemoFXML Application

Before diving deeply into the code, let’s analyze the pieces shown in Figure 5, which comprise the SearchDemoFXML application.

Figure 5: Diagram of the SearchDemoFXML Application

Starting from the top left of Figure 5, SearchDemo.java is the main module of our JavaFX application, extending Application and containing main() and start() methods. Its role in the SearchDemoFXML application is to create a scene and populate it with the scene graph obtained from either the search_demo_w_tabs.fxml file or the search_demo.fxml file via the FXMLLoader.load() method.

To enable this choice, upon startup, the application checks for a named parameter named tabs. If the value of the tabs named parameter is true, then the search_demo_w_tabs.fxml file is loaded; otherwise, the search_demo.fxml file is loaded. This produces one of the user interfaces shown in the lower left of Figure 5, depending upon the value of the tabs named parameter.

The search_demo_w_tabs.fxml and search_demo.fxml files in the top middle of Figure 5 contain two very different XML-representations of the scene graph. They also specify that the SearchDemoController.java file is a controller class, and it delegates events that occur to UI components to handler methods in the controller.

The SearchDemoController controller class in the bottom middle of Figure 5 holds references to UI components expressed in the search_demo_w_tabs.fxml and search_demo.fxml documents, and it handles events that occur in them.

Turning to the right side of Figure 5, the SearchDemoController invokes methods of the RestFX classes to query the iTunes services REST interface. RestFX is a library external to JavaFX 2.0 that is leveraged here for communication with the iTunes REST endpoints and parses its JSON responses. The “See Also” section at the end of this article has a link to the REST/FX project.

Loading the Desired FXML Document on Startup

As pointed out in the previous section, SearchDemo.java is the main module of our JavaFX application. It is shown in Listing 1:

package demos.search;

import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.SceneBuilder;
import javafx.stage.Stage;

public class SearchDemo extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        System.out.println("In SearchDemo#start()");
        Object tabsParamObj;
        Object languageParamObj;
        Object countryParamObj;
        String fxmlFile = "search_demo.fxml";
        Locale locale = Locale.getDefault();
        Map<String, String> namedParams = getParameters().getNamed();
        if (namedParams != null) {
          tabsParamObj = namedParams.get("tabs");
          if (tabsParamObj != null) {
            System.out.println("param tabs:" + tabsParamObj);
            if (((String)tabsParamObj).equalsIgnoreCase("true")) {
              fxmlFile = "search_demo_w_tabs.fxml";
            }
          }
          else {
            System.out.println("param tabs not found");
          }
          
          languageParamObj = namedParams.get("language");
          if (languageParamObj != null) {
            System.out.println("param language:" + languageParamObj);
          }
          else {
            System.out.println("param language not found");
          }

          countryParamObj = namedParams.get("country");
          if (countryParamObj != null) {
            System.out.println("param country:" + countryParamObj);
          }
          else {
            System.out.println("param country not found");
          }
        
          if ((languageParamObj != null) &&
              ((String)languageParamObj).trim().length() > 0) {
            if ((countryParamObj != null) && 
                ((String)countryParamObj).trim().length() > 0) {
              locale = new Locale(((String)languageParamObj).trim(),
                                  ((String)countryParamObj).trim());
            }
            else {
              locale = new Locale(((String)languageParamObj).trim());
            }
          }
        }
        else {
          System.out.println("No params found for SearchDemo");
        }
        
        Parent root = FXMLLoader.load(getClass().getResource(fxmlFile),
          ResourceBundle.getBundle("demos/search/search_demo", locale));
        System.out.println("Locale set to:" + locale);
        
        primaryStage.setTitle("Search Demo");
        primaryStage.setWidth(650);
        primaryStage.setHeight(500);

        primaryStage.setScene(
          SceneBuilder.create()
            .root(root)
            .stylesheets("demos/search/myStyles.css")
            .build()
        );
        primaryStage.show();
    }

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

Listing 1: SearchDemo.java

As shown in Listing 1, the values of three application-specific named parameters are obtained: tabs, language, and country:

  • tabs indicates whether the FXMLLoader.load()method should load the search_demo_w_tabs.fxml document or the search_demo.fxml document.
  • language indicates which language should be used for localizing the application. If the language parameter isn’t supplied, the language specified by the default locale is used.
  • country indicates which language should be used for localizing the application. If the country parameter isn’t supplied, the country specified by the default locale is used.

The FXMLLoader.load() invocation shown in Listing 1 then loads the desired FXML document and ResourceBundle.

To try out these parameters in the SearchDemoFXML project, you can enter them into the Run section of the Project Properties dialog box in NetBeans, as shown in Figure 6.

Figure 6: The Run Section of the Project Properties Dialog Box in NetBeans

As you learned in Part One of this series, after the FXML document is loaded, the scene graph is instantiated and assigned to the root property of the scene, which is associated with the stage and made visible with its show() method.

Understanding the FXML Used in Our Tabbed UI

As shown in Figure 5, the search_demo_w_tabs.fxml document contains an XML representation of the scene graph. Go ahead and peruse this document, which is shown in Listing 2, and we’ll point out concepts introduced in this document that weren’t covered in Part One:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.web.*?>
<?import javafx.geometry.*?>
<?import demos.search.*?>

<BorderPane id="app-border" fx:controller="demos.search.SearchDemoController" 
  xmlns:fx="http://javafx.com/fxml">
  <top>
    <HBox>
      <children>
        <ImageView>
           <image>
                <Image url="@javafx-logo.png"/>
           </image>
        </ImageView>
        <Region HBox.hgrow="ALWAYS"/>
        <Label id="app-title" text="%appTitle"/>
        <Region>
          <HBox.hgrow>ALWAYS</HBox.hgrow>
        </Region>
        <HBox>
          <children>
            <HBox spacing="5" fillHeight="false">
              <HBox.margin>
                <Insets top="20" right="10"/>
              </HBox.margin>
              <children>
                <TextField fx:id="searchTermTextField" prefColumnCount="11"
                    promptText="%titleOrArtist" 
                    onAction="#handleSearchAction"/>
                <Button fx:id="searchButton" disable="false" 
                    onAction="#handleSearchAction"/>
              </children>
            </HBox>
            <Label fx:id="statusLabel"/>
          </children>
        </HBox>
      </children>
    </HBox>
  </top>

  <center>
    <TabPane>
      <tabs>
        <Tab text="%search" closable="false">
          <content>
            <TableView fx:id="resultsTableView">
              <fx:define>
                 <ResultCellValueFactory fx:id="resultCellValueFactory"/>
              </fx:define>
                <columns>
                  <ResultTableColumn key="itemName" text="%name" 
                      prefWidth="220" 
                      cellValueFactory="$resultCellValueFactory"/>
                  <ResultTableColumn key="itemParentName" text="%album" 
                      prefWidth="220" 
                      cellValueFactory="$resultCellValueFactory"/>
                  <ResultTableColumn key="artistName" text="%artist" 
                      prefWidth="220" 
                      cellValueFactory="$resultCellValueFactory"/>
                </columns>
            </TableView>
          </content>
        </Tab>
        <Tab text="%detail" closable="false">
          <content>
            <VBox fillWidth="false" alignment="topCenter" spacing="6" 
                style="-fx-padding: 10 0 0 0">
              <children>
                <StackPane prefWidth="120" prefHeight="120" 
                    style="-fx-border-color: #929292; -fx-border-width: 1px">
                  <children>
                      <ImageView fx:id="artworkImageView"/>
                  </children>
                </StackPane>
                <Button fx:id="previewButton" text="%preview" 
                    onAction="#handlePreviewAction"/>
              </children>
            </VBox>
          </content>
        </Tab>
      </tabs>
    </TabPane>
  </center>
  <bottom>
    <HBox id="footer">
      <children>
        <Label fx:id="statusLabel"/>  
      </children> 
    </HBox>  
  </bottom>
</BorderPane>

Listing 2: search_demo_w_tabs.fxml

Accessing Resources Relative to the FXML Document

As shown in the following snippet from Listing 2, a resource (for example, an image in a PNG file) can be retrieved from a location relative to the FXML file by using the @ prefix:

        <ImageView>
           <image>
                <Image url="@javafx-logo.png"/>
           </image>
        </ImageView>

Note that this is a different technique than used for the other ImageView instances in this example, because in those cases, the Image objects are managed by the controller.

Setting Static Properties

You might have noticed that if you resize the SearchDemoFXML application (when search_demo_w_tabs.fxml has been loaded), the application title stays horizontally centered in the header. This is due to the Region instances shown in Listing 3 (which is a snippet from Listing 2) that act as horizontal “springs”:

    <HBox>
      <children>
        <ImageView>
           <image>
                <Image url="@javafx-logo.png"/>
           </image>
        </ImageView>
        <Region HBox.hgrow="ALWAYS"/>
        <Label id="app-title" text="%appTitle"/>
        <Region>
          <HBox.hgrow>ALWAYS</HBox.hgrow>
        </Region>
        <HBox>
          <children>
            <HBox spacing="5" fillHeight="false">
              <HBox.margin>
                <Insets top="20" right="10"/>
              </HBox.margin>
              <children>
                <TextField fx:id="searchTermTextField" prefColumnCount="11"
                    promptText="%titleOrArtist" 
                    onAction="#handleSearchAction"/>
                <Button fx:id="searchButton" disable="false" 
                    onAction="#handleSearchAction"/>
              </children>
            </HBox>
            <Label fx:id="statusLabel"/>
          </children>
        </HBox>
      </children>
    </HBox>

Listing 3: Region Instances Act Like Springs

The uses of HBox.hgrow in Listing 3 represent static properties, and they are equivalent to the following Java code, which is not part of this example application:

    HBox.setHgrow(fooRegion, Priority.ALWAYS);

Note that the same technique is used with HBox.margin in Listing 3 to set a margin around the HBox that holds the TextField and Button. For reference, here’s the equivalent Java code:

    HBox.setMargin(fooHBox, new Insets(20, 10, 0, 0));

Leveraging a JavaFX Cascading Style Sheet

As shown in the following snippet from Listing 2, an FXML element can reference a rule in a cascading style sheet associated with the scene:

        <Label id="app-title" text="%appTitle"/>

In this case, the styles expressed in the #app-title selector in Listing 4 will be applied to the Label in the snippet above. Please note the distinction between using id for associating an element with a CSS rule and using fx:id for mapping an element to an instance variable in the controller.

#app-border {
  -fx-border-color: grey;
  -fx-border-width: 3;
}

#footer {
  -fx-border-color: grey;
  -fx-border-width: 1;
  -fx-padding: 5;
}

#app-title {
  -fx-font-size: 20pt;
  -fx-font-weight: normal;
  -fx-text-fill: grey;
  -fx-padding: 15 0 0 0;
}

Listing 4: myStyles.css

In addition, as you learned in Part One of this series, the text for the Label will be retrieved from the ResourceBundle loaded by the FXMLLoader.load() method in Listing 1. For example, when the locale is pt_BR, the Label will display the string “Demo de Busca” retrieved from the search_demo_pt_BR.properties file shown in Listing 5.

name=Nome
album=Álbum
artist=Artista
preview=Pré-visualização
searching=Buscando...
aborting=Abortando...
cancelled=Solicitação cancelada
resultCountFormat=Encontrados %1$d itens que correspondem a sua busca
appTitle=Demo de Busca
search=Busca
detail=Detalhe
titleOrArtist=Título ou Artista

Listing 5: search_demo_pt_BR.properties

Using the SearchDemoFXML Example as Applets and via Java Web Start

Using the instructions provided in Oracle’s Deploying JavaFX Applications guide, the SearchDemoFXML example has been deployed as applets and via Java Web Start on a Web page listed in the “See Also” section at the end of this article. A link to the Deploying JavaFX Applications guide is in the “See Also” section as well. Figure 7 contains a screen shot of that Web page, showing two instances of the application running side by side, each with a different FXML document loaded.

In addition, the Web page in Figure 7 has several Java Web Start links that invoke the SearchDemoFXML application with various locales. The applet and Java Web Start link deployments leverage the named parameters discussed previously in the HTML page for applets and in the JNLP files for Java Web Start.

Figure 7: SearchDemoFXML Running as Applets and Localized Java Web Start Links

Conclusion

The facility known as FXML, which comes with JavaFX 2.0, provides the ability to express a UI using XML. In addition to the concepts covered in Part One of this series, FXML provides the ability to radically change the UI without modifying the controller. This task can be accomplished by loading different FXML documents, leveraging JavaFX cascading style sheets, and creating localized resource bundles. Named parameters can be used with these features to provide relevant information to an application at startup.

See Also

About the Author

Jim Weaver is an independent Java and JavaFX developer, author, and speaker with a passion for helping rich-client Java and JavaFX become preferred technologies for new application development.

Books that Jim has authored include Inside Java, Beginning J2EE, and Pro JavaFX Platform, with the latter being updated to cover JavaFX 2.0.  His professional background includes 15 years as a Systems Architect at EDS and the same number of years as an independent developer and software development company owner. Jim is an international speaker at software technology conferences, including presenting at the JavaOne 2011 conferences in San Francisco and São Paulo and keynoting at the O’Reilly OSCON/Java 2011 conference in Portland, Oregon.

Jim blogs at http://javafxpert.com, tweets @javafxpert, and can be reached at jim.weaver[at]javafxpert.com.