Powering a Live Escape Game with ECF & e(fx)clipse

My talk about “Powering a Live Escape Game with ECF & e(fx)clipse” got selected as an early bird for the EclipseCon Europe 2016! I’m very excited about this.

Actually, this whole thing got started exactly two years ago at the EclipseCon Europe 2014. I was there to learn about new technologies in my area of work as an RCP/JavaEE engineer and I remember that I had some free time in my schedule and stumbled over a talk by Wim Jongman How to cook an egg with the Eclipse Communication Framework and Nebula. That sounded like fun, so I went and had a look. I was immediately and completely hooked and ordered my first Raspberry Pi on my phone the same day. What really caught my interest was that the components could use ECF to remotely talk to each other without needing a server! And even better: With ECF discovery they would be able to find each other dynamically!! And all of that by simply coding OSGi services!!!

As soon I got home I started playing with this stuff but soon got bored because I didn’t have a real world project to work on. Around that time one of my oldest friends, Mike Kleist, built his first Live Escape Game in Interlaken, Switzerland: The Secret of the Captain. If you have never played a live escape game you should definitely check it out. It is a lot of fun! It’s played live (meaning you have to be physically present). You will be locked in a mysterious room full of puzzles, riddles, keys and locks where logic and creativity are the tools you need to escape.

Mike wanted to build another game in Munich and we started to talk about how we could create a technically enhanced version of this concept with lasers, hidden doors and a “bomb disarming simulation” you need to solve by cutting the right cables just like 007 in the movies. Mike had lots of ideas how to use this new technology for his game so we started to get to work. There would be multiple gadgets with different sensors and actors and there would also be an application to show the status of the game to the players.

bombe-simulation
Prototype of the bomb disarming simulation

Now, when you are dealing with any kind of creative process you really need to be agile. You literally need to be able to change the software sitting on the floor with your laptop two hours past midnight while the paint on the walls is still getting applied and the first players are scheduled to arrive in the morning. Using the ECF implementation of OSGi remote services proved to be the perfect technology for this task and during my talk I will demonstrate why.

Mike also wanted to have a display to show the status of the “bomb disarming simulation” to the players. Of course this should look really cool und make use of animations and sound effects. Using JavaFX as the UI technology for this task is pretty much a no-brainer because you get all of this out of the box and you even get a visual designer (Scene Builder) to create the look of the UI together with your artist. Now all I needed to do was to get this to work together with ECF, with proved to be equally easy using e(fx)clipse since they are both based on OSGi.

Bomb_Timer
You need to solve all the riddles before the time is up!

And because everything worked together so beautifully, we also created a e4 application for the operator of the game, so he can see the status of all the sensors and actors and check that everything is working as it should. And of course the operator can remote control all the actors in the event of something not working as it should, for example if a sensor fails or a message gets lost in the network or whatever.

Operator
Game operator application

Et voilà! A new live escape game was born: The Missing Agent.

I want to take this opportunity to thank
Wim Jongman for the inspiration
Scott Lewis (ECF project lead) for the great support on the ecf mailing list
– and Tom Schindl (efxclipse project lead) for his equaly great support on the e(fx)clipse forum.

It’s people like this who make open source development so great and so much fun!
Hope to see you all at the EclipseCon Europe 2016!

Customizing the e4 main window with e(fx)clipse

In e4 the main user interface is constructed (rendered) based on the e4 application model. If you want to change the way that a specific model element is represented in the ui, you can plug in a custom renderer for this type. This approach is very powerful, but can be a technical challenge to get working the way you want it to. If you only want to make minor adjustments to the main window, like adding a search field to the menu bar, there is now an easier way with the e(fx)clipse 2.2.0 default window renderer using FXML.

Starting Point

The starting point for this introduction will be a small demo app I developed to show case this feature. The application model only has one part but uses every possible window trim location to place some buttons.

2015-11-27_133710

Yes, it’s ugly … but don’t worry … that will get even worse! 😉

2015-11-27_120129

The full demo application is available as part of the demos in the e(fx)clipse Git repository. To run it you need to check out all the org.eclipse.fx.demo.customWindow.* projects.

Customize the locations of window ui elements

Setup of the FXML

If we want be able to control the layout of the window, we need to tell the renderer where to put the children of the window. This is done using specific IDs that the renderer will look for after loading (checking the property id, not fx:id).

ID UI Elements Default Location
client-area children (sashes, stacks, part, …) center of the main BorderPane
menu-bar-area main menu bar top of the main BorderPane
top-trim-area top window trim bar (toolbar, tool control) top of the client area
bottom-trim-area bottom window trim bar (toolbar, tool control) bottom of the client area
left-trim-area left window trim bar (toolbar, tool control) left of the client area
right-trim-area right window trim bar (toolbar, tool control) right of the client area

The root element of the FXML has to be a JavaFX BorderPane or a org.eclipse.fx.ui.controls.stage.TrimmedWindow. In most cases using the BorderPane will be what you want to do. You only need to use a TrimmedWindow if you also need control over the window decorations (like the min/max/close buttons on the top).

Hook the FXML to the window

It is very easy to hook an FXML to a window in the application model. All you need to to is set the property efx.window.root.fxml in the persisted state of the window. The value is an URL pointing to the FXML you want to use.

2015-11-27_121821

Examples

Insert a search field into the menu bar

This FXML will insert a TextField in the top row next to the main menu bar. The menu bar will be placed inside the HBox with id="menu-bar-area". The rest of the windows elements (trim bars, client area) will be placed by default. In this example I left out the properties maxHeight and maxWidth which I usually set to Double.MAX_VALUE for all containers, so you can focus on the structure of the window.

<BorderPane fx:controller="org.eclipse.fx.demo.customWindow.app.controller.MainFrameController">
	<top>
		<VBox>
			<children>
				<HBox>
					<children>
						<HBox id="menu-bar-area" />
						<TextField fx:id="searchField" promptText="Search..." />
					</children>
				</HBox>
			</children>
		</VBox>
	</top>
</BorderPane>

As you can see in this screen shot, we now have a text input field next to the menu bar:

2015-11-27_120350

To use the new search field in my application I can inject it into the controller I defined with fx:controller in the FXML. Here I use the e(fx)clipse EventBus to forward a search request, but there are lots of ways on how to proceed from here. Notice that it is possible to use dependency injection to get hold of all the e4 and OSGi services in the controller!

public class MainFrameController implements Initializable {

    @Inject private EventBus eventBus;

    @FXML TextField searchField;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        searchField.textProperty().addListener((observable, oldValue, newValue) -> {
            eventBus.publish(new Topic<String>("app/event/search"), newValue, true);
        });
    }
}

Go crazy

In the full demo I placed every trim bar on the outside of the window so I can insert a “cool” separation bar between the client area and the trim bars (yes … the blue bars with the light blue circles … I warned you it would get ugly!). I also thought it would be totally awesome to place the tool bar on top of the menu bar just to … well … show thats it’s possible, I guess. 😉

2015-11-26_132047

Please have a look at the complete FXML. I also put the “client area” in there, although I could have skipped this, since it would be placed in the center of the main BorderPane by default anyway.

Using SceneBuilder

You might notice that I sometimes used minWidth and minHeight at some places in the FXML. I also gave the root BorderPane a prefWidth and prefHeight. I did this, so that the pane visually resembles the final outcome if you open it with SceneBuilder. This is not really necessary for the correct rendering and can look broken, if you plan to remove trims at runtime, since the container will then leave an empty space where the trim can be. If you omit the minimums the container will shrink to 0 and will therefore become invisible when the trim is removed.

2015-11-27_123325

Filtering a JavaFX TreeView

In this article I will demonstrate how to filter data in a JavaFX TreeView using a FilteredList.

Introduction

To populate a TreeView with data it is necessary to create a hierarchy of tree items and set the root TreeItem for the TreeView. To filter our data we could directly manipulate TreeItem#getChildren but that would be cumbersome. Especially since JavaFX already provides classes that can help: To filter an OberservableList you can use a FilteredList and set a Predicate on the list to control the filter. So our goal is to use this class together with TreeItem.

We also want to be able to set the predicate only on the root tree item, so that we can perform the filtering in a single place. Usually we also don’t want to hide folders that still have children even if they wouldn’t match the predicate. Ideally we would like the tree item to automatically check whether it has any children and only apply the predicate when it is a leaf.

Implementation

The complete implementation can be viewed here.

The Filterable Tree Item

To start lets marry TreeItem and FilteredList in a new subclass: FilterableTreeItem. In the constructor we will create a FilteredList using a new observable array list as its source. Since overriding getChildren will not work properly in Java 8 (see JDK-8089158), we need to use a hack to set the private field TreeItem#children
to this filtered list instead of the original source. Since a FilteredList is unmodifiable we will provide a new method to access the source list directly: getInternalChildren().

public class FilterableTreeItem<T> extends TreeItem<T> {
    final private ObservableList<TreeItem<T>> sourceList;
    private FilteredList<TreeItem<T>> filteredList;

    public FilterableTreeItem(T value) {
        super(value);
        this.sourceList = FXCollections.observableArrayList();
        this.filteredList = new FilteredList<>(this.sourceList);
        setHiddenFieldChildren(this.filteredList); 
    }

    protected void setHiddenFieldChildren(ObservableList<TreeItem<T>> list) {
        try {
            Field childrenField = TreeItem.class.getDeclaredField("children"); //$NON-NLS-1$
            childrenField.setAccessible(true);
            childrenField.set(this, list);

            Field declaredField = TreeItem.class.getDeclaredField("childrenListener"); //$NON-NLS-1$
            declaredField.setAccessible(true);
            list.addListener((ListChangeListener<? super TreeItem<T>>) declaredField.get(this));
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            throw new RuntimeException("Could not set TreeItem.children", e); //$NON-NLS-1$
        }
    }

    public ObservableList<TreeItem<T>> getInternalChildren() {
        return this.sourceList;
    }
}

The Predicate

Sometimes you might want to adjust the filter depending on the hierarchy you are in. In this case it would be nice to have the current TreeItem available when evaluating Predicate#test. So let’s define a new interface and add a convenience method to convert a classic Predicate:

@FunctionalInterface
public interface TreeItemPredicate<T> {

	boolean test(TreeItem<T> parent, T value);

	static <T> TreeItemPredicate<T> create(Predicate<T> predicate) {
		return (parent, value) -> predicate.test(value);
	}

}

To use this interface in FilterableTreeItem we define an ObjectPropery to set the TreeItemPredicate. We then need to install a binding that sets the predicate property of FilteredList.

public class FilterableTreeItem<T> extends TreeItem<T> {
    private FilteredList<TreeItem<T>> filteredList;

    private ObjectProperty<TreeItemPredicate<T>> predicate = new SimpleObjectProperty<>();

    public FilterableTreeItem(T value) {
        super(value);
        this.sourceList = FXCollections.observableArrayList();
        this.filteredList = new FilteredList<>(this.sourceList);
        this.filteredList.predicateProperty().bind(Bindings.createObjectBinding(() -> {
            return child -> {
                // Set the predicate of child items to force filtering
                if (child instanceof FilterableTreeItem) {
                    FilterableTreeItem<T> filterableChild = (FilterableTreeItem<T>) child;
                    filterableChild.setPredicate(this.predicate.get());
                }
                // If there is no predicate, keep this tree item
                if (this.predicate.get() == null)
                    return true;
                // If there are children, keep this tree item
                if (child.getChildren().size() > 0)
                    return true;
                // Otherwise ask the TreeItemPredicate
                return this.predicate.get().test(this, child.getValue());
            };
        }, this.predicate));
        setHiddenFieldChildren(this.filteredList);
    }

    ...
}

Example

The usage of FilterableTreeItem is pretty straight forward. Let’s assume we are using the data type Actor as a base type for the TreeView.

class Actor {
    public String firstname;
    public String lastname;

    public Actor(String string) {
        this.lastname = string;
    }

    public Actor(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }

    @Override
    public String toString() {
        return firstname == null ? lastname : firstname + " " + lastname;
    }
}

We want to filter the displayed actor using a TextField as input.

FilterableTreeItem<Actor> root = getTreeModel();

root.predicateProperty().bind(Bindings.createObjectBinding(() -> {
    if (filterField.getText() == null || filterField.getText().isEmpty())
        return null;
    return TreeItemPredicate.create(actor -> actor.toString().contains(filterField.getText()));
}, filterField.textProperty()));

TreeView<Actor> treeView = new TreeView<>(root);

The complete sample Application can be viewed here and looks like this:
2015-05-15_132450

Conclusion

A marriage between TreeItem and FilteredList could be implemented fairly nicely if it would be possible to customize the list implementation used in TreeItem without resorting the the evil reflective hack described above (thanks to Jeanette and J. Duke for pointing me to this solution). I hope that a possibility to customize the list implementation will be introduced in the future as I have suggested in RT-40790/JDK-8091687.

These classes were contributed to the e(fx)clipse open source framework in the bundle org.eclipse.fx.ui.controls so you can use this feature, the very similar SortableTreeItem and other cool stuff provided there.

Migrate E4 RCP from SWT to JavaFX using SWTonJavaFX

In this blog I would like to demonstrate how to migrate an Eclipse4 RCP application from SWT to JavaFX using SWTonJavaFX and how to deal with some of the problems that might arise in a real world application.

Introduction

The goal of this investigation is to migrate the project as quickly and simply as possible. Most importantly, we only want to make changes to the code where absolutely necessary. This basically means that we don’t want to change any of our SWT code (parts, wizards, dialogs, etc.)! To achieve this we will integrate SWTonJavaFX, so we can keep on using the SWT API and migrate to JavaFX on demand.

I set up a small test project de.emsw.e4.migration to simulate some of the concepts used in our larger E4-SWT applications. This is what it looks like:
SWT screenshot

You can clone the git repository and have a look at the code changes. The SWT code will be in branch master and the JavaFX code will be in the branch migration.

Dirk Fauth has recently published a migration tutorial about the basic steps, which will serve as a basis for this investigation. Please note however, that Dirk uses efxclipse 1.2.0 whereas I am using efxclipse 2.0.0. There are some small but important differences!

Target Platform

I will not go into the setup a new target platform, because Dirk has already described a good way to get there in his article. Dirk uses the url to efxclipse 1.2.0, so you will need to change that. 2.0.0 has not been released at the time of writing, but you can use the nightly build site http://download.eclipse.org/efxclipse/runtime-nightly/site.

E(fx)clipse Runtime

Since I wanted to work with the current sources of e(fx)clipse 2.0.0 (and maybe contribute some code while I am into this investigation) I cloned the e(fx)clipse repo and imported the runtime bundles I needed from bundles/runtime/* into the workspace. At our company, we use a manually managed target platform, so we don’t work directly with the provided e(fx)clipse runtime feature anyway.

Note: Since we are using e(fx)clipse 2.0 we don’t need the bundles org.eclipse.fx.osgi and org.eclipse.fx.javafx anymore. For more information why please refer to Tom Schindls post on the forum.

SWTonJavaFX

The bundles for SWTonJavaFX are also in the e(fx)cipse git repository (experimental/swt/*) and can also be imported into the workspace. You need these projects:

  • org.eclipse.fx.runtime.swt
  • org.eclipse.fx.runtime.swt.compat (== org.eclipse.swt)
  • org.eclipse.fx.runtime.swt.e4

The project org.eclipse.fx.runtime.swt contains the actual code implementing the SWT API with JavaFX. The project to watch out for is org.eclipse.fx.runtime.swt.compat, since this is the project that mimics org.eclipse.swt. So you will only want to have either the original bundle org.eclispe.swt or org.eclipse.fx.runtime.swt.compat in your target platform. The bundle org.eclipse.fx.runtime.swt.e4 will register components supporting the migration from SWT.

A side note concerning org.eclipse.fx.runtime.swt.compat: Since I imported it as a project into the workspace, it will automatically be used instead of the original org.eclispe.swt from the target platform definition. If I want to use the original SWT library I only need to close org.eclipse.fx.runtime.swt.compat. This approach can lead to some difficulties though, so if you want to be 100% safe, you should remove org.eclipse.swt from your target platform.

Basic Migration

Now that we have all the libraries we need, we can migrate the actual project. Dirks migration tutorial is a big help, so I will only summarize the effects here. You can also look at the git commit to see all the changes.

Product Definition

The first thing we need to do is change the application entry point in the plugin.xml by changing the attribute application of <product> to:

org.eclipse.fx.ui.workbench.fx.application

(While you are here you can drop the property applicationCSS. This will not be evaluated by efxclipse.)

The application entry point must be changed accordingly in the product configuration (e4migration.product in my test project):

org.eclipse.fx.ui.workbench.fx.application

Bundles

I used a plug-in based product configuration for this demo project, so it is easy to see which bundles need to be replaced.

What we don’t need any more from E4-SWT:

org.eclipse.e4.ui.bindings
org.eclipse.e4.ui.css.core
org.eclipse.e4.ui.css.swt
org.eclipse.e4.ui.css.swt.theme
org.eclipse.e4.ui.widgets
org.eclipse.e4.ui.workbench.addons.swt
org.eclipse.e4.ui.workbench.renderers.swt
org.eclipse.e4.ui.workbench.swt
org.eclipse.e4.ui.workbench3

Additional bundles from e(fx)clipse (including SWTonJavaFX):

org.eclipse.fx.core
org.eclipse.fx.core.databinding
org.eclipse.fx.core.di
org.eclipse.fx.core.di.context
org.eclipse.fx.core.fxml
org.eclipse.fx.osgi.util
org.eclipse.fx.runtime.swt
org.eclipse.fx.runtime.swt.e4
org.eclipse.fx.ui.animation
org.eclipse.fx.ui.controls
org.eclipse.fx.ui.di
org.eclipse.fx.ui.dialogs
org.eclipse.fx.ui.keybindings
org.eclipse.fx.ui.keybindings.e4
org.eclipse.fx.ui.keybindings.generic
org.eclipse.fx.ui.panes
org.eclipse.fx.ui.services
org.eclipse.fx.ui.theme
org.eclipse.fx.ui.workbench.base
org.eclipse.fx.ui.workbench.fx
org.eclipse.fx.ui.workbench.renderers.base
org.eclipse.fx.ui.workbench.renderers.fx
org.eclipse.fx.ui.workbench.services

Because we are using SWTonJavaFX we also don’t need the swt platform fragments, so in my test project for windows I could also get rid of org.eclipse.swt.win32.win32.x86. Remember that we are keeping org.eclipse.swt though, because it is mimicked by org.eclipse.fx.runtime.swt.compat.

At the time of writing, we can also get rid of some dependencies that came with E4-SWT like org.apache.batik.* and org.w3c.*. The only 3rd party dependency that comes with e(fx)clipse at the moment is org.apache.commons.lang.

Launch Configuration

To use JavaFX, you need to tell Equinox to use the extension class loader by adding

-Dorg.osgi.framework.bundle.parent=ext

to the vm arguments. (Note that this differs from efxclipse 1.x where it used to be osgi.framework.extensions=org.eclipse.fx.osgi.)

This is a very important step, otherwise you will see errors about equinox not being able to load javafx.* classes:

java.lang.NoClassDefFoundError: javafx/application/Application
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at org.eclipse.osgi.internal.loader.ModuleClassLoader.defineClass(ModuleClassLoader.java:272)
    ...
Caused by: java.lang.ClassNotFoundException: javafx.application.Application cannot be found by org.eclipse.fx.ui.workbench.fx_2.0.0.qualifier
    at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:423)

MANIFEST.MF

In my case I didn’t have to make any changes to the MANIFEST.MF at this stage. This is due to the fact that I have only defined plug-in dependencies that are actually needed by the compiler. Everything else you need is added directly to the feature definition (or product definition, like in the test project).

If you are working on your own migration you need to check this and throw out any of the replaced e4 bundles mentioned above.

Note that, with e(fx)clipse 2.0, you should not add javafx.* to the imported packages, as this will be taken care of by using the extension class loader.

Application Model

To migrate the application model (TestApp.e4xmi in my test project) we follow Dirks migration tutorial and swap the two add-ons

  1. org.eclipse.e4.ui.bindings.BindingServiceAddon
  2. org.eclipse.e4.ui.workbench.swt.util.BindingProcessingAddon

with

  1. org.eclipse.fx.ui.keybindings.e4.BindingServiceAddon
  2. org.eclipse.fx.ui.keybindings.e4.BindingProcessingAddon

Frequently Encountered Problems

In this chapter I will explain some other small changes that you might need to make to your code, depending on whether or not you used certain features.

Injection of the active Shell

When it comes to dependency injection org.eclipse.fx.runtime.swt.e4 is your friend. It registers context functions which will supply a Composite or a Display instance if requested. So most of the DI stuff already works as you would expect.

One exception is IServiceConstants.ACTIVE_SHELL. We use this key in our E4-SWT projects all the time in handlers when we want to open a dialog or wizard. To demonstrate, here is the simplified handler in my test project:

public class DialogHandler {
    @Execute
    public void execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell shell) {
        SampleTitleAreaDialog dialog = new SampleTitleAreaDialog(shell);
        dialog.open();
    }
}

The problem here is that IServiceConstants.ACTIVE_SHELL is (despite its SWT-like naming) meant to be independent of the ui framework being used. The DI container will inject an object of the type that the ui framework considers to be its equivalent to Shell. Using JavaFX this will be a Stage.

The consequence of this is that our handler will compile correctly, but will never execute, because there is no Shell available to inject, only a Stage. What we want to have is a Shell wrapper around this Stage, so we can pass it as a parent in our constructor. To get this we need to change the key from IServiceConstants.ACTIVE_SHELL to SWTServiceConstants.ACTIVE_SHELL (you will need a dependency on org.eclipse.fx.runtime.swt.e4 to use this). A context function in will take care of supplying a wrapper Shell around the current Stage.

The migrated version of the handler looks almost the same:

public class DialogHandler {
    @Execute
    public void execute(@Named(SWTServiceConstants.ACTIVE_SHELL) Shell shell) {
        SampleTitleAreaDialog dialog = new SampleTitleAreaDialog(shell);
        dialog.open();
    }
}

Modifying a Widget

There might be some cases where you are doing something to an SWT widget after is has been created by the renderer. A simple example of this is the MaximizeAddon in my test project. The add-on registers an EventHandler which waits until the first MWindow has been created and then gets calls getWidget() on it to retrieve the Shell:

MWindow windowModel = (MWindow) objElement;
Shell theShell = (Shell) windowModel.getWidget();
if (theShell == null)
    return;
theShell.setMaximized(true);

Although this will compile, we will get a ClassCastException at runtime. The simple reason being that the renderer doesn’t return a Shell anymore.

Since we are down to the internals of the renderer, we need to change this code. The efxclipse renderer adds another level of abstraction (WWindow), so we need to perform two steps until we reach the Stage:

MWindow windowModel = (MWindow) objElement;
WWindow<?> windowImpl = (WWindow<?>) windowModel.getWidget();
Stage theStage = (Stage) windowImpl.getWidget();
if (theStage == null)
    return;
theStage.setMaximized(true);

You will need to add a dependency on org.eclipse.fx.ui.workbench.renderers.base to get this to compile.

Menu Mnemonics

A small difference between SWT und JavaFX is how you define a mnemonic key for menus and menu items. Where SWT uses the “&” character JavaFX expects you to use the underscore “_” instead. Hence, if you use “&” to set mnemonics with JavaFX, you will see the “&” turn up in the labels of your menu and the mnemonic will not work.

The direct solution here is to change the labels directly in the e4xmi or properties file where your menu labels are defined. If you want to have a fast hook to change the labels at runtime you could supply your own TranslationService on top of the BundleTranslationProvider. I used the life cycle handler to push my custom implementation into the application context:

@ProcessAdditions
public void processAdditions(MApplication application) {
    TranslationService translationProvider = new BundleTranslationProvider() {
        @Override
        public String translate(String key, String contributorURI) {
            String result = super.translate(key, contributorURI);
            return result.replace('&', '_');
        }
    };
    application.getContext().set(TranslationService.class, translationProvider);
}

Toolbars and ToolControls

Toolbars might look “cut off”, especially if you are using a single Toolbar in a Window Trim. The toolbar will be expanded by adding the tag fillspace to the tags of the Toolbar.
2015-03-30_110019

A tool control might also not layout as you would like. This could be because the renderer uses the JavaFX Group as a container by default. You can configure the tool control to use a HBox instead which will behave nicer if resized. This is done by a tag to the ToolControl in the application model: Container:HBox. (Don’t forget to add the fillspace tag too.)

Utilizing e(fx)clipse Features

Now that our application is running, let’s see how we can get more out of e(fx)clipse.

Add a Theme

A common reason to migrate to JavaFX is to have more control over the visual appearance of the application. Efxclipse supports theme based CSS styling. Tom Schindl described the motives for implementing themes with OSGi services in this article.

If you want to view the whole change right away, you can have a look at the commit.

Adding a Theme as a Service

The first thing we need is an implementation of the interface Theme which can be easily done by extending AbstractTheme (you need to add dependencies to the bundles org.eclipse.fx.ui.services and org.eclipse.fx.ui.theme):

public class DefaultTheme extends AbstractTheme {
    public DefaultTheme() {
        super("default", "Default Theme", DefaultTheme.class.getClassLoader().getResource("css/default.css"));
    }
}

This needs to be setup as an OSGi service using a component definition (for example in OSGI-INF/osgi-DefaultTheme-component.xml):

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="de.emsw.e4.migration.theme.default">
   <implementation class="de.emsw.e4.migration.DefaultTheme"/>
   <service>
      <provide interface="org.eclipse.fx.ui.services.theme.Theme"/>
   </service>
   <reference 
     bind="registerStylesheet" 
     cardinality="0..n" 
     interface="org.eclipse.fx.ui.services.theme.Stylesheet" 
     name="Stylesheet" 
     policy="dynamic" 
     unbind="unregisterStylesheet"/>
</scr:component>

Don’t forget to add the new file to the build.properties and to include it in MANIFEST.MF:

Service-Component: OSGI-INF/osgi-DefaultTheme-component.xml

By the way: The applicationCSS property of the product extension point is not evaluated and can be removed, if you havn’t done so already.

Modifying the CSS File

In my case, I already have the file css/default.css (which is loaded by the DefaultTheme we created before) in my project. Until now it contained css for the SWT renderer. Although this is not valid anymore, we can still reuse the file itself, so let’s open it. To edit CSS for JavaFX you should use the e(fx)clipse Css Editor.
2015-04-01_113955
(If you are asked if you want to add the “Xtest nature” to your project: Say “Yes”.)

If there are already contents in your file, you can delete it all and start from scratch. To demonstrate, let’s set up a simple dark theme as described here:

.root {
    -fx-base: rgb(50, 50, 50);
    -fx-background: rgb(50, 50, 50);
    -fx-control-inner-background:  rgb(50, 50, 50);
}

Aborting the Startup

The test application starts up with a login dialog requesting a username and a password. The original SWT code uses `System.exit(0)’ to abort the startup if the user canceled this operation. The code in the life cycle handler basically looks like this:

@PostContextCreate
public void postContextCreate(IEclipseContext context) {
    LoginService loginService = ContextInjectionFactory.make(LoginService.class, context);
    boolean result = loginService.login();
    if (!result) {
        System.exit(0);
    }
}

I have always considered this to be “unkind” to the platform, so I was happy to see that e(fx)clipse offers a “soft” way to abort the startup by returning a value of LifecycleRV from @PostContextCreate:

@PostContextCreate
public LifecycleRV postContextCreate(IEclipseContext context) {
    LoginService loginService = ContextInjectionFactory.make(LoginService.class, context);
    boolean result = loginService.login();
    if (!result) {
        return LifecycleRV.SHUTDOWN;
    }
    return LifecycleRV.CONTINUE;
}

You can also use this to restart the application after an update with LifecycleRV.RESTART or LifecycleRV.RESTART_CLEAR_STATE if you also want to reset the application model. This and some other new features you get with e(fx)clipse are described on the wiki.

Conclusion

So this is what my test application looks like with JavaFX:
JavaFX screenshot

Of course there is still some work to do, but I hope that this information will help you get started with your own migration.

If you run into problems or missing API with SWTonJavaFX please refer to this discussion about the current status of SWTonJavaFX on the e(fx)clipse forum.

If you have any questions please feel free to leave a comment.