Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Efxclipse/Runtime/Recipes
This page holds best practice recipes when writing JavaFX application using e(fx)clipse
Contents
Logging
e(fx)clipse has its own logging facade org.eclipse.fx.core.log.Logger
which allows to plug-in different log frameworks.
Currently available are:
-
java.util.Logging
(default) - log4j by adding
org.eclipse.fx.core.log4j
bundle to your OSGi-Runtime
Usage
There are different ways to use get a logger.
LoggerCreator
If you are running on OSGi you can add the org.eclipse.fx.osgi.util
bundle which provides access to the LoggerCreator
factory class
import org.eclipse.fx.core.log.Logger; import org.eclipse.fx.osgi.util.LoggerCreator; public class MyClass { private static Logger LOGGER = LoggerCreator.createLogger(MyClass.class); // .... }
LoggerFactory Service
The different logger bundles contribute their LoggerFactory
implementation into the OSGi-Registry. In case you are e.g. contributing a service via DS you can get simple add a service reference and you'll get the LoggerFactory
with the highest rank injected.
import org.eclipse.fx.core.log.LoggerFactory; import org.eclipse.fx.core.log.Logger; public class MyServiceImpl implements MyService { private Logger logger; public void setLoggerFactory(LoggerFactory factory) { this.logger = factory.createLogger(MyService.class.getName()); } public void unsetLoggerFactory(LoggerFactory factory) { } }
<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="myservice"> <implementation class="impl.MyServiceImpl"/> <service> <provide interface="service.MyService"/> </service> <reference bind="setLoggerFactory" cardinality="1..1" interface="org.eclipse.fx.core.log.LoggerFactory" name="LoggerFactory" policy="static" unbind="unsetLoggerFactory"/> </scr:component>
Eclipse DI & @Log annotation
If you make use of Eclipse DI in your code you can get a Logger
instance injected with:
import org.eclipse.fx.core.log.Log; import org.eclipse.fx.core.log.Logger; import javax.inject.Inject; public class MyDIComponent { @Inject @Log Logger logger; }
Google Guice & @Log annotation
If you use Guice as the DI container you use the org.eclipse.fx.core.guice
bundle to get a Logger injected in your component with:
import org.eclipse.fx.core.log.Log; import org.eclipse.fx.core.log.Logger; import javax.inject.Inject; public class MyDIComponent { @Log Logger logger; }
if you have configured your Guice-Module with:
import com.google.inject.Module; import com.google.inject.Binder; import org.eclipse.fx.core.log.LoggerFactory; import org.eclipse.fx.core.log4j.Log4JLoggerFactory; import org.eclipse.fx.core.guice.FXLoggerListener; public class MyModule implements Module { public void configure(Binder binder) { binder.bind(LoggerFactory.class).toProvider(Log4JLoggerFactory.class); // or JUtilLoggerFactory binder.bindListener(Matchers.any(), new FXLoggerListener()); } }
Instead of directly binding to a logger factory you can delegate to the OSGi-Service registry by using OSGiLoggerFactoryProvider
:
import com.google.inject.Module; import com.google.inject.Binder; import org.eclipse.fx.core.log.LoggerFactory; import org.eclipse.fx.core.guice.FXLoggerListener; import org.eclipse.fx.core.guice.OSGiLoggerFactoryProvider; public class MyModule implements Module { public void configure(Binder binder) { binder.bind(LoggerFactory.class).toProvider(OSGiLoggerFactoryProvider.class); binder.bindListener(Matchers.any(), new FXLoggerListener()); } }
Extending
Like outlined above there are 2 logger implementations available from the e(fx)clipse p2 repository. If you want to use a different logging framework you are able to plug-in your own by implementing LoggerFactory
and contributing it to the OSGi-Service-Registry.
import javax.inject.Provider; import org.eclipse.fx.core.log.LoggerFactory; import org.eclipse.fx.core.log.Logger; public class MyLoggerFactory implements LoggerFactory, Provider<LoggerFactory> { @Override public LoggerFactory get() { return this; } @Override public Logger createLogger(String name) { return new LoggerImpl(name); } static class LoggerImpl implements Logger { private String name; public LoggerImpl(String name) { this.name = name; } // .... } }
You contribute it to the OSGi-Service registry e.g. by using DS. You should give the service a higher ranking than 1 (which is the ranking of the log4j service) to ensure it is picked when a logger is requested.
<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="my.logger.framework.factory"> <implementation class="my.logger.framework.MyLoggerFactory"/> <property name="service.ranking" type="Integer" value="2"/> <service> <provide interface="org.eclipse.fx.core.log.LoggerFactory"/> </service> </scr:component>
Eclipse DI
Publishing to the IEclipseContext
The IEclipseContext
is the central component of the Eclipse DI container. Retrieving values from it is as easy as writing @Inject
in your java class and the DI container will fill it with a value and keep it up-to-date if you used field or method injection.
The opposite - publishing a value into the context - is not as easy because your java component will get a dependency on the DI-Container because it needs to access the IEclipseContext
directly. e(fx)clipse provides you the possibility to get around this architectual problem by defining an annotation named @ContextValue
which marks a slot in IEclipseContext
instance which can be used to observe the value and modified.
import org.eclipse.fx.core.di.ContextBoundValue; import org.eclipse.fx.core.di.ContextValue; import javax.inject.Inject; public static class SimpleInject { @Inject @ContextValue(contextKey="user") public ContextBoundValue<String> value; private void updateValue() { value.publish("tomschindl"); } }
To make use of this your bundle needs to have a dependency on org.eclipse.fx.core.di
and your runtime has to include org.eclipse.fx.core.di.context
.
Injected value as an observable
A reoccuring pattern when developing with Eclipse Databinding and Dependency Injection is that the injected value is used as the master in master-detail binding scenario.
import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.fx.core.di.ContextBoundValue; import org.eclipse.fx.core.di.ContextValue; public class DetailView { private IObservableValue master = new WritableValue(); @Inject public void update(Person p) { master.getRealm().exec(new Runnable() { public void run() { master.setValue(p); } }); } }
The @ContextValue
framework introduce in the recipe above is able to reduce the code to
import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.fx.core.di.ContextBoundValue; import org.eclipse.fx.core.di.ContextValue; public class DetailView { @Inject @ContextValue("my.domain.Person") IObservableValue master; }
If you prefer the JavaFX observable system you get let the system inject you this type as well
import javafx.beans.property.Property; import org.eclipse.fx.core.di.ContextBoundValue; import org.eclipse.fx.core.di.ContextValue; public class DetailView { @Inject @ContextValue("my.domain.Person") Property<Person> property; }
Generally speaking you can use any type for the value injected by @ContextValue
as long as there's an adapter registered which is able to convert from ContextBoundValue
to it.
An observable value is not a one way street so it also allows publish through it:
public class ListView { @PostConstruct public void initUI(@ContextValue("my.domain.Person") Property<Person> property) { ListView<Person> v = new ListView<>(); // ... property.bind(v.getSelectionModel().selectedItemProperty()); } }
@FXMLLoader
If you want to use DI in the controller attached to an FXML-File you'd normally use an org.eclipse.fx.ui.di.InjectingFXMLLoader
to free you from configuring the loader with information the DI container already knows you can make use of the @org.eclipse.fx.ui.di.FXMLLoader
in your components which provides you an org.eclipse.fx.ui.di.FXMLLoaderFactory
instance.
public class MyLoginView { @PostConstruct void init(BorderPane p, @FXMLLoader FXMLLoaderFactory factory) { GridPane gridPane = factory.loadRequestorRelative("myLoginView.fxml").load(); p.setCenter(gridPane); // ... } }
and now you can use @Inject
in your controller:
public class MyLoginController { @Inject MyLoginService loginService; @FXML TextField username; @FXML TextField password; }
@Adapt
Sometimes a generic container objects like an IStructuredSelection
are pushed into the context but in your application you want to get a concrete type. So generic type conversion can be implemented using the AdapterService
introduced below. To make direct use of the adapters as part of the the DI-Process you can use the @org.eclipse.fx.core.adapter.Adapt
annotation who will try to convert the value in the context using the the AdapterService.
For the example. Let's suppose there's an adapter who is able to convert from a String
to an Integer
you could use DI in the following way
import org.eclipse.fx.core.adapter.Adapt; @Inject @Adapt @Named("myValue") Integer myValueAsInteger;
FXML
FXML in OSGi
Loading FXML-Files in OSGi is a bit harder than doing it in a standard java application and one can not use the static FXMLLoader.load()
so we provide an extra class named org.eclipse.fx.osgi.utilOSGiFXMLLoader
public class MyView { @PostConstruct public void init(BorderPane p) { Node n = OSGiFXMLLoader.load(getClass(), "myView.fxml", null, null); } }
DI in FXML controller
Eclipse DI
If you are running in OSGi and with Eclipse DI (e.g. when you are writing an Eclipse 4 Application) you can make use of org.eclipse.fx.ui.di.InjectingFXMLLoader
.
public class MyView { @PostConstruct public void initUI(BorderPane p, IEclipseContext context) { InjectingFXMLLoader<Node> iFXMLLoader = InjectingFXMLLoader.create( context, getClass(), "myView.fxml" ); Node n = iFXMLLoader.load(); // ... } }
The above code is perfectly ok although you can get rid of the direct IEclipseContext
dependency by using the @FXMLLoader annotation.
Google Guice
If you are not running on OSGi and Eclipse DI but develop a standard java application which uses Google Guice as the DI container you can make use of org.eclipse.fx.core.guice.InjectingFXMLLoader
public class MyGuiceComponent { public void init(Injector injector) { Node n = InjectingFXMLLoader.loadFXML(injector,getClass().getResource("myui.fxml")); // ... } }
Adapter System
The @ContextValue
support introduce above makes use of the adapter system provided by e(fx)clipse core runtime bundle (org.eclipse.fx.core
) which constits of the 3 main interfaces:
-
Adaptable
-
AdapterService
-
AdapterProvider
Usage
The simplest usage is if the source-object implements the Adaptable
interface
public class Sample { private void sellProduct(Person person) { Customer customer = person.adapt(Customer.class); // ... } }
If the source-object itself does not implement the Adaptable
interface or you are the one who has to implement a class which implements Adaptable
you have to use the AdapterService
whicn is provided through the OSGi-Service-Registry if the object is managed by Eclipse DI it will look like this:
public class Sample { @Inject AdapterService adapterService; private void sellProduct(Person person) { Customer customer = adapterService.adapt(person,Customer.class); // ... } }
Otherwise you need to query the OSGi-Service-Registry to get access to the AdapterService
.
Extending
To enhance the adapter system it is possible to register AdapterProvider
as OSGi-Services. All you need to do is to implement the AdapterProvider
likes this:
public class CustomerAdapterProvider implements AdapterProvider<Person, Customer> { @Override public Class<Person> getSourceType() { return Person.class; } @Override public Class<Customer> getTargetType() { return Customer.class; } @Override public boolean canAdapt(Person sourceObject, Class<Customer> targetType) { return true; } @Override public Customer adapt(final Person sourceObject, Class<Customer> targetType, ValueAccess... valueAccess) { // ... } }
and register it e.g. through DS with
<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="my.adapter.sample.customer"> <implementation class="my.adapter.sample.CustomerAdapterProvider"/> <service> <provide interface="org.eclipse.fx.core.adapter.AdapterProvider"/> </service> </scr:component>
e4
Position a TrimBar element to the far right
Sometimes it is necessary to position an element in the TrimBar to the far right or bottom. This can be implemented by adding a spacer element to the trimbar
<trimBars elementId="my.sample.trim.bottom" side="Bottom"> <children xsi:type="menu:ToolControl" > <tags>fillspace</tags> </children> <children xsi:type="menu:ToolControl" elementId="my.sample.trim.progress" contributionURI="bundleclass://my.bundle/my.bundle.ProgressControl"/> </trimBars>
Restart Service
Sometimes it is necessary to restart your application with a cleared persisted state. This could be the case when you want to reset the user interface to its default or after an update to your application.e4xmi
or a fragment.e4xmi
.
To use the RestartService
, you can inject it into your handler. To clean the persisted state on the next start of the application pass true
to the restart
method.
public class CleanRestartHandler { @Execute public void execute(RestartService restartService) { restartService.restart(true); } }