JavaFX Findings
JavaFX and Eclipse
The plugin
e(fx)clipse can be very helpful for JavaFX development. It also helps with the problem of Eclipse marking the JavaFX packages as a restricted API, leading to warnings or errors.
Eclipse may hang/crash when trying to debug code in or called from JavaFX event handlers with breakpoints. If this happens, use the VM param
-Dsun.awt.disablegrab=true when debugging with breakpoints! See
http://stackoverflow.com/questions/27863245/ide-hangs-in-debug-mode-on-break-point-in-java-fx-application and
http://bugs.java.com/view_bug.do?bug_id=6714678 for more information.
Note that even with the VM param, debugging drag & drop / mouse dragging might still cause crashes. You may have to debug those scenarios with sysouts...
Integrating Swing and JavaFX
Swing and JavaFX can be integrated: You can use a JFXPanel in Swing to include JavaFX content in Swing, and you can use SwingNode in JavaFX to include Swing content in JavaFX. However, some issues arise, mainly relating the levels of nesting, window modality, and handling the two separate GUI event threads.
Levels of nesting
First-Level nesting
JavaFX in Swing
- Modal JavaFx dialogs in Swing do not "act" modal ( see below). Modal Swing dialogs work fine when explicit GUI thread switching is used ( see below).
- In general, including JavaFX content in Swing with JFXPanel appears to be working fine. It should therefore be possible to create new GUI elements in JavaFX and include them in existing Swing applications.
Swing in JavaFX
- Placing any Swing content (even empty Swing Panels) in SwingNodes leads to performance issues: Adding any Swing content to a SwingNode and clicking it leads to a continuous, endless filling of the heap accompanied by increased CPU load. The heap usage is not a memory leak, as the objects placed on the heap can (and will) be garbage collected regularly, but the applications is very busy and continually uses more heap/CPU than actually necessary. (This issue is probably identical with the issue described in https://bugs.openjdk.java.net/browse/JDK-8136530 ).
- Modal Swing dialogs in FX do not "act" modal ( see below). Modal JavaFx dialogs work fine both for explicit GUI thread switching and the VM param for using a single GUI thread ( see below).
Second-Level nesting and beyond
Nesting JavaFX and Swing inside each other for more than one level seems not advisable as several issues were found.
Swing in JavaFX in Swing - Using a SwingNode inside a JFXPanel in a Swing application
JavaFX in Swing in JavaFX: Using a JFXPanel inside a SwingNode in a JavaFX application
Handling the two GUI threads
Both Swing and JavaFX work with their own GUI event thread. Both GUI toolkits are supposed to be only used by invoking all their methods on their own GUI thread. Swing does not check for this and often tolerates being accessed from other threads. JavaFX however checks for correct thread usage and will often (though not necessarily always) throw Exceptions if JavaFX is accessed from outside the GUI thread. This has to be taken into account whenever the two toolkits are mixed.
Shared resources
Sharing resources between the GUI threads is best avoided. However, when it is necessary to share resources (for example models), it is important to consider two issues: Avoiding concurrent modifications of data (solvable by synchronized access methods to the model etc.) and making sure the correct GUI thread is used for manipulating the GUI based on the data. The latter is done by
explicitly switching between the GUI threads.
Note: If you bind JavaFX GUI controls directly to JavaFX's Observable Collections in models, those observable collections must only be manipulated from the JavaFX thread.
This means that if you need to manipulate these resources from the Swing thread, you must perform the thread switch. As there is no way to wait for the Fx thread to return (see
below), you cannot assume that the changes to the shared resources have already been applied when you go on working in the Swing thread.
Explicit GUI thread switching
Code can be run on the Swing GUI thread (Swing Event Dispatch Thread, EDT) by
EventQueue.invokeLater( () -> { // code to run on Swing thread });
EventQueue.invokeAndWait( () -> { // code to run on Swing thread });
Code can be run on the JavaFX GUI thread (JavaFX Application Thread) by
Platform.runLater( () -> { // code to run on JavaFX thread });
There is
no Platform.runAndWait() method for JavaFX so far. It is therefore not possible to wait in Swing for something to finish initializing etc. on the JavaFX thread and then work with the definitely fully initialized JavaFX resource.
It is also important to note that Platform.runLater() can only be used when the JavaFX runtime has been initialized.
- In a JavaFX application, this is a given from the start.
- In a Swing application, the JavaFX runtime is initialized when the first JFXPanel() is created. It is possible to run into issues when a JFXPanel() is shown and then completely thrown away - this may exit the JavaFX runtime, and trying to create another JFXPanel can lead to exceptions. If that is the case, use Platform.setImplicitExit(false) to avoid the FX runtime exit. Then, to make sure the FX runtime correctly exits when the application exists, explicit calls to Platform.exit() must be performed with any actions that close the application.
Some resources/methods allow usage from other threads than the JavaFX thread (this is usually stated explicitly in the documentation) - if that is the case, thread switching is not necessary. For example, the SchemaWidget 's SchemaWidgetPresenterImpl can be created on any thread. In general,
JavaFX Nodes can be created on any thread, but adding them to a Scene or manipulating nodes already in a Scene must be done on the JavaFX thread.
VM Param for combining the GUI threads
There is an experimental VM parameter that merges the two GUI threads into one:
-Djavafx.embed.singleThread=true
When using this param, JavaFX will not throw any Exceptions regarding wrong thread usage when JavaFX functionality is called from either the JavaFX or the Swing GUI thread. It
is important to make sure you are on one of those two threads though! Existing Swing applications can be working correctly without explicitly switching to the Swing GUI thread at application startup, but JavaFX
will notice when used from a non-GUI thread and throw Exceptions. Solution: Explicitly switch to one of the GUI threads at application startup.
Using this param work well for tests with JavaFX applications that included Swing content.
For
Swing applications with included JavaFX content, the param appeared to be working well except for one big issue: there is
no way to create a truly modal dialog (
see below)
.
Dialog Modality
When including content of one GUI toolkit within an application of the other toolkit,
only modal dialogs belonging to the toolkit used for the application work correctly. The modal dialogs belonging to the included toolkit usually do not fulfill the modality expectations (modal dialogs should stay in the foreground and block the whole application (which includes that closing the application with the window's X-Button should not work).
Swing Application with included JavaFX content
Modal dialogs show different behaviour depending on whether explicit GUI thread switching or the VM param is used:
Explicit GUI Thread Switching
- Modal Swing dialogs: work correctly (in foreground, whole application blocked, can not close application while dialog is showing)
- Modal Fx Dialogs: Not in foreground, only Fx part of the application is blocked (Swing part of the application is not blocked), closing of application is possible
Using the VM param for single GUI thread
- Modal Swing dialogs: in foreground, application is not blocked, closing of application is possible
- Modal Fx Dialogs: (same as for explicit thread switching)
There seems to be no report of this changed behaviour for using the VM param anywhere. Guesswork: The reason why it is not working is possibly because modality in Swing is achieved by blocking the original "event pump" and creating a new one, which seems to happen in a new thread. When the GUI threads are merged, the event pump blocking does not happen or does not work correctly, so the events for the GUI areas that should be blocked are still propagated.
JavaFX Application with included Swing content
Both when using explicit GUI thread switching or the VM param, modal dialogs always show the same behaviour:
- Modal Swing dialogs: Not in foreground, only Swing part of the application is blocked (Fx part of the application is not blocked), closing of application is possible but Swing dialog stays open until closed
- Modal Fx Dialogs: work correctly (in foreground, whole application blocked, can not close application while dialog is showing)
Using the SceneBuilder
SceneBuilder is a tool to visually create JavaFX GUIs, creating FXML files for the GUIs. Oracle does not directly support SceneBuilder anymore and does not provide installers for new versions.
Installers for the old versions 1.1 and 2.0 can be found on the
Oracle website , but those old versions do not support all of the JavaFX features present in Java 8.
Gluon provides newer versions for SceneBuilder.
On the asl74x cluster, SceneBuilder executables can be found at /common/usr/cscoap/opt/javafxscenebuilder/ . You can directly start SceneBuilder by typing "javafxscenebuilder" in the console.
Using SceneBuilder from Eclipse
In order to open FXML files directly from Eclipse by right-clicking and choosing "Open with SceneBuilder " from the context menu, install the
e(fx)clipse plugin in Eclipse (and make sure SceneBuilder in installed on your development machine). Then go to Window -> Preferences -> JavaFX and provide the path to the executable: See
Eclipse Configuration for the current path to SceneBuilder.
Issues with SwingNodes in FXMLs
If an FXML file contains a SwingNode, that file cannot be opened by SceneBuilder instances that were created from Eclipse. Both opening SceneBuilder from Eclipse and then trying to open the FXML file in SceneBuilder, and trying to open the FXML file from Eclipse with SceneBuilder, result in silent crashes of SceneBuilder.
-
Workaround 1: A separate SceneBuilder instance (openend not from Eclipse, but from outside) can usually open the file.
-
Workaround 2: Avoid including SwingNodes in the FXML files. Instead, create them programmatically in Java and add them to the container node specified in the FXML.
Adding custom components to SceneBuilder
If you want to use custom components, for example the SchemaWidget, in SceneBuilder, you have to import their JAR files and (!) possibly JARs from their dependecies.
- Open SceneBuilder. In the "Library" area (left), click the little gear symbol next to the search field. Choose "JAR/FXML Manager".
- Choose "Add Library/FXML from file system". Enter the path to your component's JAR file (the JAR created by Maven).
- AW: I have tried to add JARs by A) searching repositories or B) using "Manually add library from repository". For A), I could see the artifacts, but could not import them. For B), I managed to import a JAR that does not contain any custom components - but the import failed for a JAR with a custom component. Not sure what is going wrong there.
- You should now see the Import Dialog.
- If you see your custom component(s) in the list on the left: Good! Check their respective checkboxes and choose "Import Component". The custom component(s) should now show up under the "custom" section of the "Library" area.
- If you do not see your custom component(s) in the list on the left, SceneBuilder could not "see" the component(s). You probably need additional JAR imports. Hit either "Cancel" or "Import JAR".
- Check the imports in your custom components' class files / FXMLs to determine which classes from other JARs you are referencing. Then, import those JARs exactly like described for adding the JAR of a custom component. The list on the left of the Import Dialog will probably be empty for them, that is ok. Hit "Import JAR".
- Once you have imported all referenced JARs, try to import the JAR of the custom component(s) again. If you hit "Import JAR" before, you can simply click the edit symbol for this JAR in the "JAR/FXML Manager" to open the Import Dialog. The custom component(s) should now show up in the list on the left.
Once you have imported the JAR files into SceneBuilder, you should be able to open FXML files containing custom components both from Eclipse and in separate SceneBuilder instances.
Ideas for possible good practices
Integrating Swing and Fx
Use explicit thread handling
As the
VM param causes window modality issues and its use is not officially recommended, we should probably resort to explicit thread handling when mixing the Swing and JavaFX toolkits.
General advice: Avoid having Swing and Fx components influence each other when possible! Thread handling gets much easier if each GUI component either influences a GUI component of the other toolkit,
or is influenced by a GUI component of the other toolkit, but does not do both.
Example scenario to avoid: A Swing JList and a JavaFx ListView both show the same items and you can interact with both lists. When the selection in one list changes, the other list must match that selection: The lists
influence each other. This situation needs careful thread handling to make sure both lists show the same selection without deadlocking or getting into an infinite update cycle. The latter could happen without proper synchronization.
Example scenario that is ok: A Swing JList and a JavaFx TableView both show the same items. When the selection in the list changes, an other row in the table gets highlighted. There is no way to change the selection/highlighting by interacting with the table. This is okay: The Swing list influences the JavaFX table, but not the other way around. Thread handling is relatively easy, as there is no need for synchronization: The list never has to wait for the table to update its highlighting.
In order to use JavaFx's
Platform.runLater(Runnable) for explicit thread handling, the Fx toolkit must already be fully initialized.
In JavaFx applications, this happens automatically when the class that extends Application is constructed.
In Swing applications, the Fx toolkit is only initialized when the first JFXPanel is constructed. This means that we must first create a JFXPanel before we ever call
Platform.runLater(Runnable)
. It would probably be easiest to simply create a JFXPanel and throw it away when the application starts.
Enable access to embedding wrapper components/nodes
Sometimes when integrating components of the two toolkits with each other, it can be helpful for an Fx node to know the JFXPanel which wraps it, or, vice versa, for a Swing component to know which SwingNode contains it.
When an Fx node is embedded in a JFXPanel, there is no way to access said JFXPanel from the Fx node. An easy way to provide access would be to store a property referencing the JFXPanel in the JFXPanel's Scene: The Scene and its properties can be easily retrieved by any Fx node in the Fx content.
String EMBEDDING_JFXPANEL_PROP_KEY = "foo.bar.embeddingJFXPanel";
Node fxContent = ...;
Scene scene = new Scene(fxContent); // create scene
// add content in scene to JFXPanel
JFXPanel jfxPanel = new JFXPanel();
jfxPanel.setScene(scene);
scene.getProperties().put(EMBEDDING_JFXPANEL_PROP_KEY, jfxPanel);
...
// somewhere in the fx content
JFXPanel embeddingJfxPanel = (JFXPanel) someFxNode.getScene().getProperties().get(EMBEDDING_JFXPANEL_PROP_KEY);
In the same way, a Swing component in a SwingNode cannot access the SwingNode. A similar workaround can be applied by using a wrapper component as a "root component" with a specified name to store a property referencing the SwingNode:
String ROOT_COMPONENT_NAME = "foo.bar.rootComponentName";
String EMBEDDING_SWINGNODE_PROP_KEY = "foo.bar.embeddingJFXPanel";
JComponent swingContent = ...;
// create root component wrapper
JComponent rootComponent = new JPanel();
rootComponent.add(swingContent);
rootComponent.setName(ROOT_COMPONENT_NAME);
// add content in root wrapper to SwingNode
SwingNode swingNode = new SwingNode();
swingNode.setContent(rootComponent);
rootComponent.putClientProperty(EMBEDDING_SWINGNODE_PROP_KEY, swingNode);
...
// somewhere in Swing content
SwingNode embeddingSwingNode = (SwingNode) ( ( (JComponent) SwingUtilities.getAncestorNamed(ROOT_COMPONENT_NAME, someSwingComponent) ).getClientProperty(EMBEDDING_SWINGNODE_PROP_KEY) );
Use bindings to automatically adapt text to a language change
JavaFX GUIs can automatically adapt their displayed texts to different Locales when they are loaded from FXML. To change the application language on the fly while the application is running, the whole GUI would have to be reloaded. This might not be desired.
An alternative to reloading the whole GUI from FXML is presented at
http://stackoverflow.com/questions/32464974/javafx-change-application-language-on-the-run : All texts of controls are bound to custom StringBindings that are automatically updated whenever the language/Locale is changed. Simple examples that show how this could be used with the LanguageTranslator can be found in the Scheduling App SVN repository (revision 657).
--
AnnekeWalter