Monday, January 16, 2012

Focus on JavaFX 2 FXML with NetBeans 7.1

In October 2011, I used the post Hello JavaFX 2.0: Introduction by NetBeans 7.1 beta to look at using NetBeans 7.1 beta to build a simple Hello, World style of JavaFX 2.0 application. In this post, I look at using NetBeans 7.1 (no longer in beta) to build a slightly more sophisticated JavaFX 2 application that makes heavy use of FXML.

Most of my posts on JavaFX 2 up to this point have emphasized the "pure Java" nature of JavaFX 2. The examples in these posts have been written in standard Java using JavaFX 2 APIs directly from the Java source code. These examples have also been built with the javac compiler and executed with the java application launcher. In this post, I take one of those examples [(Pure Java) JavaFX 2.0 Menus] and "port" it to use FXML for presentation layout.

For convenience, I include the entire Java listing for the "pure Java" implementation of the JavaFX 2 menus demonstration.

JavaFX 2.0 Menus Demonstration (Pure Java)
package dustin.examples;

import static java.lang.System.out;

import javafx.application.Application;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * Example of creating menus in JavaFX.
 * 
 * @author Dustin
 */
public class JavaFxMenus extends Application
{
   /**
    * Build menu bar with included menus for this demonstration.
    * 
    * @param menuWidthProperty Width to be bound to menu bar width.
    * @return Menu Bar with menus included.
    */
   private MenuBar buildMenuBarWithMenus(final ReadOnlyDoubleProperty menuWidthProperty)
   {
      final MenuBar menuBar = new MenuBar();

      // Prepare left-most 'File' drop-down menu
      final Menu fileMenu = new Menu("File");
      fileMenu.getItems().add(new MenuItem("New"));
      fileMenu.getItems().add(new MenuItem("Open"));
      fileMenu.getItems().add(new MenuItem("Save"));
      fileMenu.getItems().add(new MenuItem("Save As"));
      fileMenu.getItems().add(new SeparatorMenuItem());
      fileMenu.getItems().add(new MenuItem("Exit"));
      menuBar.getMenus().add(fileMenu);

      // Prepare 'Examples' drop-down menu
      final Menu examplesMenu = new Menu("JavaFX 2.0 Examples");
      examplesMenu.getItems().add(new MenuItem("Text Example"));
      examplesMenu.getItems().add(new MenuItem("Objects Example"));
      examplesMenu.getItems().add(new MenuItem("Animation Example"));
      menuBar.getMenus().add(examplesMenu);

      // Prepare 'Help' drop-down menu
      final Menu helpMenu = new Menu("Help");
      final MenuItem searchMenuItem = new MenuItem("Search");
      searchMenuItem.setDisable(true);
      helpMenu.getItems().add(searchMenuItem);
      final MenuItem onlineManualMenuItem = new MenuItem("Online Manual");
      onlineManualMenuItem.setVisible(false);
      helpMenu.getItems().add(onlineManualMenuItem);
      helpMenu.getItems().add(new SeparatorMenuItem());
      final MenuItem aboutMenuItem =
         MenuItemBuilder.create()
                        .text("About")
                        .onAction(
                            new EventHandler<ActionEvent>()
                            {
                               @Override public void handle(ActionEvent e)
                               {
                                  out.println("You clicked on About!");
                               }
                            })
                        .accelerator(
                            new KeyCodeCombination(
                               KeyCode.A, KeyCombination.CONTROL_DOWN))
                        .build();             
      helpMenu.getItems().add(aboutMenuItem);
      menuBar.getMenus().add(helpMenu);

      // bind width of menu bar to width of associated stage
      menuBar.prefWidthProperty().bind(menuWidthProperty);

      return menuBar;
   }

   /**
    * Start of JavaFX application demonstrating menu support.
    * 
    * @param stage Primary stage.
    */
   @Override
   public void start(final Stage stage)
   {
      stage.setTitle("Creating Menus with JavaFX 2.0");
      final Group rootGroup = new Group();
      final Scene scene = new Scene(rootGroup, 800, 400, Color.WHEAT);
      final MenuBar menuBar = buildMenuBarWithMenus(stage.widthProperty());
      rootGroup.getChildren().add(menuBar);
      stage.setScene(scene);
      stage.show();
   }

   /**
    * Main executable function for running examples.
    * 
    * @param arguments Command-line arguments: none expected.
    */
   public static void main(final String[] arguments)
   {
      Application.launch(arguments);
   }
}

Besides adding the ability to write JavaFX applications in "pure Java" as implemented in the last code listing, JavaFX 2 also provides FXML, an XML grammar for specifying a JavaFX application's layout. This approach is very similar to that provided by Flex's MXML, by OpenLaszlo's LZX, and by Mozilla's XUL. This approach works well with JavaFX's hierarchical Scene Graph.

The ported FXML code is shown next. It is easy to see the presentation structure in this XML.

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

<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox id="vbox" prefHeight="400" prefWidth="800"
      xmlns:fx="http://javafx.com/fxml"
      fx:controller="dustin.examples.MenuController">
   <MenuBar fx:id="menuBar" onKeyPressed="#handleKeyInput">
      <menus>
         <Menu text="File">
            <items>
               <MenuItem text="New"/>
               <MenuItem text="Open"/>
               <MenuItem text="Save"/>
               <MenuItem text="Save As"/>
               <SeparatorMenuItem  />
               <MenuItem text="Exit"/>
            </items>
         </Menu>
         <Menu text="JavaFX 2.0 Examples">
            <items>
               <MenuItem text="Text Example"/>
               <MenuItem text="Objects Example"/>
               <MenuItem text="Animation Example"/>
            </items>
         </Menu>
         <Menu text="Help">
            <items>
               <MenuItem text="Search" disable="true"/>
               <MenuItem text="Online Manual" visible="false"/>
               <SeparatorMenuItem />
               <MenuItem text="About" onAction="#handleAboutAction"/>
            </items>
         </Menu>
      </menus>
   </MenuBar>
</VBox>

The important portion of this simple FXML can be viewed on a single page within NetBeans 7.1 as shown in the next screen snapshot.

Besides the conciseness of the FXML and the aesthetically pleasing hierarchical presentation, the FXML also should look familiar to anyone who has worked with Flex, OpenLaszlo, or XUL. Most of the presentation structure is easily identified in the FXML document. This example includes an onAction handler that responds to the "About" menu item and there is an onKeyPressed action associated with the menu bar as well (I could not figure out a way to easily associate this handler with the menu item itself). Both of these actions reference names that start with the # symbol. The names following the # are names of methods on the controller class (defined by the fx:controller attribute of the VBox as the class named dustin.examples.MenuController). The source for this controller class is shown next.

MenuController.java
package dustin.examples;

import static java.lang.System.out;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.MenuBar;
import javafx.scene.input.InputEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

/**
 * Controller class for JavaFX 2 Menus with FXML post and demonstration.
 * 
 * @author Dustin
 */
public class MenuController implements Initializable
{
   @FXML
   private MenuBar menuBar;

   /**
    * Handle action related to "About" menu item.
    * 
    * @param event Event on "About" menu item.
    */
   @FXML
   private void handleAboutAction(final ActionEvent event)
   {
      provideAboutFunctionality();
   }

   /**
    * Handle action related to input (in this case specifically only responds to
    * keyboard event CTRL-A).
    * 
    * @param event Input event.
    */
   @FXML
   private void handleKeyInput(final InputEvent event)
   {
      if (event instanceof KeyEvent)
      {
         final KeyEvent keyEvent = (KeyEvent) event;
         if (keyEvent.isControlDown() && keyEvent.getCode() == KeyCode.A)
         {
            provideAboutFunctionality();
         }
      }
   }

   /**
    * Perform functionality associated with "About" menu selection or CTRL-A.
    */
   private void provideAboutFunctionality()
   {
      out.println("You clicked on About!");      
   }

   @Override
   public void initialize(final URL url, final ResourceBundle rb)
   {
      menuBar.setFocusTraversable(true);
   }   
}

The above code from the controller class includes the two methods referenced as action handlers in the FXML with the # prefixes. These two methods are private and normally would not be visible to the FXML loader, but the use of the annotation @FXML gets around this problem. The menuBar attribute is also private but visible to the FXML loader thanks to its own @FXML annotation.

To get the application to respond to CTRL-A before any initial access of the menu bar, I found that I needed to have a reference to the menu bar in the controller and call setFocusTraversable(true) on that instance. Without doing that, the CTRL-A would only be responded to if I had first clicked on the menu bar once. The hook from the FXML to this attribute in the controller is the fx:id attribute used in the FXML. That attribute references the name "menuBar", which is (not coincidentally) also the name of the attribute in the controller class. In other words, the attribute fx:id="menuBar" ties the XML element that attribute is on in the FXML to the attribute with the same name in the controller.

I did not show much in this post specific to NetBeans 7.1, but it is worth noting that I had all the pertinent files created in a new project using NetBeans 7.1's New Project wizard and selecting a JavaFX FXML Application as the project type. This created a basic FXML file, a basic controller file, and a main class for loading the FXML and running the application. I modified these to the examples above for the FXML and for the controller class. The modified class that loads the FXML and starts the JavaFX application is shown next. (There's not a lot to it!)

JavaFxMenusWithFxmlDemo.java
package dustin.examples;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

/**
 * Simple example of JavaFX 2 FXML used to demonstrate JavaFX 2 menus. This is a
 * "port" of the example featured using "pure Java" (no FXML) in the post
 * "(Pure Java) JavaFX 2.0 Menus"
 * (http://marxsoftware.blogspot.com/2011/12/pure-java-javafx-20-menus.html).
 * 
 * @author Dustin
 */
public class JavaFxMenusWithFxmlDemo extends Application
{
   /**
    * Main function for running this application.
    * 
    * @param arguments Command-line arguments: none expected.
    */
   public static void main(String[] arguments)
   {
      Application.launch(JavaFxMenusWithFxmlDemo.class, arguments);
   }

   /**
    * Overridden Application.start(Stage) method.
    * 
    * @param stage Primary stage.
    * @throws Exception JavaFX application exception.
    */
   @Override
   public void start(final Stage stage) throws Exception
   {
      final Parent fxmlRoot = FXMLLoader.load(getClass().getResource("JavaFx2Menus.fxml"));
      stage.setScene(new Scene(fxmlRoot));
      stage.show();
   }
}

I won't show the output here because it's essentially what the "pure Java" version produced, which is shown in my post on that approach. Besides generating the files that I adapted for my application, NetBeans 7.1's new project wizard for a JavaFX FXML application also created a project that automatically packages up what I need for my JavaFX application in an executable JAR. The contents of the JAR in this case are shown in the next screen snapshot.

The trickiest part about using FXML is the relatively less available documentation. The pure Java APIs have the strong Javadoc API documentation, but I know of no such thing for FXML (including no documentation or schema for the namespace http://javafx.com/fxml). There are some great resources of FXML such as Getting Started with FXML and Introduction to FXML. Perhaps the best document for figuring out what elements and attributes FXML provides is Introducing FXML: A Markup Language for JavaFX, which explains the "mapping" of JavaFX standard Java API classes to JavaFX FXML elements and attributes.

FXML appears to be a fairly attractive approach for building a JavaFX application because it nicely separates presentation (FXML) from business logic (controller class and classes that would be called by controller methods). This can make it easier to visually see the GUI layout in the hierarchical XML and keep the logic and presentation from cluttering each other.

11 comments:

Peter Hull said...

You can set properties in the element itself, ie. if you have <MenuBar fx:id="menuBar" focusTraversable="true" onKeyPressed="#handleKeyInput" >
you shouldn't need to call setFocusTraversable in the controller.

Tom said...

What schema would you expect. There are only 3 things in the fx-namespace. For the rest of the FXML-File there can't be a schema.

Dustin said...

Peter,

Thanks for the idea. I'll try that on my next one.

Dustin

Dustin said...

Tom,

Although you make a good point that a schema for FXML such as it is would not be very helpful with a 3 element namespace, I think the real issue is why not give FXML a more complete namespace definition so that any IDE and many editors could be used to achieve automatic element and attribute completion "for free" using their already available XML support?

If you're using an XML grammar anyway, why not make it easy to work with in the widest range of IDEs and editors? FXML: Why It Rocks, And The Next Phase addresses this question, but I'm not convinced the benefit of flexibility is worth the cost in this case.

Addressing Bug 204741 brings this completion capability to NetBeans, but a reasonable namespace combined with an XSD would automatically bring this to many more IDEs and editors (as well as older versions of NetBeans). Without the schema, the expectation is that "IDEs will add specific FXML editor support."

Dustin said...

A comment on the JavaLobby version of this post reminds me that it's important to point out that the FXML example above works on JavaFX 2.1, but needs an explicit "children" element within the VBox element in JavaFX 2.0. I have posted additional details in the post JavaFX 2.1: JavaFX 2's @DefaultProperty Annotation with FXML.

Dustin

Ouro said...

Thank you very much! i didnt guess why my project didnt work, then when i was reading this examble i saw fx:controller on your fxml but on mine i didnt write it!
Thank you again!

Dustin said...

Ouro,

I'm glad that it was helpful. Thanks for letting me know it was helpful.

Dustin

bApTizE said...

I have been searching for such example and found this one, thank you :-). I'm excited about JavaFX.

bApTizE said...

Dustin, do i need netbeans for this?

Dustin said...

bApTizE,

You don't need NetBeans to build the examples shown in this post, though it does make it easier. You can create all the files directly as shown here and then run them without NetBeans.

Dustin

cbrueggenolte said...

Hello.

Do you know how I can access the Stage Object in my MainClass from my Controller Class? I need that for the DirectoryChooser Component.

Thanks