Put in the class path:
- http://mvnrepository.com/artifact/javax.enterprise/cdi-api
- http://mvnrepository.com/artifact/org.jboss.weld/weld-core/
- http://mvnrepository.com/artifact/org.jboss.weld.se/weld-se/
The challenge is to bring FXMLLoader and CDI together, because JavaFX is creating naked objects by itself, now they have to be „CDI-aware“.
package tutego.fx;
import java.nio.charset.*;
import javafx.fxml.FXMLLoader;
import javafx.util.Callback;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
public class FXMLLoaderProducer
{
@Inject
Instance<Object> instance;
@Produces
public FXMLLoader createLoader()
{
return new FXMLLoader( null, null, null, new Callback<Class<?>, Object>() {
@Override public Object call( Class<?> param ) {
return instance.select( param ).get();
}
}, StandardCharsets.UTF_8 );
}
}
That was the hardest part.
The first regular class has the unique main(String[]) method and it’s a JavaFX application. It starts Weld, the CDI container.
package tutego.fx;
import java.io.IOException;
import javafx.application.Application;
import javafx.stage.Stage;
import org.jboss.weld.environment.se.*;
public class Main extends Application
{
private Weld weld;
public static void main( String[] args )
{
Application.launch( args );
}
@Override
public void init()
{
weld = new Weld();
}
@Override
public void start( Stage stage ) throws IOException
{
weld.initialize().instance().select( FxMain.class ).get().start( stage, getParameters() );
}
@Override
public void stop()
{
weld.shutdown();
}
}
Weld delegates to the FxMain class, the first CDI-enabled class:
package tutego.fx;
import java.io.*;
import javafx.application.Application.Parameters;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.stage.Stage;
import javax.inject.Inject;
public class FxMain
{
@Inject
private FXMLLoader fxmlLoader;
public void start( Stage stage, Parameters parameters ) throws IOException
{
try ( InputStream fxml = RandomController.class.getResourceAsStream( "/random.fxml" ) ) {
Parent root = (Parent) fxmlLoader.load( fxml );
stage.setScene( new Scene( root ) );
stage.show();
}
}
}
The injected FXMLLoader now has to load the FXML-file random.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="tutego.fx.RandomController">
<children>
<Button onAction="#onButtonClick" text="Next random" />
<Label id="text" fx:id="label" />
</children>
</VBox>
In the FXML-file there is a reference to the FX Controller. JavaFX has to load it and can make the injections with the help of our very first class. A regular service is getting injected into the controller:
package tutego.fx;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javax.inject.Inject;
public class RandomController
{
@FXML
private Label label;
@Inject
private RandomService randomService;
@FXML
public void onButtonClick()
{
label.setText( "Random " + randomService.nextInt() );
}
}
The service itself is a simple singleton:
package tutego.fx;
import java.util.Random;
import javax.inject.Singleton;
@Singleton
public class RandomService
{
private Random rnd = new Random();
public int nextInt()
{
return rnd.nextInt();
}
}
Thats it!
PS: When you start, dont forget to put a (even blank) beans.xml in META-INF.
Thanks for the article, it helps a lot!
I stumbled over an issue with the shown code if I try to load an fxml file which references stylesheets via
…
…
In this case I got an NoSuchMethodException for java.net.URL.() .
This can be solved by handing over an instance of JavaFXBuilderFactory when creating the FXMLLoader in FXMLLoaderProducer.java :
import java.nio.charset.StandardCharsets;
import javafx.fxml.FXMLLoader;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.util.Callback;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
public class FXMLLoaderProducer {
@Inject
Instance instance;
@Produces
public FXMLLoader createLoader() {
return new FXMLLoader(null, null, new JavaFXBuilderFactory(),
new Callback<Class, Object>() {
@Override
public Object call(Class param) {
return instance.select(param).get();
}
}, StandardCharsets.UTF_8);
}
}
Nice article. Helped me a lot to get a small app that I am working on bootstrapped.
Just one point for anyone that may hit the same issue as myself.
I wanted to load the FXML files via a URL. Unfortunately the load(URL) on FXMLLoader is a static (which internally creates a new FXMLLoader anyways which defeats the purpose). So to avoid having to have InputStreams in my code, you can use two isntance methods, setLocation(URL url) and load():
@Inject
private FXMLLoader fxmlLoader;
@Inject
private ResourceAccessor resourceAccessor;
public void start(Stage primaryStage)
throws IOException {
fxmlLoader.setLocation(resourceAccessor
.getResource(ResourceAccessor.FXML_LANDING_PAGE));
Parent root = (Parent) fxmlLoader.load();
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(
resourceAccessor.getResource(ResourceAccessor.CSS_APP)
.toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}