As explained in JavaFX Interoperability with SWT it is possible to embed JavaFX controls in a SWT UI. This is useful for example if you want to softly migrate big applications from SWT to JavaFX or if you need to add animations or special JavaFX controls without completely migrating your application.
The following recipe will show how to integrate JavaFX with an Eclipse 4 application. It will cover the usage of Java 17 and JavaFX 17.
Note:
If you are interested in the usage of Java 8 with integrated JavaFX, have a look at the older versions of this tutorial, e.g. the one published at vogella Blog.
- JDK >= 17
- e.g. Eclipse Temurin
- Simply run the executable and follow the installation instructions
- Eclipse IDE >= 2024-03
- Eclipse IDE Downloads
- Choose the package that fits your needs the best, e.g. Eclipse for RCP and RAP Developers
- After starting the IDE and choosing a workspace, update the IDE to ensure the latest service release is installed. This is necessary to get the latest bugfixes and security patches.
- Main Menu → Help → Check for Updates
- e(fx)clipse 3.9.0
- Update your Eclipse installation
- Main Menu → Help → Install New Software...
- Use the e(fx)clipse 3.9.0 Update Site https://download.eclipse.org/efxclipse/updates-released/3.9.0/site
- Select e(fx)clipse - install / e(fx)clipse - IDE to get the all IDE features installed needed to develop JavaFX within Eclipse (https://www.eclipse.org/efxclipse/index.html)
- Restart the IDE and choose a workspace
- Update your Eclipse installation
- JavaFX >= 17
- https://openjfx.io/
- Download the JavaFX archive for your operating system
- Extract it
- e(fx)clipse
This recipe is based on the Eclipse RCP Cookbook – The Decoration Recipe. To get started fast with this recipe, the recipe is prepared for you on GitHub.
To use the prepared recipe, import the project by cloning the Git repository:
- File → Import → Git → Projects from Git
- Click Next
- Select Clone URI
- Enter URI https://github.com/fipro78/e4-cookbook-basic-recipe.git
- Click Next
- Select the preferences branch
- Click Next
- Choose a directory where you want to store the checked out sources
- Select Import existing Eclipse projects
- Click Finish
-
Open the target definition org.fipro.eclipse.tutorial.target.target in the project org.fipro.eclipse.tutorial.target
-
Update the Software Sites in the opened Target Definition Editor
- Alternative A
- Switch to the Source tab and add the following snippet to the editor
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <target name="E4 Cookbook Target Platform" sequenceNumber="1568034040"> <locations> <location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit"> <unit id="org.eclipse.equinox.executable.feature.group" version="3.8.2400.v20240213-1244"/> <unit id="org.eclipse.sdk.feature.group" version="4.31.0.v20240229-1022"/> <unit id="org.eclipse.equinox.core.feature.feature.group" version="1.15.0.v20240214-0846"/> <unit id="org.eclipse.equinox.p2.core.feature.feature.group" version="1.7.100.v20240220-1431"/> <repository location="https://download.eclipse.org/releases/2024-03"/> </location> <location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit"> <unit id="org.fipro.e4.service.preferences.feature.feature.group" version="0.4.0.202405151311"/> <repository location="https://github.com/fipro78/e4-preferences/raw/master/releases/0.4.0"/> </location> <location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit"> <repository location="http://download.eclipse.org/efxclipse/runtime-released/3.9.0/site"/> <unit id="org.eclipse.fx.runtime.min.feature.feature.group" version="3.9.0.202210162353"/> <unit id="org.eclipse.fx.target.rcp4.feature.feature.group" version="3.9.0.202210170008"/> </location> <location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit"> <repository location="https://downloads.efxclipse.bestsolution.at/p2-repos/openjfx-17.0.2/"/> <unit id="openjfx.media.feature.feature.group" version="17.0.2.202204012121"/> <unit id="openjfx.standard.feature.feature.group" version="17.0.2.202204012121"/> <unit id="openjfx.swt.feature.feature.group" version="17.0.2.202204012121"/> <unit id="openjfx.web.feature.feature.group" version="17.0.2.202204012121"/> </location> </locations> </target>
- Alternative B
- Add the e(fx)clipse Runtime extension to add JavaFX support
- Select Add...
- Select Software Site
- Click Next
- Enter http://download.eclipse.org/efxclipse/runtime-released/3.9.0/site in Work with: for the e(fx)clipse 3.9.0 release build
- Expand FX Target
- Check Minimal JavaFX OSGi integration bundles
- Check RCP e4 Target Platform Feature
(in case additional features provided by e(fx)clipse should be used)
- Click Finish
- Add the JavaFX libraries as OSGi bundles
Further information here- Select Add...
- Select Software Site
- Click Next
- Enter https://downloads.efxclipse.bestsolution.at/p2-repos/openjfx-17.0.2/ in Work with:
- Disable Group by Category as the items are not categorized and check all available items
- openjfx.media.feature
- openjfx.standard.feature
- openjfx.swt.feature
- openjfx.web.feature
- Click Finish
- Add the e(fx)clipse Runtime extension to add JavaFX support
- Alternative A
-
Switch to the Definition tab
- Wait until the Target Definition is completely resolved (check the progress at the bottom right)
- Reload and activate the target platform by clicking Reload Target Platform in the upper right corner of the Target Definition Editor
Instead of changing the InverterPart
to use a single JavaFX control instead of a SWT control for the output, we add a new InverterFXPart
that completely uses JavaFX controls. This is a difference to the original blog post.
- Update the application model fragments
-
Open the file fragment.e4xmi in the project org.fipro.eclipse.tutorial.inverter
-
Add a PartStack
-
Select Model Fragment Definition → Model Fragments → Model Fragment for PartSashContainer
-
On the details side select PartStack in the dropdown and click Add
-
In the tree view drag the existing Part to the created PartStack
-
Select the Part and set Label to Inverter (SWT)
-
Select the PartStack in the tree view
-
Select Part in the combo
-
Click on the Add button
- Set Label to Inverter (JavaFX)
-
Create the part implementation
-
Click the Class URI link in the part detail view
-
Set the values in the opened dialog
Property Value Package org.fipro.eclipse.tutorial.inverter.part Name InverterFXPart PostConstruct Method check -
Click Finish
-
Save the fragment.e4xmi file
-
-
-
Open the file fragment.e4xmi in the project org.fipro.eclipse.tutorial.logview
- Select Model Fragment Definition → Model Fragments → Model Fragment for PartSashContainer
- Change the value in Position in list: to
after:org.fipro.eclipse.tutorial.inverter.partstack.0
-
Implement the
InverterFXPart
- Create the content in the method annotated with
@PostConstruct
- Ensure that a
org.eclipse.swt.layout.GridLayout
is set on the parentComposite
- Add a
javafx.embed.swt.FXCanvas
to the parentComposite
inInverterFXPart#postConstruct(Composite)
- Create an instance of
javafx.scene.layout.GridPane
- Create a
javafx.scene.Scene
instance that takes the createdGridPane
as root node and sets the background color to be the same as the background color of the parentShell
- Set the created
javafx.scene.Scene
to theFXCanvas
- Ensure that a
- Create the content in the method annotated with
-
GridLayoutFactory.fillDefaults().applyTo(parent);
// add FXCanvas for adding JavaFX controls to the UI
FXCanvas canvas = new FXCanvas(parent, SWT.NONE);
GridDataFactory
.fillDefaults()
.grab(true, true)
.span(3, 1)
.applyTo(canvas);
// create the root layout pane
GridPane layout = new GridPane();
// create a Scene instance
// set the layout container as root
// set the background fill to the background color of the shell
Scene scene = new Scene(layout, Color.rgb(
parent.getShell().getBackground().getRed(),
parent.getShell().getBackground().getGreen(),
parent.getShell().getBackground().getBlue()));
// set the Scene to the FXCanvas
canvas.setScene(scene);
Now JavaFX controls can be added to the scene graph via the GridPane
instance.
- Create an instance field for the
input
control of typejavafx.scene.control.TextField
- Create an instance field for the
output
control of typejavafx.scene.control.Label
- Create an instance field for the
Color
to be used with the controls (needed for the preference support on startup)
TextField input;
Label output;
Color textColor;
- Create the user interface with labels and buttons and configure the layout via
javafx.scene.layout.GridPane
- Add the created controls to the
GridPane
// create the controls
Label inputLabel = new Label();
inputLabel.setText("String to revert:");
GridPane.setConstraints(inputLabel, 0, 0);
GridPane.setMargin(inputLabel, new Insets(5.0));
input = new TextField();
input.setStyle("-fx-text-fill: " + (textColor == Color.BLUE ? "blue" : "black") + ";");
GridPane.setConstraints(input, 1, 0);
GridPane.setHgrow(input, Priority.ALWAYS);
GridPane.setMargin(input, new Insets(5.0));
Button button = new Button();
button.setText("Revert");
GridPane.setConstraints(button, 2, 0);
GridPane.setMargin(button, new Insets(5.0));
Label outputLabel = new Label();
outputLabel.setText("Inverted String:");
GridPane.setConstraints(outputLabel, 0, 1);
GridPane.setMargin(outputLabel, new Insets(5.0));
output = new Label();
output.setTextFill(textColor);
GridPane.setConstraints(output, 0, 2);
GridPane.setColumnSpan(output, 3);
GridPane.setHgrow(output, Priority.ALWAYS);
GridPane.setHalignment(output, HPos.CENTER);
// don't forget to add children to gridpane
layout.getChildren().addAll(
inputLabel, input, button, outputLabel, output);
Add some animations to see some more JavaFX features:
-
Create a
javafx.animation.RotateTransition
that rotates the output label. -
Create a
javafx.animation.ScaleTransition
that scales the output label. -
Create a
javafx.animation.ParallelTransition
that combines theRotateTransition
and theScaleTransition
. This way both transitions are executed in parallel. -
Add starting the animation in the
SelectionAdapter
and theKeyAdapter
that are executed for reverting a String.// add an animation for the output RotateTransition rotateTransition = new RotateTransition(Duration.seconds(1), output); rotateTransition.setByAngle(360); ScaleTransition scaleTransition = new ScaleTransition(Duration.seconds(1), output); scaleTransition.setFromX(1.0); scaleTransition.setFromY(1.0); scaleTransition.setToX(4.0); scaleTransition.setToY(4.0); ParallelTransition parallelTransition = new ParallelTransition(rotateTransition, scaleTransition);
Align the functionality with the InverterPart
:
-
Get the
InverterService
and theIEventBroker
injected@Inject @Service private InverterService inverter; @Inject IEventBroker broker;
-
Add the action listener for the button and the input field
// add the action listener button.setOnAction(event -> { output.setText(inverter.invert(input.getText())); broker.post("TOPIC_LOGGING", "triggered via button (FX)"); parallelTransition.play(); }); input.setOnAction(event -> { output.setText(inverter.invert(input.getText())); broker.post("TOPIC_LOGGING", "triggered via field (FX)"); parallelTransition.play(); });
-
Add the following method to add the preference support like in the SWT
InverterPart
:@Inject @Optional public void setTextColor( @Preference(nodePath = "org.fipro.eclipse.tutorial.inverter", value = "inverter_color") String color) { textColor = "blue".equals(color) ? Color.BLUE : Color.BLACK; if (input != null) { input.setStyle("-fx-text-fill: " + color + ";"); } if (output != null) { output.setTextFill(textColor); } }
- Open the file org.fipro.eclipse.tutorial.app.product in the project org.fipro.eclipse.tutorial.product
- Switch to the Contents tab and add additional features
- Option A: Use the Minimal JavaFX OSGi integration bundles
- org.eclipse.fx.runtime.min.feature
- Option B: Use the RCP e4 Target Platform Feature
Note: As Include required Features and Plug-ins automatically is active, the additional required features and plug-ins will be added automatically. To have more control over the included Features and Plug-ins, disable this option and ensure manually that everything that is required is included.- org.eclipse.fx.target.rcp4.feature
- JavaFX OSGi bundles
- openjfx.media.feature
- openjfx.standard.feature
- openjfx.swt.feature
- openjfx.web.feature
- Option A: Use the Minimal JavaFX OSGi integration bundles
- Switch to the Launching tab
- Add -Dosgi.framework.extensions=org.eclipse.fx.osgi to the VM Arguments (adapter hook to get JavaFX-SWT integration on the classpath)
- Start the application from within the IDE
- Open the Product Configuration in the org.fipro.eclipse.tutorial.product project
- Select the Overview tab
- Click Launch an Eclipse Application in the Testing section
The started application should look similar to the following screenshot.
To build a deliverable product it is recommended to use Maven Tycho. The project was already prepared to build via pomless Tycho in the Eclipse RCP Cookbook – The Thermomix Recipe.
JavaFX is not on the default classpath, therefore the location of the JavaFX libraries need to be configured in the Tycho build for compile time resolution. The OpenJFX libraries are available via Maven Central and can be added as extra classpath elements via Maven. But the javafx-swt
module is not available via Maven Central as reported here.
That means for OpenJFX 17 following section needs to be added in the pluginManagement
section, where the JAVAFX_HOME environment variable points to your OpenJFX installation:
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho.version}</version>
<configuration>
<encoding>UTF-8</encoding>
<extraClasspathElements>
<extraClasspathElement>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>17.0.11</version>
</extraClasspathElement>
<extraClasspathElement>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swt</artifactId>
<version>17.0.11</version>
<systemPath>${JAVAFX_HOME}/lib/javafx-swt.jar</systemPath>
<scope>system</scope>
</extraClasspathElement>
</extraClasspathElements>
</configuration>
</plugin>
Start the build
mvn clean verify
The resulting product variants for each platform is located under e4-cookbook-basic-recipe/org.fipro.eclipse.tutorial.product/target/products
The currently available re-bundled OpenJFX versions can be found in this download area. If you are interested about newer OpenJFX versions you can have a look at the openjfx-osgi repository on GitHub or get in contact with BestSolution.at who created and provide the bundles.
Note:
As an alternative to bundling JavaFX with your application, you can also configure to use externally located JavaFX libraries. For this add -Defxclipse.java-modules.dir=<PATH_TO_YOUR_JAVAFX_LIBS> to the VM Arguments of the Product Configuration. As this approach makes the installation dependent on external states, I did not cover it here. But it is worth to mention as it might be interesting in some cases. There is also a blog post by Tom Schindl about JavaFX, Java 11, RCP and PDE.
There is an UI freeze issue related to JavaFX in Eclipse. It is an incompatibility of JavaFX and the glass.dll it tries to load when there are multiple Java versions available, e.g. if you start your application with a Java version that is different to the Java version configured in your PATH system environment variable.
When Eclipse launches it automatically generates an entry for java.library.path
that contains the path to the Java installation that is used to start Eclipse, and the PATH system environment variable. If another or additional Java installations are on the PATH, the java.library.path
environment variable contains paths to multiple Java versions. It can then happen that JavaFX loads the glass.dll from the not matching Java installation, which then leads to a NoSuchMethodError
or similar that cause a crash.
This issue is already reported here.
java.library.path
is a Java environment variable that is used to add additional native libraries to the runtime. It is only used in case native libraries should be added to an application that are loaded inside Java via System.loadLibrary()
. In Eclipse/OSGi development, native libraries are typically included inside a plug-in project and do not reside external, as this would make the installation dependent on locally installed native libraries on the consumer side. Therefore setting java.library.path
to an empty value (or an appropriate value in case it is needed) should not have any effect on other functionalities.
- Open the file org.fipro.eclipse.tutorial.app.product in the project org.fipro.eclipse.tutorial.product
- Switch to the Launching tab
- Add -Djava.library.path= to the VM Arguments (java.library.path will be empty and JavaFX will load the glass.dll from the Java installation that was used to start the application)