Über unsMediaKontaktImpressum
Alexander Casall & Stefan Saring 28. Juli 2015

Swing und JavaFX – Migration und Rundumerneuerung?

Alte Swing-Anwendungen, die seit Jahren in Produktion sind und weiterhin gepflegt werden müssen, sind keine Seltenheit. Dabei wird die Wartung der historisch gewachsenen Software immer ineffizienter und neu geforderte Features sind umständlich zu implementieren – wenn es überhaupt möglich ist, sämtliche Kundenanforderungen mit der in die Jahre gekommenen Technologie umzusetzen.

Die Gründe für die Modernisierung einer Swing-Anwendung sind vielfältig. Wenn die Entscheidung gefallen ist, die Anwendung mit JavaFX zu erneuern, stellt sich natürlich die Frage, ob es dazu technische Hilfsmittel seitens JavaFX gibt. Diese Hilfsmittel gibt es. Zum einen können JavaFX-Komponenten in Swing, zum anderen können Swing-Komponenten in JavaFX eingebettet werden. Wir stellen zwei Werkzeuge für unterschiedliche Migrationsansätze vor.

Möglichkeit 1 – JavaFX in Swing

Mit der Komponente javafx.embed.swing.JFXPanel ist es möglich, JavaFX-Inhalte in eine bestehende Swing-Anwendung zu integrieren. An dieser Stelle gibt es zwei Anwendungsfälle:

  1. Schrittweise Erneuerung der Anwendung von innen heraus
    Ein Beispiel dafür wäre: Zunächst werden alle Dialoge erneuert, anschließend die restlichen Bereiche und abschließend wird der alte Swing-Anwendungsrahmen auf JavaFX umgestellt. An dieser Stelle sei bereits erwähnt: Dies ist nicht trivial, erhält jedoch die Release-Fähigkeit der Anwendung.
  2. Neue Features mit JavaFX
    Sollten neue Features zur Anwendung hinzukommen, lohnt es sich nicht mehr, diese mit der alten Technologie umzusetzen, da die Migration sowieso ansteht. Um an der Stelle die Investitionen zu sichern, lohnt eine direkte JavaFX-Implementierung, um zur eigentlichen Migration nicht unnötig Umstellungsaufwand zu erzeugen.

Kommen wir nun zu einem konkreten Beispiel. Im Folgenden ist eine Swing-Anwendung programmiert, welche einen javax.swing.JButton und einen javafx.scene.control.Button untereinander anzeigt. Es wird also JavaFX-Content in Swing eingebettet.


public class SwingFrame extends JFrame {
    
    public SwingFrame() {
        super("Swing Frame");
        setLayout(new GridLayout(0, 1));
        JButton swingButton = new JButton("Swing Button");
        
        // Create JavaFX Container
        JFXPanel jfxPanel = new JFXPanel();
        
        // Switch to JavaFX Thread to create JavaFX content
        Platform.runLater(() -> {
            Button btJavaFX = new Button("JavaFX Button");
            jfxPanel.setScene(new Scene(btJavaFX));
            SwingUtilities.invokeLater(() -> {
                // Switch to Swing Thread, because we need to layout all stuff, when the JavaFX content loaded
                    pack();
                    setVisible(true);
                });
        });
        
        getContentPane().add(swingButton);
        getContentPane().add(jfxPanel);
    }
    
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new SwingFrame());
    }
}

Das Beispiel zeigt, welche Hürden zu meistern sind: Sowohl JavaFX als auch Swing verwenden einen eigenen UI-Thread. Änderungen an der jeweiligen UI müssen aus dem jeweiligen Thread getätigt werden, sonst können Layouting-Probleme entstehen bzw. Exceptions (siehe im Folgenden) auftreten.

java.lang.IllegalStateException: Not on FX application thread

Um dies zu umgehen, bieten beide Toolkits die Möglichkeit, von einem nicht-UI-Thread aus Operationen im UI-Thread auszuführen. Dabei gibt es zwei Wege:

Swing

SwingUtilities.invokeLater(() -> //Runnable with operations regarding Swing UI)

JavaFX

Platform.runLater(() -> //Runnable with operations regarding JavaFX UI)

Bei beiden Aufrufen wird ein Runnable übergeben, welches an das Ende der jeweiligen Event- Queue gehängt und anschließend im UI-Thread ausgeführt wird. Im Beispiel fällt auf, dass mehrmals zwischen den Threads hin und her gesprungen wird. Dadurch werden Layout-Probleme umgangen, denn #pack() wird erst nach dem Erstellen des JavaFX-Contents aufgerufen. Sollte #pack() nicht nach dem Erstellen des JavaFX-Contents aufgerufen werden sondern früher, wären die Bounds des JavaFX-Contents noch nicht berechnet und würden damit auch nicht in die Layout-Berechnung einbezogen werden. Das Resultat ist ein zu kleines Anzeigefenster.

Die nächste Herausforderung ist im Screenshot zu erkennen. Es fällt ein Bruch im Look & Feel auf – ein Problem, das wiederum nicht einfach zu lösen ist. Eine Möglichkeit ist, JavaFX-Komponenten mit CSS dem Swing-L&F anzunähern.

Möglichkeit 2 – Swing in JavaFX

Das javafx.embed.swing-SwingNode kommt zum Einsatz, wenn das Anwendungsgerüst bereits auf JavaFX umgestellt ist, jedoch bestimmte Alt-Komponenten weiterhin verwendet werden. Ein Beispiel dafür ist eine komplexe JTable die Excel nachempfindet und im ersten Migrationsschritt aus Zeitgründen noch nicht neu implementiert werden soll.

Kommen wir wieder zu einem konkreten Beispiel. Ähnlich wie beim letzten Beispiel, handelt es sich um eine JavaFX-Anwendung, die einen javax.swing.JButton und einen javafx.scene.control.Button darstellt. An dieser Stelle wird also Swing in JavaFX eingebettet.


public class JfxApplication extends Application {
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        Button label = new Button("JavaFX");
        SwingNode swing = new SwingNode();
        
        SwingUtilities.invokeLater(() -> {
            JButton jLabel = new JButton("Swing");
            swing.setContent(jLabel);
            Platform.runLater(() -> {
                VBox vBox = new VBox(label, swing);
                Scene scene = new Scene(vBox);
                primaryStage.setScene(scene);
                primaryStage.show();
            });
        });
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}        

Die Herausforderungen sind dieselben wie gehabt – Es muss darauf geachtet werden, wie zwischen den Threads kommuniziert wird und auch die L&F-Probleme sind wieder offensichtlich.

Fazit

Mit SwingNode und JFXPanel können Swing und JavaFX verheiratet werden. Entgegen dem echten Leben sollte diese Vereinigung jedoch nur temporär sein. Das eigentliche Ziel ist es nicht, einen möglichst komplexen Mix aus Swing und JavaFX zu erzeugen. Ziel ist es, die Technologien nur punktuell zu verknüpfen und die Altanwendung zeitnah komplett mit JavaFX abzulösen.

Autoren

Alexander Casall

Alexander Casall veröffentlicht Artikel in Fachzeitschriften, hält Vorträge auf Konferenzen (JavaOne, JAX, W-JAX) und ist bei User Groups aktiv.
>> Weiterlesen

Stefan Saring

Stefan Saring ist Software-Architekt und Entwickler. Seit 15 Jahren arbeitet er vorwiegend in Projekten aus dem Java-Standard- und Enterprise-Bereich.
>> Weiterlesen
botMessage_toctoc_comments_9210