Connecting SceneBuilder edited FXML to Java code

Recently I had to answer several questions regarding how to connect an UI built with the JavaFX SceneBuilder 1.0 Developer Preview to Java Code. So I figured out that a short overview might be helpful. But first, let me state the obvious.

What is FXML?

To make it short, FXML is an XML based declaration format for JavaFX. JavaFX provides an FXML loader which will parse FXML files and from that construct a graph of Java object. It may sound complex when stated like that but it is actually quite simple. Here is an example of FXML file, which instantiate a StackPane and puts a Button inside it:

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

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

<StackPane prefHeight="150.0" prefWidth="200.0" xmlns:fx="http://javafx.com/fxml">
  <children>
    <Button mnemonicParsing="false" text="Button" />
  </children>
</StackPane>

... and here is the code I would have had to write if I had chosen to do the same thing programatically:

  import javafx.scene.control.*;
  import javafx.scene.layout.*;

  ...

  final Button button = new Button("Button");
  button.setMnemonicParsing(false);

  final StackPane stackPane = new StackPane();
  stackPane.setPrefWidth(200.0);
  stackPane.setPrefHeight(150.0);
  stacPane.getChildren().add(button);

As you can see - FXML is rather simple to understand - as it is quite close to the JavaFX API. So OK FXML is simple, but why would I use it?

Well, there are several answers to that - but my own favorite is: because you can make it with SceneBuilder.

What is SceneBuilder?

In short SceneBuilder is a layout tool that will let you graphically build JavaFX user interfaces by dragging and dropping JavaFX components from a library, and save it as an FXML file. SceneBuilder can also be used to load and modify JavaFX scenegraphs declared in FXML. Here is how I made the small FXML file above:

  • Start the JavaFX SceneBuilder 1.0 Developer Preview
  • In the Library on the left hand side, click on 'StackPane' and drag it on the content view (the white rectangle)
  • In the Library, select a Button and drag it onto the StackPane on the content view.
  • In the Hierarchy Panel on the left hand side - select the StackPane component, then invoke 'Edit > Trim To Selected' from the menubar

That's it - you can now save, and you will obtain the small FXML file shown above. Of course this is only a trivial sample, made for the sake of the example - and SceneBuilder will let you create much more complex UIs.

So, I have now an FXML file. But what do I do with it? How do I include it in my program? How do I write my main class?

Loading an FXML file with JavaFX

Well, that's the easy part - because the piece of code you need to write never changes. You can download and look at the SceneBuilder samples if you need to get convinced, but here is the short version:

  • Create a Java class (let's call it 'Main.java') which extends javafx.application.Application
  • In the same directory copy/save the FXML file you just created using SceneBuilder. Let's name it "simple.fxml"

Now here is the Java code for the Main class, which simply loads the FXML file and puts it as root in a stage's scene.

/*
 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 */
package simple;

import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Application.launch(Main.class, (java.lang.String[])null);
    }

    @Override
    public void start(Stage primaryStage) {
        try {
            StackPane page = (StackPane) FXMLLoader.load(Main.class.getResource("simple.fxml"));
            Scene scene = new Scene(page);
            primaryStage.setScene(scene);
            primaryStage.setTitle("FXML is Simple");
            primaryStage.show();
        } catch (Exception ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Great! Now I only have to use my favorite IDE to compile the class and run it. But... wait... what does it do? Well nothing. It just displays a button in the middle of a window. There's no logic attached to it. So how do we do that? How can I connect this button to my application logic? Here is how:

Connection to code

First let's define our application logic. Since this post is only intended to give a very brief overview - let's keep things simple. Let's say that the only thing I want to do is print a message on System.out when the user clicks on my button. To do that, I'll need to register an action handler with my button. And to do that, I'll need to somehow get a handle on my button. I'll need some kind of controller logic that will get my button and add my action handler to it. So how do I get a handle to my button and pass it to my controller? Once again - this is easy: I just need to write a controller class for my FXML.

With each FXML file, it is possible to associate a controller class defined for that FXML. That controller class will make the link between the UI (the objects defined in the FXML) and the application logic. To each object defined in FXML we can associate an fx:id. The value of the id must be unique within the scope of the FXML, and is the name of an instance variable inside the controller class, in which the object will be injected. Since I want to have access to my button, I will need to add an fx:id to my button in FXML, and declare an @FXML variable in my controller class with the same name. In other words - I will need to add fx:id="myButton" to my button in FXML:

    <Button fx:id="myButton" mnemonicParsing="false" text="Button" />

and declare @FXML private Button myButton in my controller class

    @FXML
    private Button myButton; // value will be injected by the FXMLLoader

Let's see how to do this.

Add an fx:id to the Button object

  • Load "simple.fxml" in SceneBuilder - if not already done
  • In the hierarchy panel (bottom left), or directly on the content view, select the Button object.
  • Open the Properties sections of the inspector (right panel) for the button object
  • At the top of the section, you will see a text field labelled fx:id. Enter myButton in that field and validate.

Associate a controller class with the FXML file

Still in SceneBuilder, select the top root object (in our case, that's the StackPane), and open the Code section of the inspector (right hand side)

At the top of the section you should see a text field labelled Controller Class. In the field, type simple.SimpleController. This is the name of the class we're going to create manually.

If you save at this point, the FXML will look like this:

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

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

<StackPane prefHeight="150.0" prefWidth="200.0" xmlns:fx="http://javafx.com/fxml" fx:controller="simple.SimpleController">
  <children>
    <Button fx:id="myButton" mnemonicParsing="false" text="Button" />
  </children>
</StackPane>

As you can see, the name of the controller class has been added to the root object: fx:controller="simple.SimpleController"

Coding the controller class

In your favorite IDE, create an empty SimpleController.java class. Now what does a controller class looks like? What should we put inside? Well - SceneBuilder will help you there: it will show you an example of controller skeleton tailored for your FXML. In the menu bar, invoke View > Show Sample Controller Skeleton. A popup appears, displaying a suggestion for the controller skeleton: copy the code displayed there, and paste it into your SimpleController.java:

/**
 * Sample Skeleton for "simple.fxml" Controller Class
 * Use copy/paste to copy paste this code into your favorite IDE
 **/

package simple;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;


public class SimpleController
    implements Initializable {

    @FXML //  fx:id="myButton"
    private Button myButton; // Value injected by FXMLLoader


    @Override // This method is called by the FXMLLoader when initialization is complete
    public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
        assert myButton != null : "fx:id=\"myButton\" was not injected: check your FXML file 'simple.fxml'.";

        // initialize your logic here: all @FXML variables will have been injected

    }

}

Note that the code displayed by SceneBuilder is there only for educational purpose: SceneBuilder does not create and does not modify Java files. This is simply a hint of what you can use, given the fx:id present in your FXML file. You are free to copy all or part of the displayed code and paste it into your own Java class.

Now at this point, there only remains to add our logic to the controller class. Quite easy: in the initialize method, I will register an action handler with my button:

        ...

        // initialize your logic here: all @FXML variables will have been injected

        myButton.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("That was easy, wasn't it?");
            }
        });

        ...

That's it - if you now compile everything in your IDE, and run your application, clicking on the button should print a message on the console!

Summary

What happens is that in Main.java, the FXMLLoader will load simple.fxml from the jar/classpath, as specified by 'FXMLLoader.load(Main.class.getResource("simple.fxml"))'. When loading simple.fxml, the loader will find the name of the controller class, as specified by 'fx:controller="simple.SimpleController"' in the FXML. Upon finding the name of the controller class, the loader will create an instance of that class, in which it will try to inject all the objects that have an fx:id in the FXML.
Thus, after having created '<Button fx:id="myButton" ... />', the FXMLLoader will inject the button instance into the '@FXML private Button myButton;' instance variable found on the controller instance. This is because

  • The instance variable has an @FXML annotation,
  • The name of the variable exactly matches the value of the fx:id

Finally, when the whole FXML has been loaded, the FXMLLoader will call the controller's initialize method, and our code that registers an action handler with the button will be executed.

For a complete example, take a look at the HelloWorld SceneBuilder sample. Also make sure to follow the SceneBuilder Get Started guide, which will guide you through a much more complete example.

Of course, there are more elegant ways to set up an Event Handler using FXML and SceneBuilder. There are also many different ways to work with the FXMLLoader. But since it's starting to be very late here, I think it will have to wait for another post.

I hope you have enjoyed the tour!

    --daniel

Comments:

This sounds great!!!

Is JavaFX available for SPARC clients yet?

We have thin clients deployed with SPARC virtual desktops.

Posted by Dave on June 27, 2012 at 02:00 AM CEST #

Nice tutorial Daniel, thanks for posting it.

@Dave, see the following thread: https://forums.oracle.com/forums/thread.jspa?threadID=2344624 "JavaFX 2.0 on Solaris"

Posted by jewelsea on July 03, 2012 at 02:35 AM CEST #

Hello
how do add Java Code into FXML codes?

Posted by Ilkin on July 05, 2012 at 11:53 PM CEST #

@Ilkin - You don't write Java code inside FXML. You don't need to. What you do is write Java code in a Java class (the controller class), and then link the FXML with the controller class (and thus the java code) - as shown above.

Hope this helps

Posted by daniel on July 06, 2012 at 09:11 AM CEST #

Good night Daniel, I built an example set exactly equal to his, but with separate directories in the MVC pattern and get the following return:

Grave: null
java.lang.NullPointerException: Location is required.
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2737)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2721)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2694)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2683)
at appotica_v2.AppOtica_v2.gotoTeste(AppOtica_v2.java:141)
at appotica_v2.AppOtica_v2.start(AppOtica_v2.java:48)
at com.sun.javafx.application.LauncherImpl$5.run(LauncherImpl.java:319)
at com.sun.javafx.application.PlatformImpl$5.run(PlatformImpl.java:206)
at com.sun.javafx.application.PlatformImpl$4.run(PlatformImpl.java:173)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:76)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication$3$1.run(GtkApplication.java:82)
at java.lang.Thread.run(Thread.java:722)

When the class with main method start is in some folder that is always the return they get.
My project name: AppOtica_v2
My directories: appotica_v2 with de Class AppOtica_v2 //Main Class
view with the fxml arquives
controller with de controller class
I try FXML load for many ways (FXMLLoader.load(AppOtica_v2.class.getResource("simple.fxml")); or FXMLLoader.load(appotica_v2.AppOtica_v2.class.getResource("simple.fxml"));)but the error continues to occur.
If I write the code in javafx without fxml the problem disappears.
Can you help me please?
Thank you

Posted by Junior Tada on August 07, 2012 at 03:53 AM CEST #

Hi,

@Junior

This exception is well known: Class.getResource(...) will return null if it does not find the resource - and FXMLLoader will say "Location not set" if load() is called but no URL was provided.

What it means is probably that your file is either not named "simple.fxml" (e.g. you made a typo somewhere) - or it has not been copied to your class folder (or included in your jar).

When you do appotica_v2.AppOtica_v2.class.getResource("simple.fxml") it means that you have put simple.fxml in the same folder than appotica_v2/AppOtica_v2.java - (i.e. there should be appotica_v2/simple.fxml) and that your IDE/compile/jar command has copied simple.fxml next to appotica_v2/AppOtica_v2.class (if you use NetBeans that's the default behavior).

Make sure that "simple.fxml" is copied next to AppOtica_v2.class and included in your jar and all will be well.

Hope this helps,

-- daniel

Posted by guest on August 07, 2012 at 08:12 AM CEST #

Hi,

Daniel

I writer wrong in my example. The real location for fxml arquive is: view/sample.fxml, but it is not a problem. My problem occur when Main Class not is in default package or i try execute (FXMLLoader.load(AppOtica_v2.class.getResource("view/simple.fxml")) in other class diferent the Main class.
My question is: the method FXMLLoader.load(MainClass.class.getResource("paste/arquive.fxml" or only arquive if is in default package with Mainclass in some paste)) only runs on the Main class? If not, which the correct path to method MainClass.class.getResource() or this method only write with MainClass.class.getResource()? The JavaFX accept many MainClass in same project? How call fxml arquive in other simple java class when not Mainclass?

Thank you again.

Ps: Sorry, i see in my e-mail, i submit 3 times my comment, but the Oracle site not return if my comment is submit sucess.

Junior Tada

Posted by guest on August 07, 2012 at 04:53 PM CEST #

Hi,

Daniel

Thank you for support. I writing wrong in example, the correct path is "view/sample.fxml". I got making a example running Main class in one different package, but the fxml archives had to be together in the same package how you said. If Main class is in the default package the fxml archives can be changed for any package. Thank you for words.
You know tell me if is a JavaFx specification or is a bug for my netbeans or my JavaFx version or i'm making anything wrong?
Thank you again and forgive for my bad english, i'm from Brazil.

Junior Tada

Posted by Junior Tada on August 09, 2012 at 05:14 AM CEST #

Nice article to kickstart. I selected the project type as Javafx application and added the controller manually after I porting the fxml from scene builder. Works fine. Can I see scene builder insidenetbeans like swing design mode ?

Posted by Neil on August 09, 2012 at 10:44 PM CEST #

Also I need to know at which point the controller in ininstantiated and initialize() method is called. I need to pass some config values from the
main java class's start method to the controller. and use at the end of initialization.

Posted by Neil on September 05, 2012 at 08:27 AM CEST #

@Junior

Hi Junior - when you write "MyClass.class.getResource("paste/simple.fxml")" it means that simple.fxml is located in a sub directory "paste" compared to where MyClass is located. It means that on disk you have:

<somedir>/MyClass.java
<somedir>/paste/simple.fxml

and in your jar you have a similar layout.

I recommend not using the default package for any of your java classes.

You can call getResource("somepath") on any class - the class package usually indicates the root of the path, and "somepath" is a path/name relative to that package location (unless it starts with "/").

See the Java Documentation for more details:
http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getResource%28java.lang.String%29

-- daniel

Posted by daniel on September 05, 2012 at 08:28 AM CEST #

@Neil

You can start SceneBuilder from NetBeans - that's what should happen when you double click on an FXML file in NetBeans, provided that you have SceneBuilder installed.

The controller's initialize method is called by the FXMLLoader when everything has been loaded - at the end of the FXMLLoader.load() method. This means that it is called before your node are added to the Scene (assuming that's what you do after FXMLLoader.load()).

-- daniel

Posted by daniel on September 05, 2012 at 08:37 AM CEST #

@daniel

Thanks, yes I use scene builder that way, works fine but every times it creates a backup file. May be in latter version we can introduce a "design" tab in fxml source file editor like flex's mxml. Just a feature suggestion :)

Posted by Neil on September 05, 2012 at 09:49 AM CEST #

Hi Daniel,

We have implemented the Acclemetor example in JavaFX with FXML and normal JavaFX Java Controls. We observed that FXML is taking too much time to change the screens. We have 10 Screens, all the screens we are intializing at once. but the coding part is working smooth and very fast where as fxml is taking time to shuffle the screens. is FXML will try to parse again and again while we are shuffling the screens? How is differing with the traditional coding ?

Posted by guest on September 07, 2012 at 07:58 AM CEST #

Hello Daniel. Good article.
I have a nullpointerexception with "loginBtn" already injected by FXML loader.

loginBtn.setOnAction(new EventHandler<ActionEvent>() {

@Override
public void handle(ActionEvent event) {
System.out.println("That was easy, wasn't it?");
}
});

Could you please help me.

Thanks!
Pedro.

Posted by Pedro on October 01, 2012 at 09:50 PM CEST #

@guest FXML is using reflection to create objects - while normal coding simply calls the constructors & setters. Although it's true that reflection is less efficient than direct API calls, my experience is that the price to pay is usually small compared to the actual time taken for initializing the scene graph itself (applying CSS - caspian + custom style sheet etc...).

@Pedro
It's likely that your variable loginBtn is null, which will happen if any of the statements below is not true.
Your FXML must contain a button with fx:id="loginBtn"
Your controller must have a variable named loginBtn, and it must be tagged with the @FXML annotation.

Finally the value of the variable loginBtn may not be available before initialize(...) is called. Where did you place your code to loginBtn.setOnAction?

-- daniel

Posted by daniel on October 02, 2012 at 12:27 PM CEST #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Daniel Fuchs blogs on Scene Builder, JMX, SNMP, Java, etc...

The views expressed on this blog are those of the author and do not necessarily reflect the views of Oracle.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today