When developing an Eclipse RCP application in the past, you needed to use the Standard Widget Toolkit (SWT) as UI toolkit for creating and rendering the user interface. With Eclipse 4 you are not restricted to SWT anymore. The architecture separated the application model and the rendering, which allows to create an Eclipse based application using a different UI toolkit. Currently there are implementations for JavaFX and Vaadin available.
The following recipe shows the steps necessary to migrate a simple Eclipse 4 application from SWT to JavaFX using the e(fx)clipse tooling and runtime. It is based on the basic recipe introduced in a previous blog post and can also be used as basis for further recipes.
Cookware
- JDK 8
- http://www.oracle.com/technetwork/java/javase/downloads/index.html
- Simply run the executable and follow the installation instructions
- e(fx)clipse IDE 1.2.0
- JavaFX tooling and runtime for Eclipse and OSGi
- http://efxclipse.bestsolution.at/install.html#all-in-one
- Ensure the IDE is started with Java 8 if you have multiple Java versions installed
- Edit the file eclipse.ini which is located in the e(fx)clipse installation directory
- Add the following lines (typically before -showsplash)
-vm C:/Program Files/Java/jre8/bin/javaw.exe
See https://wiki.eclipse.org/Eclipse.ini#Specifying_the_JVM for further information.
- 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
Ingredients
This recipe uses the Eclipse RCP Cookbook – Basic Recipe . To get started fast with this recipe, we have prepared the basic recipe for you on GitHub .
If you want to use the prepared basic recipe to follow the migration from SWT to JavaFX, 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 master branch
- Click Next
- Choose a directory where you want to store the checked out sources
- Click Next
- Select Import existing projects
- Click Next
- Click Finish
Preparation
Step 1: Update the Target Platform
- Open the target definition de.codecentric.eclipse.tutorial.target.target in the project de.codecentric.eclipse.tutorial.target
- Remove the Software Site http://download.eclipse.org/releases/luna/ by selecting it in the Locations section and then clicking Remove
- Add a new Software Site by clicking Add… in the Locations section
- Select Software Site
- Software Site for the e(fx)clipse 1.2.0 release build
http://download.eclipse.org/efxclipse/runtime-released/1.2.0/site - Expand FX Target and check Target Platform Feature
Note: RCP e4 Target Platform Feature only contains fx plugins for helping adding JavaFX content to e4-SWT-RCP applications - Uncheck Include required software
- Click Finish
- Activate the target platform by clicking Set as Target Platform in the upper right corner of the Target Definition Editor
Step 2: Migrate the Application Project
- Update the application model
The application model itself is UI toolkit independent. But the binding addons are not. Therefore the default SWT binding addons need to be replaced with the corresponding JavaFX counterpart.- Open the file Application.e4xmi in the project de.codecentric.eclipse.tutorial.app
- Expand the Addons tree node under Application
- Remove the addon org.eclipse.e4.ui.bindings.service
(org.eclipse.e4.ui.bindings.BindingServiceAddon) - Add a new addon
- ID: org.eclipse.fx.ui.keybindings.e4.service
- Class:
bundleclass://org.eclipse.fx.ui.keybindings.e4/org.eclipse.fx.ui.keybindings.e4.BindingServiceAddon
- Remove the addon org.eclipse.e4.ui.workbench.bindings.model
(org.eclipse.e4.ui.workbench.swt.util.BindingProcessingAddon) - Add a new addon
- ID: org.eclipse.fx.ui.keybindings.e4.model
- Class:
bundleclass://org.eclipse.fx.ui.keybindings.e4/org.eclipse.fx.ui.keybindings.e4.BindingProcessingAddon
- Update the bundle dependencies
- Open the file MANIFEST.MF in the project de.codecentric.eclipse.tutorial.app
- Switch to the Dependencies tab
- Remove the following bundles from the Required Plug-ins
- javax.inject
- org.eclipse.core.runtime
- org.eclipse.swt
- org.eclipse.jface
- Add the following bundles to the Required Plug-ins
- org.eclipse.fx.ui.workbench.fx
- org.eclipse.fx.ui.theme
- org.eclipse.fx.ui.di
- org.eclipse.fx.ui.services
- org.eclipse.e4.core.services
- org.eclipse.e4.core.di.extensions
- Ensure the following bundles are set
- org.eclipse.e4.ui.model.workbench
- org.eclipse.e4.core.di
- org.eclipse.e4.ui.di
- org.eclipse.e4.core.contexts
- org.eclipse.e4.ui.workbench
- org.eclipse.e4.ui.services
- Add the following packages to the Imported Packages
- Update the extension points
- Open the file plugin.xml in the project de.codecentric.eclipse.tutorial.app
- Switch to the Extensions tab
- Update product extension point
- Set application to org.eclipse.fx.ui.workbench.fx.application
- Add a new property to the product extension point
- Right click on the product extension point → property
- name applicationXMI
- value de.codecentric.eclipse.tutorial.app/Application.e4xmi
Note that e(fx)clipse does only support theme based CSS styling. Therefore the applicationCSS property of the product extension point does not have an effect in e(fx)clipse 1.2.0 and can be removed. The recommended way to configure a theme is via declarative service as explained here . Since there is no CSS styling in the basic recipe, we don’t have to perform any migration actions here.
Step 3: Migrate the Plug-in Project
- Update the bundle dependencies
- Open the file MANIFEST.MF in the project de.codecentric.eclipse.tutorial.inverter
- Switch to the Dependencies tab
- Remove the following bundles from the Required Plug-ins
- org.eclipse.swt
- org.eclipse.jface
- Add the following packages to the Imported Packages
- javafx.*
In the basic recipe, the plug-in project is the only place where we directly get in touch with JavaFX. In the part implementation, UI toolkit controls are used to create the visible part of the application. Therefore the InverterPart
needs to be re-implemented for the usage of JavaFX.
The first thing to look at are the containers. A JavaFX application basically consists out of two containers, the Stage
which is the main/top-level container, and the Scene
which is the background container for UI elements, that can be exchanged on the Stage
. Within a Scene
, UI elements are arranged in a hierarchical scene graph, that typically has a layout pane as the root node.
Using the e(fx)clipse runtime, the Stage
and the Scene
are managed by the renderer. Within a part we start with a layout pane where the UI elements can be placed on. By default this is javafx.scene.layout.BorderPane
. This can be adjusted by setting a tag to the part definition in the application model. The list of available tags can be found in the Eclipse Wiki .
In SWT layouts are managed by creating a layout instance and setting it to a control. In the basic recipe based on SWT the parent org.eclipse.swt.widgets.Composite
is injected and a org.eclipse.swt.layout.GridLayout
is set it. To show a one-to-one migration, we will also use a grid layout in the JavaFX version. As the layout is specified by the node itself, we use the javafx.scene.layout.GridPane
as parent container, were the other controls will be added to. Note that our simple layout could also be achieved using for example a combination of wrapped javafx.scene.layout.VBox
and javafx.scene.layout.HBox
instances.
- Specify
javafx.scene.layout.GridPane
as the root container of theInverterPart
- Open the file Application.e4xmi in the project de.codecentric.eclipse.tutorial.app
- Select the part in the application model
Application → Windows and Dialogs → Trimmed Window → Controls → PartSashContainer → Part - Switch to the Supplementary tab on the right side of the part details
- Add the tag Container:GridPane via Tags input field
- Open the
InverterPart
in the project de.codecentric.eclipse.tutorial.inverter- Change the type of the parent parameter for the
postConstruct()
method fromComposite
toGridPane
- Delete the line that sets the
GridLayout
, since theGridPane
is already our layout pane - Exchange the control implementations. Use the default constructor for every JavaFX control.
org.eclipse.swt.widgets.Label
→javafx.scene.control.Label
org.eclipse.swt.widgets.Text
→javafx.scene.control.TextField
org.eclipse.swt.widgets.Button
→javafx.scene.control.Button
- Set the layout constraints for the controls
- While in SWT the column and row position of a control is determined by the insertion order when it is added to the parent control, you need to specify the column and row position in a JavaFX
GridPane
explicitly. This can be done by setting a constraint using one of the staticGridPane#setContraints()
helper methods, or by using one of theGridPane#add()
convenience methods for adding a control to aGridPane
instance. - Another difference in specifying a layout is, that in JavaFX the layout pane knows how to layout the children in detail, while in SWT the layout configuration (e.g. whether a control should grab all the remaining space) is configured on the control itself.
For example, the following SWTGridData
configuration tells that the input control should fill the available space and grab the extra horizontal space when the layout resizes.1GridDataFactory.fillDefaults().grab(true, false).applyTo(input);
To achieve the same in JavaFX we create and set a constraint for the input control with the following lines of code.
1GridPane.setConstraints(input, 1, 0); 2GridPane.setHgrow(input, Priority.ALWAYS); 3GridPane.setMargin(input, new Insets(5.0));
- Specify the action that should be performed on button click
- In SWT you set listeners to a control to be able to react on an event. Most of these listeners handle several different events in different methods, e.g. a
org.eclipse.swt.events.MouseListener
has methods to react onmouseDown()
,mouseUp()
andmouseDoubleClick()
. In JavaFX the same is achieved by setting anjavafx.event.EventHandler
for an explicit event via varioussetOnXxx()
methods. Asjavafx.event.EventHandler
is a functional interface, it has exactly one method for handling an event. This allows the usage of Java lambda expressions for registering anjavafx.event.EventHandler
.
In SWT you react on a click on aorg.eclipse.swt.widgets.Button
by adding aorg.eclipse.swt.events.SelectionListener
:1button.addSelectionListener(new SelectionAdapter() { 2 @Override 3 public void widgetSelected(SelectionEvent e) { 4 output.setText(StringInverter.invert(input.getText())); 5 } 6});
For JavaFX exchange this with the following line of code:
1button.setOnMouseClicked((e) -> 2 output.setText(StringInverter.invert(input.getText())));
- Also exchange the
org.eclipse.swt.events.KeyListener
on the input field with the followingjavafx.event.EventHandler
1input.setOnKeyPressed(event -> { 2 if (KeyCode.ENTER.equals(event.getCode())) { 3 output.setText(StringInverter.invert(input.getText())); 4 } 5});
- In SWT you set listeners to a control to be able to react on an event. Most of these listeners handle several different events in different methods, e.g. a
- Add the children to the layout pane
1parent.getChildren().addAll(inputLabel, input, button, outputLabel, output);
- While in SWT the column and row position of a control is determined by the insertion order when it is added to the parent control, you need to specify the column and row position in a JavaFX
- Change the type of the parent parameter for the
The finished part might look similar to the following snippet:
1package de.codecentric.eclipse.tutorial.inverter.part;
2
3import javafx.geometry.Insets;
4import javafx.scene.control.Button;
5import javafx.scene.control.Label;
6import javafx.scene.control.TextField;
7import javafx.scene.input.KeyCode;
8import javafx.scene.layout.GridPane;
9import javafx.scene.layout.Priority;
10
11import javax.annotation.PostConstruct;
12
13import de.codecentric.eclipse.tutorial.inverter.helper.StringInverter;
14
15public class InverterPart {
16
17 @PostConstruct
18 public void postConstruct(GridPane parent) {
19 Label inputLabel = new Label();
20 inputLabel.setText("String to revert:");
21 GridPane.setConstraints(inputLabel, 0, 0);
22 GridPane.setMargin(inputLabel, new Insets(5.0));
23
24 final TextField input = new TextField();
25 GridPane.setConstraints(input, 1, 0);
26 GridPane.setHgrow(input, Priority.ALWAYS);
27 GridPane.setMargin(input, new Insets(5.0));
28
29 Button button = new Button();
30 button.setText("Revert");
31 GridPane.setConstraints(button, 2, 0);
32 GridPane.setMargin(button, new Insets(5.0));
33
34 Label outputLabel = new Label();
35 outputLabel.setText("Inverted String:");
36 GridPane.setConstraints(outputLabel, 0, 1);
37 GridPane.setMargin(outputLabel, new Insets(5.0));
38
39 final Label output = new Label();
40 GridPane.setConstraints(output, 1, 1);
41 GridPane.setColumnSpan(output, 2);
42 GridPane.setHgrow(output, Priority.ALWAYS);
43 GridPane.setMargin(output, new Insets(5.0));
44
45 button.setOnMouseClicked((e) ->
46 output.setText(StringInverter.invert(input.getText())));
47
48 input.setOnKeyPressed(event -> {
49 if (KeyCode.ENTER.equals(event.getCode())) {
50 output.setText(StringInverter.invert(input.getText()));
51 }
52 });
53
54 // don't forget to add children to gridpane
55 parent.getChildren().addAll(
56 inputLabel, input, button, outputLabel, output);
57 }
58}
Step 4: Migrate the Product Configuration
- Open the file de.codecentric.eclipse.tutorial.app.product in the project de.codecentric.eclipse.tutorial.product
- Switch to the Overview tab
- Uncheck The product includes native launcher artifacts
- Update the Product Definition
- Application: org.eclipse.fx.ui.workbench.fx.application
- Switch to the Dependencies tab
- Remove
- org.eclipse.e4.rcp
- org.eclipse.emf.ecore
- org.eclipse.emf.common
- Add
- org.eclipse.fx.runtime.e4fx.feature
- Remove
- Switch to the Launching tab
- Add -nosplash to the Program Arguments
- Add -Dosgi.framework.extensions=org.eclipse.fx.osgi to the VM Arguments
Step 5: Taste
- Start the application from within the IDE
- Open the Product Configuration in the de.codecentric.eclipse.tutorial.product project
- Select the Overview tab
- Click Launch an Eclipse Application in the Testing section
- Export the application and start the deliverable
- Open the Product Configuration in the de.codecentric.eclipse.tutorial.product project
- Select the Overview tab
- Click Eclipse Product export wizard in the Exporting section
- Select a directory to export to in the Destination section of the export wizard
- Leave the other options unchanged
- Click Finish
- After the export is done, open the directory the application was exported to and start the application by executing the following command on the command line
java -jar plugins/org.eclipse.equinox.launcher_$VERSION.jar
In both cases the application should look similar to the following screenshot.
- Troubleshooting
- In case the application does not start, showing for example ClassCastExceptions, ensure to clean the workspace and the configuration directory.
- Run → Run Configurations → Select the run configuration for the application (Eclipse Application – de.codecentric.eclipse.tutorial.app.product)
- On the Main tab check Clear in the Workspace Data section
- On the Configuration tab check Clear the configuration area before launching in the Configuration Area section
- In case the application does not start, showing for example ClassCastExceptions, ensure to clean the workspace and the configuration directory.
Step 6: Delivery
With the above setup, the export will not generate an executable. We have chosen this setup because the preferred way to export a runnable JavaFX application is by using the JavaFX Packager Tool . On Windows and Linux the PDE export in Eclipse also works, so you basically have two options to create an application that can be delivered with an executable:
- Create an export via PDE export (only Windows and Linux)
- Open the target definition de.codecentric.eclipse.tutorial.target.target in the project de.codecentric.eclipse.tutorial.target
- Add a new Software Site
- Add a new Software Site by clicking Add… in the Locations section
- Software Site http://download.eclipse.org/releases/luna
- Disable Group by Category and filter for Eclipse
- Select Eclipse Platform Launcher Executables
- Activate the target platform by clicking Set as Target Platform in the upper right corner of the Target Definition Editor
- Open the file de.codecentric.eclipse.tutorial.app.product in the project de.codecentric.eclipse.tutorial.product
- Switch to the Overview tab
- Check The product includes native launcher artifacts
- Export and verify that the exported product contains an executable that starts the application
- Open the target definition de.codecentric.eclipse.tutorial.target.target in the project de.codecentric.eclipse.tutorial.target
- Create a self contained application package via JavaFX Packager Tool
- To package the application ensure that you already exported the application like shown in Step 5.
- To use the JavaFX Packager Tool you need to create a build.xml file. A good starting point is the build.xml created by the e4 JavaFX application wizard
- Create a temporary e4 JavaFX project
- File → New → Other → JavaFX → OSGI → e4 Application projects
- Enter temp as Bundle-ID-Prefix
- Enter temp as Product Name
- Ensure Tycho Build Stubs is checked
- Ensure Native Packaging is checked
- Create a new release engineering project
- File → New → Project → General → Project
- Set name to de.codecentric.eclipse.tutorial.releng
- Copy the following files from the temporary application releng project to the newly created releng project
- org.eclipse.fx.ide.ant.jar
- build.xml
- Modify the build.xml
- Change the eclipse–app-dir property to the directory the application was exported to
- Also update the fileset with the id equinox-launcher to point to that directory
You can reuse the eclipse-app-dir property
- Update the application name in fx:application and fx:info
- Execute the build
- Right click on build.xml → Run As → Ant Build
- Refresh the de.codecentric.eclipse.tutorial.releng project
- the sub-directory deploy/bundles contains a deliverable application with an executable
- Delete the temporary created projects
You can also find the finished migration on GitHub .
I’m curious to hear if the migration steps work for you and what further recipes you are interested in or if you faced any issues regarding such a migration. Feel free to contact me via email or comments section.
More articles
fromDirk Fauth
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Dirk Fauth
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.