jLuger.de - Notes about JavaFX with Java8

Recently I've made a small JavaFX application based on Java 8 as a tech demo. Here are some notes that I've found out useful to remember.

Pane
The Pane container does not resize its children to fill the available space. Use one of its sublcasses.

Scene Builder
Scene Builder is a visual layout tool to edit fxml files. It was created by Oracle and is open source but since Java 8u40 there will be no more binary build downloads. You have to either build it yourself or download a fork by Gluon. It is currently unclear if there is ongoing development for Oracles Scene Builder.

Controller
A lot of youtube videos show that you can declare the controller class in the fxml files. While this allows to bind methods to actions in Scene Builder I don't use it. It is pretty difficult to get dependencies into the controller with this approach.
That is why I create the controller in the java code and set it during loading of the fxml file.
		SampleController controller = new SampleController(resource);
FXMLLoader loader = new FXMLLoader(getClass().getResource("screen.fxml"));
loader.setController(controller);
Node screenNode;
try {
screenNode = loader.load();
} catch(IOException e) {
throw new RuntimeException(e);
}


DisableProperty vs. DisabledProperty

The Node class (base class for JavaFX GUI elements) has a disable and a disabled property. The last one is read only. This means you can't bind it to a property. That isn't a problem as long as you know that there is a disable property that you can write and thus bind. Just keep it in mind to use the right one and be cautious for other such pairs.

Textfields
Until JavaFX 8 u40 the only solution to limit text input in textfields (e.g. NumberFields) was to extend the TextField class. Since that release you can set TextFormatter. A lot of popular answers on google currently don't include this new fact.

Background Jobs
As Swing JavaFX has a GUI thread where all the GUI operations have to be done but where you shouldn't do long running background tasks.
Oracle has provided some documentation on how to run background jobs here but I was wondering what it would take to create a simple replacement for SwingWorker. It turned out that a first version could written easily with Java 8.

Here is the code for the Worker:
import java.util.function.Consumer;
import java.util.function.Supplier;

import javafx.application.Platform;

/**
* Provides a simple reimplmentation of SwingWorker for JavaFx under Java 8.
*/
public class BackgroundWorker {
/**
* Executes a background job in a separate thread that returns a result that
* should be processed in the JavaFX Application thread.
*
* @param background
* The job that should be executed in background thread. Must not
* be <code>null</code>.
* @param success
* A {@link Consumer} that processes the result of the background
* job and is executed in the JavaFX Application thread. May be
* <code>null</code>.
* @param fail
* Is called when the background jobs throws an exception. The
* parameter is the thrown exception. May be <code>null</code>.
* @param always
* A job that should be executed always after the background
* thread no matter whether the background job succeeded or
* failed. Isn't executed when the previous success or fail job
* throws an exception. May be <code>null</code>.
*/
public static <T> void executeBackgroundJob(Supplier<T> background, Consumer<T> success, Consumer<Exception> fail,
final Runnable always) {
if (background == null) {
throw new NullPointerException("background must not be null.");
}
new Thread(() -> {
T result = null;
try {
result = background.get();
} catch (final Exception e) {
handleFail(fail, always, e);
}
handleSuccess(success, result, always);
}).start();
}

/**
* Executes a background job in a separate thread that returns no result
* that should be processed in the JavaFX Application thread.
*
* @param background
* The job that should be executed in background thread. Must not
* be <code>null</code>.
* @param success
* A {@link Runnable} that is executed after the background job
* has finished.It is executed in the JavaFX Application thread.
* May be <code>null</code>.
* @param fail
* Is called when the background jobs throws an exception. The
* parameter is the thrown exception. May be <code>null</code>.
* @param always
* A job that should be executed always after the background
* thread no matter whether the background job succeeded or
* failed. Isn't executed when the previous success or fail job
* throws an exception. May be <code>null</code>.
*/
public static void executeBackgroundJob(Runnable background, Runnable success, Consumer<Exception> fail,
final Runnable always) {
if (background == null) {
throw new NullPointerException("background must not be null.");
}
new Thread(() -> {
try {
background.run();
} catch (final Exception e) {
handleFail(fail, always, e);
}
handleSuccess(success, always);
}).start();
}

/**
* Handle successful execution of a background job that returned an object
* to be processed in the JavaFX Application Thread.
*
* @param success
* A {@link Consumer} that processes the result of the background
* job and is executed in the JavaFX Application thread. May be
* <code>null</code>.
* @param result
* The object generated as a result by the background thread.
* @param always
* A job that should be executed always after the background
* thread no matter whether the background job succeeded or
* failed. Isn't executed when the previous success or fail job
* throws an exception. May be <code>null</code>.
*/
private static <T> void handleSuccess(Consumer<T> success, T result, final Runnable always) {
if (success != null || always != null) {
Platform.runLater(() -> {
if (success != null) {
success.accept(result);
}
if (always != null) {
always.run();
}
});
}
}

/**
* Handle successful execution of a background job that returns nothing.
*
* @param success
* A {@link Runnable} that is executed after the background job
* has finished.It is executed in the JavaFX Application thread.
* May be <code>null</code>.
* @param always
* A job that should be executed always after the background
* thread no matter whether the background job succeeded or
* failed. Isn't executed when the previous success or fail job
* throws an exception. May be <code>null</code>.
*/
private static void handleSuccess(Runnable success, final Runnable always) {
if (success != null || always != null) {
Platform.runLater(() -> {
if (success != null) {
success.run();
}
if (always != null) {
always.run();
}
});
}
}

/**
* Called when a background job has thrown an exception.
*
* @param fail
* Is called when the background jobs throws an exception. The
* parameter is the thrown exception. May be <code>null</code>.
* @param always
* A job that should be executed always after the background
* thread no matter whether the background job succeeded or
* failed. Isn't executed when the previous success or fail job
* throws an exception. May be <code>null</code>.
* @param e
* The exception that was thrown.
*/
private static void handleFail(Consumer<Exception> fail, final Runnable always, final Exception e) {
if (fail != null || always != null) {
Platform.runLater(() -> {
if (fail != null) {
fail.accept(e);
}
if (always != null) {
always.run();
}
});
}
}
}
The class would be used like this:
		rootPane.setDisable(true);
final Expense expense = workScreenViewModel.getExpense();
BackgroundWorker.executeBackgroundJob(() -> {
expensesRestClient.saveExpense(expense);
}, () -> {
workScreenViewModel.getObservableExpenseList().add(expense);
},(Exception ex) -> {
logger.log(Level.SEVERE, "Failed to save Expense "+expense, ex);
}, () -> {
rootPane.setDisable(false);
});

and
		rootPane.setDisable(true);
BackgroundWorker.<List<Expense>>executeBackgroundJob(() -> {
final List<Expense> expenses = expensesRestClient.getExpenses(month, year);
return expenses;
}, (expenses) -> {
workScreenViewModel.getObservableExpenseList().setAll(expenses);
}, (Exception ex) -> {
logger.log(Level.SEVERE, "Failed to get Expenses", ex);
}, () -> {
rootPane.setDisable(false);
});