Introduction
In this study the possibility to brigde Swing components in JavaFX is investigated. Java Swing is a Java framework for the development of RCP ( Rich Client Platform ) applications. In the light of Oracle having announced, that Java FX is the new common UI framework*, it should be examined, how existing UI components of Swing can be reused in an JavaFX environment.
Current Java Version: JDK1.8.0_25
JavaFX
JavaFX is a
framework for platform independend rich client applications. Since JDK 7u6
JavaFX is part of the Java runtime and Java development kit. It is meant to
supersede Swing as Javas UI technology.
Scene Graph*
The elements of a JavaFX UI are organized in a tree structure, also
called scene graph. The JavaFX Scene Graph API makes graphical user interfaces
easier to create, especially when complex visual effects and transformations
are involved. A scene graph is a tree data structure, most commonly found in
graphical applications and libraries such as vector editing tools, 3D
libraries, and video games. The JavaFX scene graph is a retained mode API,
meaning that it maintains an internal model of all graphical objects in your
application. At any given time, it knows what objects to display, what areas of
the screen need repainting, and how to render it all in the most efficient
manner. Instead of invoking primitive drawing methods directly, you instead use
the scene graph API and let the system automatically handle the rendering
details. This approach significantly reduces the amount of code that is needed
in your application.
The individual items held within the JavaFX scene graph are known as
nodes. Each node is classified as either a branch node (meaning that it can
have children), or a leaf node (meaning that it cannot have children). The
first node in the tree is always called the root node, and it never has a
parent. See a general inheritance diagram in Figure 1.
Figure 1: Tree Structure |
Figure 2: JavaFX Scene Graph |
JavaFX Threading
The
elements of a JavaFX scene graph are managed in the so called JavaFX Application
Thread. This means, that changes, e.g. displaying a text in a text field, are
processed by the JavaFX Application Thread. If a long running calculation is
being processed in another thread and the result should be displayed at the
JavaFX UI, is has to be delivered back to the JavaFX Application Thread. For
this, JavaFX provides the static method javax.application.Platform.runLater(
Runnable ). The commands within the
run() method of the Runnable argument are being processed at the JavaFX
Application Thread.
Example 1 shows a
background thread delivering a calculated result back to the JavaFX UI. If the
background thread calles the TextField directly, JavaFX will throw an java.lang.IllegalStateException: Not on FX
application thread.
public class JavaFxBackgroundCalculator
extends Application{
private
TextField textField;
public
static void main(String[] arg0){
Application.launch(arg0);
}
@Override
public
void start(Stage stage) throws Exception {
Button
button = new Button("Calculate a while");
button.setOnAction(new
EventHandler<ActionEvent>() {
public
void handle(ActionEvent evt) {
calculate();
}
});
textField
= new TextField();
HBox
hBox = new HBox();
hBox.setPadding(new
Insets(10));
hBox.getChildren().add(textField);
hBox.getChildren().add(button);
Scene
scene = new Scene(hBox);
stage.setScene(scene);
stage.show();
}
private
void calculate(){
Runnable
calculation = new Runnable() {
public
void run() {
int
duration = 1000; // 10 sec
for(int
i = 0; i <= 10; i++){
String
message = duration / 100.0 * i + "%";
//back
to JavaFX Application Thread
Platform.runLater(new
Runnable() {
public
void run() {
textField.setText(message);
}
});
try
{
Thread.sleep(100);
}
catch (InterruptedException e) {
throw
new RuntimeException(e);
}
}
}
};
new
Thread(calculation).start();
}
}
Example 1
For a convenient usage of background processes, JavaFX provides the package javafx.concurrent. It contains implementations of the known Worker, FutureTask und Services*, which are bridging to the JavaFX Application Thread internally.
Embedding Swing in JavaFX*
JavaFX 8 introduces the SwingNode class, which is located in the
javafx.embed.swing package. This class is a JavaFX Node implementation and can
be added to the Scene Graph as any other Node object. SwingNode enables the
embedding of Swing content in a JavaFX application. To specify the content of
the SwingNode object, the setContent method must be called, which accepts an
instance of the javax.swing.JComponent class. The setContent method can be
called on either the JavaFX application thread or event dispatch thread (EDT).
However, to access the Swing content, it must be ensured that the code runs on
EDT, because the standard Swing threading restrictions apply.
The code shown in Example 2 illustrates the general pattern of using the
SwingNode class.
import
javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
public class SwingFx extends Application {
@Override
public void start (Stage
stage) {
final SwingNode swingNode
= new SwingNode();
createSwingContent(swingNode);
StackPane pane = new
StackPane();
pane.getChildren().add(swingNode);
stage.setTitle("Swing
in JavaFX");
stage.setScene(new
Scene(pane, 250, 150));
stage.show();
}
private void
createSwingContent(final SwingNode swingNode) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
swingNode.setContent(new JButton("Click me!"));
}
});
}
}
When
executed, this code produces the output shown in Figure 3.
Figure 3: SwingButton In JavaFx |
Interoperability between JavaFX and Swing
When
combining JavaFX and Swing in one Application, it has to be minded, that both
components run in their own thread. Should a Swing component be called from
JavaFX it has to happen in the Swing thread. The other way round a JavaFX has
to be called from Swing withing the JavaFX Application Thread. Example 3 shows
the mutual call of JavaFX to Swing and vice versa using javafx.application.Platform.runLater
( Runnable ) and javax.swing.SwingUtilities.invokeLater ( Runnable ).
public class SwingInFx extends
Application {
private
TextField fxTextField;
private
JTextField swingTextField;
public
static void main(String[] args) {
launch(args);
}
@Override
public
void start(Stage stage) {
HBox
box = new HBox();
createFXContent(box);
SwingNode
swingNode = new SwingNode();
createSwingContent(swingNode);
GridPane
pane = new GridPane();
pane.add(box,
1, 1);
pane.add(swingNode,
1, 2);
stage.setTitle("Swing
in JavaFX");
stage.setScene(new
Scene(pane, 250, 150));
stage.show();
}
private
void createFXContent(HBox box) {
fxTextField
= new TextField("FX text");
Button
fxButton = new Button("Click FX");
fxButton.setOnAction(new
EventHandler<javafx.event.ActionEvent>() {
public
void handle(javafx.event.ActionEvent evt) {
setSwingText("FX
clicked");
}
});
box.getChildren().add(fxTextField);
box.getChildren().add(fxButton);
}
private
void createSwingContent(final SwingNode swingNode) {
SwingUtilities.invokeLater(new
Runnable() {
@Override
public
void run() {
swingTextField
= new JTextField("Swing Text");
JButton
swingButton = new JButton("Click Swing");
swingButton.addActionListener(new
ActionListener() {
public
void actionPerformed(java.awt.event.ActionEvent e) {
setFXText("Swing
clicked");
}
});
JPanel
panel = new JPanel(new GridLayout(1, 2));
panel.add(swingTextField,
BorderLayout.NORTH);
panel.add(swingButton,
BorderLayout.NORTH);
swingNode.setContent(panel);
}
});
}
private
void setFXText(String text) {
Platform.runLater(new
Runnable() {
public
void run() {
fxTextField.setText(text);
}
});
}
private
void setSwingText(String text) {
SwingUtilities.invokeLater(new
Runnable() {
public
void run() {
swingTextField.setText(text);
}
});
}
}
When
run, this code produces the output shown in Figure 4.
Figure 4: JavaFX Swing Interoperability |
This
application consists of two rows. The upper one consist of JavaFX components,
the lower one consists of Swing components. Clicking on the JavaFX button will
adjust the Swing textfield and vice versa .
However, since two threads are
running in this application, the rendering of the UI works not properly.
Usually, right after
the start the swing components are not yet rendered, as shown in Figure 5
There
has to be some event first, which initiates the swing grafical renderer, like a
mouse move over the swing button or a mouse click on the swing text field. To
avoid such behaviour, a single thread option was build into JavaFX since
version 8*. Starting the application with vm argument
-Djavafx.embed.singleThread=true should execute all swing command on the JavaFX
application thread. This feature is still experimental and not yet officially
documentent. Also it seems not do what it promises. Starting the same sample
application with that option: java -Djavafx.embed.singleThread=true
de.psi.pen.java8.test.SwingInFx will cause no rendering at all as shown in the
next figures. After dragging the application window, causing a rerendering,
only the swing components come to light.
Figure 6: Swing in JavaFX - SingleThread 1 |
Figure 7: Swing in JavaFX - SingleThread 2 |
Embedding JavaFX in Swing
JavaFX
provides also a way of being integrated into Swing applications. The javafx.embed.swing.JFXPanel
is a javax.swing.JComponent implementation, which can be added any swing/awt
java.awt.Container, like Panels, Windows, Frames or JDialogs. For JavaFX the
JFXPanel acts as a Stage, at which the Scene, containing the Scene Graph, must
be set. Example 4 shows how JavaFX content can be embedded into a Swing
application. Also in this case the different threads must be kept in mind. Any
adjustments to JavaFX components must happen within the JavaFX Application
Thread.
public class FxInSwing {
private
static void initAndShowGUI() {
//
This method is invoked on the EDT thread
JLabel
swingLabel = new JLabel("This is Swing");
JFXPanel
fxPanel = new JFXPanel();
Platform.runLater(new
Runnable() {
public
void run() {
Scene
scene = createScene();
fxPanel.setScene(scene);
}
});
Panel
panel = new Panel(new BorderLayout());
panel.add(swingLabel,
BorderLayout.NORTH);
panel.add(fxPanel,
BorderLayout.CENTER);
JFrame
frame = new JFrame("Swing Application");
frame.add(panel);
frame.setSize(300,
200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private
static Scene createScene() {
Group
root = new Group();
Scene
scene = new Scene(root, Color.ALICEBLUE);
Text
text = new Text();
text.setX(40);
text.setY(100);
text.setFont(new
Font(25));
text.setText("Welcome
JavaFX!");
root.getChildren().add(text);
return
(scene);
}
public
static void main(String[] args) {
SwingUtilities.invokeLater(new
Runnable() {
@Override
public
void run() {
initAndShowGUI();
}
});
}
}
Example 4
When
executed, this code produces the output shown in Figure 8.
Figure 8: JavaFX in Swing |
Conclusion
Swing
components can be easily integrated into JavaFX applications with javafx.embed.swing.SwingNode.
Like in any other Java application, the Swing component must be called within
the Swing thread through SwingUtilities.invokeLater. The SwingNode, as part of
JavaFXs Scene Graph, is responsible for rendering its contained Swing component
and forwardes all input and focus events from the JavaFX UI to the contained
JComponent. However, the documentain states, that no heavyweight components
should be contained in a SwingNode, because rendering problems might occur*. How much weight a Swing component must gain
until display issues can be expected is shown in Example 3: It can happen right
from the beginning! Besides the official Oracle documentation, not much
feasible experience can be found on the web at the moment, and few blogs rather
tend to advise, not to use this feature at the moment*. The fact, that Oracle is still dealing with
threading issues ( SingleThread experiment) between the two worlds should be
minded when more effort into Swing/JavaFX bridging is being considered.
0 Kommentare:
Kommentar veröffentlichen