Blog

Convert FXML to Java as part of the build

FXML is a declarative way to define you JavaFX UIs, you can edit it by hand e.g. useing e(fx)clipse, use a DSL like FXGraph or use a WYSIWYG-Tool like SceneBuilder. At runtime a class named FXMLLoader takes the FXML and creates a SceneGraph out of it using reflection. Let's look at a small sample. FXML:

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

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

<BorderPane xmlns:fx="http://javafx.com/fxml">
  <center>
    <Button text="Hello World"></Button>
  </center>
</BorderPane>

Loading in Java:

BorderPane p = FXMLLoader.load(getClass().getClassLoader().getResource("Sample.fxml"));

Simple & easy BUT it is slow. On desktop system the slowness might not bother you but on constrainted devices like Raspberry Pi or smartphone reflection it will. So the idea I had already since some time [Bug] is to move translation from runtime to build time so that one can simply add an instruction to the build script and FXML-Files are translated to Java-Files and compiled by the Java compiler. While not yet complete I can now share the first version which does at least supports the most basic stuff. To build my JavaFX app which uses FXML I can now use an ant-file like this:

<project default="test">
  <path id="fxcompile">
    <filelist>
      <file name="build/classes"/>
      <file name="build-libs/org.eclipse.fx.fxml.compiler_0.9.0-SNAPSHOT.jar"/>
    </filelist>
  </path>
	
  <path id="buildpath">
    <filelist>
      <file name="build/classes"/>
      <file name="libs/org.eclipse.fx.core_0.9.0.201308281625.jar"/>
    </filelist>
  </path>
	
 <target name="test">
   <delete dir="build" />
   <mkdir dir="build/classes"/>
		
   <taskdef name="fxml-compiler" classpathref="fxcompile" classname="org.eclipse.fx.ide.fxml.compiler.ant.FXMLCompilerTask" />
		
   <javac srcdir="src" destdir="build/classes" classpathref="buildpath"></javac>
		
   <fxml-compiler sourcedir="src" destdir="build/gen-src"/>
   <javac srcdir="build/gen-src" destdir="build/classes" classpathref="buildpath"></javac>
  </target>
</project>

The important line is <fxml-compiler... which instructs the build to translate all FXML-Files found below src into .java-Files and place them into build/gen-src. Generated Java-File:

import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;
import javafx.scene.control.Button;

import java.util.Map;
import java.net.URL;
import java.util.ResourceBundle;

import org.eclipse.fx.core.fxml.FXMLDocument;

import java.util.HashMap;

@SuppressWarnings("all")
public class Sample extends FXMLDocument<BorderPane> {
  private Map<String,Object> namespaceMap = new HashMap<>();
  public Object getController() {
    return null;
  }
	
  public BorderPane load(URL location, ResourceBundle resourceBundle) {
    BorderPane root = new BorderPane();
    {
      Button e_1 = new Button();
      e_1.setText("Hello World");
      root.setCenter(e_1);
    }
    return root;
  }
}

In the original code the only change I have to make is switch from FXMLLoader to a smarter class which first checks if there is a .class-File with the same name and create an instance of it.

ExtendedFXMLLoader loader = new ExtendedFXMLLoader();
loader.load(getClass().getClassLoader(),"Sample.fxml");

You can play with it yourself. I've uploaded a sample project to http://downloads.efxclipse.org/fxml-compiler.zip, give it a try on your own FXML-Files and if things fail file bugs against e(fx)clipse.

published on
Previous
JavaFX on EclipseCon Europe