Aus alt mach neu

Wie man Swing-Anwendungen mit JavaFX modernisiert

Alexander Casall, Stefan Saring

Mit JavaFX hat Oracle ein zeitgemäßes UI-Toolkit für Desktop und Embedded platziert. FXML, CSS oder auch das Property-API sind nur einige Beispiele der nützlichen Features, die bei der Entwicklung moderner Oberflächen unterstützen. Spätestens seit Java 8 ist Swing keine Option mehr für neue Projekte, und die Vorteile von JavaFX überwiegen deutlich. Wie sieht es jedoch bei bestehenden Swing-Projekten aus, die gewartet und stetig weiterentwickelt werden?

Vor allem aus der Sicht der Entscheider stellt sich am Anfang stets die Frage: Ist denn die Migration zu JavaFX wirklich nötig, und was rechtfertigt den Aufwand? Diese Frage kann aus verschiedenen Blickwinkeln beantwortet werden. Zum einen gibt es den produktstrategischen Blickwinkel: Ist man durch Marktfaktoren bzw. Kundenanforderungen gezwungen, seine Anwendung zu modernisieren? Zum anderen gibt es den technischen Blickwinkel: Können durch das modernere JavaFX Toolkit mit Features wie Properties API, FXML und CSS die Wartbarkeit der Anwendung erhöht und die Weiterentwicklung vereinfacht werden? Dies sind nur zwei der möglichen Argumente für eine Migration. Oracle selbst liefert eine passende Antwort in den offiziellen JavaFX FAQ: „Swing wird auf absehbare Zeit Bestandteil des JRE bleiben, neue Anwendungen sollen jedoch möglichst immer mit JavaFX umgesetzt werden. Existierende Swing-Anwendungen können mit JavaFX erweitert werden, dies ermöglicht eine sanfte und fortlaufende Umstellung.

In den letzten Major Releases von Java wurde zudem bewiesen, dass Oracle an Swing kaum noch weiterentwickelt. Mit Java 7 wurden lediglich das Nimbus Look-and-Feel und die JLayer-Komponente eingeführt, obwohl ein akuter Bedarf an modernen Toolkit-Funktionalitäten besteht. In Java 8 wurde im Wesentlichen nur die Integration von Swing und JavaFX verbessert, damit in existierenden Projekten der Schritt zu JavaFX erleichtert wird.

Auch in der Community ist der Wandel gut zu beobachten. Vormals wichtige Swing-Bibliotheken wie z. B. das SwingX-Projekt verlieren die Entwickler und werden kaum weiterentwickelt, neue Komponenten entstehen selten. Stattdessen erscheinen zahlreiche neue JavaFX-Bibliotheken und Frameworks, die vor allem typische, noch fehlende Komponenten und eine erweiterte Funktionalität bereitstellen. Ein führendes Beispiel ist das ControlsFX-Projekt, das sich bereits in verschiedenen Projekten bewährt hat.

Auf den Umstieg können sicherlich Anwendungen verzichten, die lediglich gewartet und kaum weiterentwickelt werden. Alle anderen Projekte sollten zumindest einen mittel- oder langfristigen Umstieg in Betracht ziehen.

Vorbedingung

Da JavaFX mittlerweile fester Bestandteil der Java-Distribution (JRE und JDK) ist, muss sichergestellt werden, dass im Projekt die aktuelle Java-Version 8 eingesetzt werden kann. Erst mit diesem Release wurden einige grundlegende Komponenten bereitgestellt (z. B. Datumsauswahl, Baumtabellen, Druck-API). Vor allem wurde jedoch mit SwingNode eine für die Migration wichtige Komponente eingeführt (dazu später mehr).

Noch besser ist der Einsatz von Java 8 ab Update 40, mit dieser Version sind weitere wichtige Steuerelemente wie z. B. Dialoge und Benachrichtigungen zum Toolkit hinzugekommen. Dass ein Umstieg auf Java 8 zahlreiche weitere Vorteile (z. B. Lambdas oder Date and Time API) mit sich bringt, muss an dieser Stelle nicht erneut erläutert werden.

Aus Sicht der Architektur muss vor der Migration geprüft werden, ob in der Swing-Anwendung bereits die Geschäftslogik und die Benutzeroberfläche strikt voneinander getrennt sind. Nur die Teilung in separate Module ermöglicht die schrittweise Umstellung einzelner Funktionen bzw. Oberflächen auf JavaFX und das Entfernen der dann nicht mehr benötigten Swing-basierten Komponenten. Ist das nicht der Fall, so wird zunächst zwingend ein entsprechendes Refactoring der Anwendung empfohlen. Unabhängig von der Migration wird dadurch auch die Qualität der Software gesteigert. So kann z. B. die Geschäftslogik nun wesentlich einfacher automatisiert getestet, in ein Backend extrahiert oder in anderen Projekten wiederverwendet werden.

Als letzte Vorbedingung muss geklärt werden, nach welchem Konzept die Migration der Anwendung erfolgen soll. Zur Auswahl stehen die folgenden Möglichkeiten, die im Rahmen dieses Artikels vorgestellt werden:

  1. Schrittweise Umstellung der existierenden Anwendung auf JavaFX-Komponenten
  2. Erstellung einer neuen JavaFX-Anwendung mit Komponenten der Swing-Anwendung
  3. Komplette Neuentwicklung als JavaFX-Anwendung

UI-Threads

Unabhängig vom gewählten Konzept ist zunächst das Grundverständnis für UI-Threads nötig. Wie die meisten UI-Frameworks (egal ob für Java oder nicht) benutzen sowohl Swing als auch JavaFX einen eigenen Thread für sämtliche Operationen an der Benutzeroberfläche. Dadurch wird vor allem sichergestellt, dass in nebenläufigen Anwendungen keine Probleme beim gleichzeitigen Zugriff auf die Oberfläche entstehen. Der Entwickler muss stets alle langwierigen Aufgaben in eigenen Threads ausführen, die Darstellung der Ergebnisse und die Interaktion mit dem Benutzer müssen jedoch immer im UI-Thread erfolgen. Andernfalls blockieren die aufwändigen Operationen das UI. Die Folge sind zähe und nicht reagierende Oberflächen, was fälschlicherweise häufig dem Mythos „Java ist langsam“ zugerechnet wird.

Leider benutzen Swing und JavaFX getrennte UI-Threads für deren Toolkits. In Swing ist es der Event Dispatching Thread (kurz EDT), in JavaFX wird er nur Application Thread genannt. Egal welche der vorhin genannten Migrationsvarianten gewählt wird, bei der gemeinsamen Nutzung von Swing und JavaFX innerhalb einer Oberfläche muss stets auf die Benutzung des korrekten UI-Threads geachtet werden. Der Zugriff auf Swing-Komponenten ist nur aus dem Swing EDT heraus möglich, auf JavaFX-Komponenten darf nur aus dem JavaFX Application Thread zugegriffen werden!

Für den Zugriff auf Swing-Komponenten aus anderen Threads als dem EDT bietet die Klasse SwingUtilities die Methode invokeLater(Runnable) an, das übergebene Runnable wird asynchron zum nächstmöglichen Zeitpunkt im Swing EDT ausgeführt. Beispiel:

SwingUtilities.invokeLater(new Runnable() {
   @Override
   public void run() {
       swingLabel.setText("Task completed");
   }
});

Ziemlich umständlich, aber dank der neuen Lambdas in Java 8 wird daraus meist ein Einzeiler:

Platform.runLater(() -> fxLabel.setText("Task completed"));

Beim Zugriff auf JavaFX-Komponenten aus dem falschen Thread wird meist eine IllegalStateException (Not on FX application thread) geworfen, der Fehler wird vom Entwickler frühzeitig erkannt und muss korrigiert werden. Das ist leider nicht immer der Fall. Schlimmer ist es in Swing, dort werden beim Zugriff außerhalb des EDT in der Regel keine Exceptions geworfen (Abb. 1).

Daher muss der Entwickler von Anfang an auf die Benutzung der korrekten UI-Threads achten. Die möglichen Fehler treten meist erst zum späten Zeitpunkt oder im Betrieb auf und sind sehr schwer reproduzierbar. Typische Folgen sind z. B. fehlende Anzeigen, Fehler im UI-Layout oder Drawing-Probleme.

Abb. 1: Kommunikation zwischen Swing und JavaFX

Abb. 1: Kommunikation zwischen Swing und JavaFX

Mit Java 8 wurde eine experimentelle Neuerung eingeführt, die den Swing EDT und den JavaFX Application Thread vereinen soll, der Entwickler könnte dann auf die hier vorgestellte Threadzuordnung verzichten. Die Anwendung muss dafür lediglich mit dem Parameter Djavafx.embed.singleThread=true gestartet werden. Leider ist diese Funktion noch nicht reif für den produktiven Einsatz, im Test zeigten sich erhebliche Probleme.

Konzept 1: JavaFX-Komponenten in Swing-Anwendungen

Für die schrittweise Umstellung auf JavaFX-Komponenten innerhalb einer Swing-Anwendung bieten sich zwei Möglichkeiten an: Entweder werden neue JavaFX-Komponenten innerhalb eines Swing-Fensters (JFrame) oder Swing-Dialogs (JDialog) eingebettet, oder es werden stets gesamte Fenster oder Dialoge der Swing-Anwendung auf JavaFX (Stage) umgestellt, beide Varianten werden hier betrachtet.

Die Einbettung von JavaFX-Komponenten in Swing-Fenster gestaltet sich recht einfach. Bereits in JavaFX 2.0 wurde dafür die Swing-Komponente JFXPanel eingeführt. Ein JFXPanel kann innerhalb von Swing-Oberflächen beliebig platziert werden. Mittels der Methode setScene(Scene) stellt es die übergebene JavaFX-Scene innerhalb der JFXPanel-Komponente dar. Als Beispiel zeigt Listing 1 eine einfache Swing-Anwendung mit einem JavaFX-Button, der auf die Swing-Komponenten zugreift (Abb. 2).

Abb. 2: JavaFX-Komponente in Swing-Fenster

Abb. 2: JavaFX-Komponente in Swing-Fenster

 

public class SwingFrameWithJavaFXControl extends JFrame {
  private JLabel swingLabel;

  public SwingFrameWithJavaFXControl() {
      // Setup Swing-Fenster, -Button und -Label
      super("Swing Frame");
     ...
      JFXPanel jfxPanel = new JFXPanel();
      swingRootPanel.add(jfxPanel, BorderLayout.CENTER); 

      Platform.runLater(() -> setupJavaFXScene(jfxPanel)); 
  }

  private void setupJavaFXScene(JFXPanel jfxPanel) {
      Button btJavaFX = new Button("JavaFX Button");
      jfxPanel.setScene(new Scene(btJavaFX)); 

      btJavaFX.setOnAction(event -> {
SwingUtilities.invokeLater(() -> swingLabel.setText("JavaFX button pressed"));
      });
      SwingUtilities.invokeLater(this::showSwingWindow); 
  }

  private void showSwingWindow() {
      pack();
      setVisible(true); 
  }

  public static void main(String[] args) {
      SwingUtilities.invokeLater(SwingFrameWithJavaFXControl::new); 
  }
}

Da JFXPanel eine Swing-Komponente ist, muss deren Benutzung stets innerhalb des Swing EDT erfolgen. Lediglich die Methode setScene(Scene) kann auch aus dem JavaFX Application Thread gerufen werden – praktisch, um die JavaFX Scene nach deren Erstellung zu platzieren. Der Event Handler des JavaFX-Buttons darf wiederum nicht direkt auf das Swing-Label zugreifen, dies muss asynchron im Swing EDT erfolgen.

Neben dem korrekten UI-Threading muss eine weitere Besonderheit berücksichtigt werden, die in den Oracle-Tutorials und der API-Dokumentation leider vergessen wurde. Das Layout des Swing-Fensters durch die Methode pack() sowie dessen Anzeige können erst erfolgen, wenn die asynchrone Erstellung und Initialisierung der JavaFX-Scene abgeschlossen und diese im JFXPanel eingebettet wurde. Andernfalls gibt es Fehler in der Darstellung des Swing-Fensters, in unserem Beispiel wird entweder der JavaFX-Button nicht angezeigt oder er überlagert den Swing-Button. Die Methode showSwingWindow() muss daher abschließend nach der Scene-Erstellung gerufen werden, dies jedoch wiederum asynchron innerhalb des Swing EDT.

Screencast mit Livemigration

Dieses YouTube-Video veranschaulicht die technischen Werkzeuge, die bei einer Migration zur Verfügung stehen: https://youtu.be/z–ue6QSkCA

Abbildung 2 zeigt jedoch einen Nachteil dieses Vorgehens: das unterschiedliche Look-and-Feel von Swing- und JavaFX-Komponenten, betreffend Farben, Schriften, Größen usw. Je nach Komplexität des gemischten Fensters wird dies für den Benutzer zunehmend störender. In den Abbildungen 3 und 4 sind die Look-and-Feel-Unterschiede anhand eines migrierten Swing-Dialogs sehr deutlich zu erkennen.

Abb. 3: Swing-Dialog vor Migration

Abb. 3: Swing-Dialog vor Migration

Abb. 4: JavaFX-Dialog nach Migration

Abb. 4: JavaFX-Dialog nach Migration

Ein weiteres Problem ist das unterbrochene Fokuswechselverhalten innerhalb des Fensters. Liegt der Fokus auf dem Swing-Button, ist es nicht möglich, per Tab-Taste den JavaFX-Button zu fokussieren und umgekehrt. Wird diese Funktion gefordert, so muss dies manuell durch die kombinierte Benutzung der Fokus-Listener implementiert werden.

JavaFX-Fenster in Swing-Anwendungen

Die genannten Probleme werden vermieden, wenn innerhalb der Swing-Anwendung gesamte Fenster oder Dialoge auf JavaFX umgestellt werden. Dabei gibt es einige Hürden, die jedoch durch wenige Workarounds umgangen werden können. Ein einfacher Ansatz, die Probleme zu lösen, ist die Verwendung eines JDialogs (Listing 2).

JDialog dialog = new JDialog();
JavaFXDialog myJavaFXDialog = new JavaFXDialog();
dialog.setModal(true);
dialog.add(jfxWrapper);
    
Platform.runLater(() -> {
  Scene scene = new Scene(loginDataDialog);
  jfxWrapper.setScene(scene);
});

Das Problem bei dem vorangegangenen Beispiel ist Folgendes: Das Fenster ist immer noch ein Swing Frame und keine JavaFX-Stage. Soll eine Stage verwendet werden, ist ein tieferer Griff in die Trickkiste nötig. Dies demonstrieren wir im nächsten Beispiel, einer Swing-Anwendung, die eine modale JavaFX Stage anzeigt. Abbildung 5 zeigt diese Anwendung, der Quellcode ist in den Listings 3 bis 5 zu finden.

Abb. 5: Modaler JavaFX-Dialog aus Swing-Fenster

Abb. 5: Modaler JavaFX-Dialog aus Swing-Fenster

Für die Benutzung von JavaFX ist stets die Initialisierung des JavaFX Toolkits nötig, das erfolgt in reinen JavaFX-Anwendungen durch die Basisklasse javafx.application.Application beim Aufruf von launch(). Im zuvor beschriebenen JFXPanel übernimmt dieses die Initialisierung. Wird die JavaFX Stage jedoch innerhalb einer Swing-Anwendung erstellt, wird das sofort mit einer IllegalStateException quittiert (Toolkit not initialized).

Ein Ausweg ist die Erstellung einer nicht sichtbaren (transparenten) JavaFX-Anwendung mit minimaler Fenstergröße. Diese Application-Instanz wird vor dem Start der Swing-Anwendung in einem eigenen Thread per launch() initialisiert und kann während der gesamten Laufzeit der Swing-Anwendung genutzt werden, z. B. als Elternfenster für neue JavaFX-Dialoge.

An dieser Stelle zeigt sich das nächste Problem: Sowohl das Swing- als auch das JavaFX API bieten keine Möglichkeit, eine Beziehung zwischen deren Fenstern zu definieren. Es ist nicht vorgesehen, dass z. B. ein modaler JavaFX-Dialog für eine Swing-Anwendung mittig über dem Swing-Fenster angezeigt wird und die Swing-Anwendung währenddessen blockiert ist. Aber auch dieses Problem kann auf Umwegen gelöst werden.

Zunächst muss das JavaFX-Dialogfenster mittig über dem Swing-Elternfenster positioniert werden, die Berechnung der benötigten Koordinaten ist eigentlich keine Herausforderung. Leider ist die Größe eines JavaFX-Fensters erst nach dessen Anzeige bekannt, die Berechnung muss jedoch zuvor erfolgen. Der Ausweg ist die temporäre Anzeige des JavaFX-Fensters, dabei muss das Fenster jedoch für den Benutzer unsichtbar bleiben – das wird durch setOpacity(0) erreicht. Danach sind die Breite und Höhe des JavaFX-Fensters gesetzt, und die Positionierung kann berechnet werden.

Etwas schwieriger gestaltet sich die Anzeige von modalen JavaFX-Dialogen aus Swing-Fenstern. Aufgrund des fehlenden API ist folgender Trick nötig: Ein Listener auf dem JavaFX-Fenster deaktiviert bei dessen Anzeige das komplette Swing-Fenster, beim Schließen des JavaFX-Fensters wird das Swing-Fenster wieder aktiviert. Mittels Stage.setAlwaysOnTop(true) wird das JavaFX-Fenster über dem Swing-Fenster angezeigt, das Swing-Fenster ist währenddessen nicht mehr bedienbar. Unter Mac OS X muss noch zusätzlich die Menüleiste des Swing-Fensters deaktiviert und wieder aktiviert werden, da sie nicht im Fenster, sondern im Systemmenü angezeigt wird.

All diese Funktionen sind am besten in einer externen Helper-Klasse aufgehoben (Listing 4, JFXHiddenApplication), damit sich die Migrations-Workarounds später ohne große Aufwände entfernen lassen.

Zugegeben, dieses Beispiel ist etwas aufwändiger als die JFXPanel-Benutzung, jedoch liegen die Vorteile auf der Hand: das Look-and-Feel für jedes einzelne Fenster ist zumindest für sich konsistent, Fokusprobleme treten nicht mehr auf. Die neuen JavaFX-Fenster (Listing 5, JFXDialog) enthalten keinen Migrationscode, die fehlerträchtigen Wechsel der UI-Threads zwischen Swing und JavaFX entfallen ebenfalls. Somit sind die Aufwände bei der zukünftigen finalen Migration der Swing-Fenster wesentlich geringer.

Das beschriebene Vorgehen wurde auch erfolgreich bei der Migration des OpenSource-Projekts SportsTracker genutzt. Diese Software zur Trainingsverwaltung wurde schrittweise von Swing nach JavaFX migriert und war während des gesamten Zeitraums vollständig funktionsfähig. Die Details der Migration können in der GitHub-Historie des Projekts vollständig eingesehen werden. Das Vorgehen wird von den Autoren auch in Kundenprojekten eingesetzt, auf die an dieser Stelle nicht eingegangen werden kann.

 public class SwingFrameWithJavaFXWindow extends JFrame {

  public SwingFrameWithJavaFXWindow() {

      // Setup Swing-Fenster und -Button
      super("Swing Frame");
     ...
      pack();
      setVisible(true);
  }

  private void showJavaFXWindow() {
      Platform.runLater(() -> {
          Stage parent = JFXHiddenApplication.getPrimaryStage();
          JFXDialog fxDialog = new JFXDialog(parent);
          JFXHiddenApplication.showJavaFXDialog(fxDialog, this);
      });
  }

  public static void main(String[] args) {
      JFXHiddenApplication.launchApplication();
      SwingUtilities.invokeLater(SwingFrameWithJavaFXWindow::new);
  }
}

 

public class JFXHiddenApplication extends Application {

  private static Stage primaryStage;

  public static void launchApplication() {
      new Thread(JFXHiddenApplication::launch).start();
  }

  @Override
  public void start(Stage primaryStage) throws Exception {
      JFXHiddenApplication.primaryStage = primaryStage;

      primaryStage.initStyle(StageStyle.TRANSPARENT);
      primaryStage.setScene(new Scene(new Pane(), 1, 1));
      primaryStage.show();
  }

  public static Stage getPrimaryStage() {
      return primaryStage;
  }

  public static void showJavaFXDialog(Stage fxDialog, JFrame swingParent) {
      fxDialog.setOpacity(0d);
      fxDialog.show();
      fxDialog.hide();
      fxDialog.setOpacity(1d);

      fxDialog.setX(swingParent.getBounds().getCenterX() - (fxDialog.getWidth() / 2));
      fxDialog.setY(swingParent.getBounds().getCenterY() - (fxDialog.getHeight() / 2));

      fakeModalDialog(fxDialog, swingParent);

      fxDialog.setAlwaysOnTop(true);
      fxDialog.showAndWait();
  }

  private static void fakeModalDialog(Stage fxDialog, JFrame swingParent) {

      fxDialog.setOnShown(e -> {
          SwingUtilities.invokeLater(() -> {
              swingParent.setEnabled(false);
              if (swingParent.getJMenuBar() != null) {
                  swingParent.getJMenuBar().setEnabled(false);
              }
          });
      });

      fxDialog.setOnHidden(e -> {
          SwingUtilities.invokeLater(() -> {
              swingParent.setEnabled(true);
              if (swingParent.getJMenuBar() != null) {
                  swingParent.getJMenuBar().setEnabled(true);
              }
              swingParent.toFront();
          });
      });
  }
}

 

public class JFXDialog extends Stage {

  public JFXDialog(Stage parent) {
      setTitle("JavaFX Dialog");
      initOwner(parent);
      initModality(Modality.APPLICATION_MODAL);
      initContent();
  }

  private void initContent() {
      Button btClose = new Button("Schließen");
      btClose.setOnAction(event -> close());

      VBox root = new VBox();
      root.setPadding(new Insets(32, 64, 32, 64));
      root.getChildren().add(btClose);
      setScene(new Scene(root));
  }
}

Konzept 2: Swing-Komponenten in JavaFX-Anwendungen

Kommen wir nun zum umgekehrten Fall – Swing in JavaFX einzubetten. Es kostet viel Zeit und damit Geld, komplexe Komponenten und Controls stabil zu implementieren. Ein Beispiel aus der Swing-Welt sind Tabellen, die Excel-Funktionalität nachempfinden. Aus technischer und wirtschaftlicher Sicht kann es sinnvoll sein, genau diese Elemente in der neuen JavaFX-Anwendung weiterzuverwenden und erst in einem zukünftigen Release zu erneuern. Für diesen Anwendungsfall bietet JavaFX das so genannte SwingNode. Mit diesem speziellen Container können JComponents an beliebigen Stellen der Anwendung integriert werden. Zu beachten ist, dass SwingNode erst mit JavaFX 8 zur Verfügung steht. In niedrigeren Versionen kann diese Komponente nicht verwendet werden (Abb. 6).

Abb. 6: Swing-Komponente in JavaFX-Fenster in Windows

Abb. 6: Swing-Komponente in JavaFX-Fenster in Windows

 

       Button label = new Button("JavaFX"); 
     SwingNode swing = new SwingNode();
   //Swing Stuff im Swing Thread
     SwingUtilities.invokeLater(() -> {
                 JButton jLabel = new JButton("Swing");
                 swing.setContent(jLabel);
VBox vBox = new VBox(label, swing);
//Zurück in JFXThread wechseln und gelayoutetes Element hinzufügen
Platform.runLater(() -> scene.setRoot(vbox));
     });

Wie in Listing 6 zu sehen, wird ein SwingNode erzeugt und mit setContent() ein JLabel mitgegeben. An genau dieser Stelle gibt es Gefahrenpotenzial. Wie im vorangegangenen Teil des Artikels beschrieben, sollten alle Interaktionen mit der Swing-Welt im EDT über ein SwingUtilities.invokeLater() abgehandelt werden. Im Gegensatz zu JavaFX quittiert Swing die Interaktion mit UI-Elementen aus einem anderen Thread, jedoch nicht mit einer RuntimeException. Das birgt Fehlerpotenzial und kann zu massiven Layouting-Problemen führen.

Zudem wird im JavaDoc explizit darauf hingewiesen, dass im SwingNode keine HeavyWeight-Komponenten integriert werden sollten, da die Gefahr von Repainting-Problemen im SwingNode besteht.

Konzept 3: Komplette UI-Schicht mit JavaFX neu entwickeln

Diese Variante sollte in Betracht gezogen werden, wenn für die Migration ausreichend Zeit und Budget bereitstehen und die komplette Präsentationsschicht neu entwickelt werden kann. Der große Vorteil ist die Vermeidung typischer Probleme, die durch einen Swing-und-JavaFX-Mix entstehen (Threading, Look-and-Feel etc.). Wie im Abschnitt „Vorbedingungen“ beschrieben, ist zunächst ein umfangreiches Refactoring der Altanwendung notwendig. Anschließend kann die alte UI-Schicht mit JavaFX abgelöst werden. Im Zuge der Neuimplementierung einer Oberfläche sollte auch eine Modernisierung aus UI/UX-Sicht vorgenommen werden. Es empfiehlt sich nicht, die alte Oberfläche 1:1 nachzubauen. Einen Vergleich aller Konzepte zeigt Abbildung 7.

Fazit – Wann passt welche Strategie?

1: Sanfte Migration: Wenn nach und nach der JavaFX-Anteil der Anwendung erhöht wird, ist eine langwierige Neuimplementierung zunächst nicht notwendig. So können den Nutzern schnell die Vorteile von JavaFX (z. B. die WebView) zugänglich gemacht werden. Das macht es aber nicht einfacher – Threading-Probleme und Look-and-Feel-Inkonsistenzen sind keine triviale Angelegenheit. Andererseits ist in diesem Fall nicht zwingend ein vorangegangenes Refactoring der bestehenden Views notwendig. Zudem empfehlen wir, gerade neue Features nicht mehr mit der alten Technologie umzusetzen, wenn die Entscheidung auf JavaFX bereits gefallen ist. Dieser Ansatz bringt ein schnelles Nutzererlebnis, jedoch auch hohe technische Schuld, die exponenziell zum Vermischungsgrad von Swing und JavaFX steigt. Letztendlich ist der Technologiemix keine Dauerlösung, und ein kompletter Wechsel auf JavaFX muss zwingend geplant werden.

2: Neuimplementierung mit Weiterverwendung von Komponenten: Wenn Zeit und Budget für die Umstellung eines Großteils der Anwendung zur Verfügung stehen, jedoch bestimmte Bereiche noch nicht umgestellt werden sollen, ist dieser Weg ein Kompromiss. Man kann sich vorerst den Aufwand der Neuentwicklung von existierenden, komplexen Komponenten sparen. Die technische Schuld ist dabei niedriger als bei der sanften Migration, da hier ein umfangreiches Refactoring der abzulösenden Views vorgenommen werden muss und der Grad der Vermischung von Swing und JavaFX minimiert wird. Threading und Look-and-Feel-Probleme bis zur Migration der alten Komponenten müssen trotzdem gelöst werden.

3: Komplette Neuimplementierung (hohe Wartbarkeit, weniger komplex): Diese Variante ist die bevorzugte, wenn Zeit und Geld es zulassen. Man spart sich Threading und Look-and-Feel-Probleme und muss lediglich das Refactoring der bestehenden Anwendung als Vorbereitung auf die kommende Migration durchführen. Dadurch sinken die Komplexität und die technische Schuld. Ein großer Nachteil der Variante ist, dass eine Neuentwicklung die mittelfristige Releasefähigkeit der Anwendung gegen 0 senkt.

Abb. 7: Vergleich der Konzepte

Abb. 7: Vergleich der Konzepte

Abschluss

Ist einmal die Entscheidung gefallen, in die Zukunft einer Swing-Anwendung zu investieren, führt kein Weg an JavaFX vorbei. Die Gründe dabei können vielseitig sein, und die Herangehensweise sei wohl überlegt. Wenn Sie genau an diesem Punkt stehen und weiterführende Fragen haben, stehen wir Ihnen jederzeit zu Verfügung (alexander.casall@saxsys.de).

Aufmacherbild: Street Sign von Shutterstock / Urheberrecht: keport

Geschrieben von
Alexander Casall
Alexander Casall
Alexander Casall hat 2011 seinen Master in Informatik abgeschlossen und arbeitet seitdem bei der Saxonia Systems AG als Softwareentwickler. Sein Fokus liegt auf der Implementierung moderner Multi-Touch-Applikationen mit JavaFX. U.a. arbeitet er an Kollaborationswerkzeugen zur verteilten, agilen Entwicklung. In seiner Freizeit entwickelt Alexander native iOS Apps (www.buildpath.de), veröffentlicht Artikel in Fachzeitschriften, oder hält Vorträge auf Konferenzen (JavaOne, JAX, W-JAX) und User Groups.
Stefan Saring

Stefan Saring ist als Softwarearchitekt und Entwickler bei der Saxonia Systems AG tätig. Seit nunmehr fünfzehn Jahren arbeitet er vorwiegend in Projekten aus dem Java-Standard- und Enterprise-Bereich.

Kommentare

Hinterlasse einen Kommentar

4 Kommentare auf "Wie man Swing-Anwendungen mit JavaFX modernisiert"

avatar
400
  Subscribe  
Benachrichtige mich zu:
trackback

[…] und andere Besonderheiten wurden im Rahmen unseres Migrations-Artikels im JavaMagazin 09/2015 (Online-Version) vorgestellt. Zudem kann der Quellcode der gesamten Migration des SportsTracker-Projekts auf GitHub […]

Martin Jung
Gast
Hi, vielen Dank erst mal für den Artikel. Ich wollte mir gerade einen Eindruck davon verschaffen, wie Swing mit JavaFX zusammen geht und ob es überhaupt lohnt eine Swing-Anwendung nach JavaFX zu portieren. Leider war schon das erstes Beispiel (Listing 1) bei mir nicht lauffähig, auch wenn ich (…) entferne und ’swingRootPanel‘? mit ‚rootPane‘ ersetze (was fehlt noch?). Schade, so ein kleines Progrämmchen von ~ 30 Zeilen sollte doch in Gänze abdruckbar und lauffähig sein, oder wenigstens ein Link zum Download kompletter (getesteter) lauffähiger Klassen bereitgestellt werden können. Das steigert die Motivation beim Einstieg in Neuland ungemein. Dass es auch… Read more »
Martin Jung
Gast
import java.awt.GridLayout; import javafx.application.Platform; import javafx.embed.swing.JFXPanel; import javafx.scene.Scene; import javafx.scene.control.Button; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; public class SwingFrame extends JFrame { private Button btJavaFX = null; public SwingFrame() { super(„Swing Frame“); initGUI(); } private void initGUI() { setLayout(new GridLayout(0, 1)); JButton swingButton = new JButton(„Swing Button“); JLabel swingLabel = new JLabel(„Test“); // Create JavaFX Container JFXPanel jfxPanel = new JFXPanel(); // Switch to JavaFX Thread to create JavaFX content Platform.runLater(() -> { btJavaFX = new Button(„JavaFX Button“); btJavaFX.setOnAction ( event -> { SwingUtilities.invokeLater(() -> swingLabel.setText(„JavaFX button pressed“)); } ); jfxPanel.setScene(new Scene(btJavaFX)); SwingUtilities.invokeLater ( () -> { //… Read more »
Peter Schneider
Gast

Leider erwähnen Sie nicht, dass JavaFX noch nicht vollständig ist und daher für viele Programme nicht brauchbar.
Es fehlen RichText Komponenten, Schnittstellen für Verwendung von OpenGL etc.
Ausserdem wurde JavaFX bereits für einige Platformen eingestellt.
Eine zukunftsträchtige GUI sieht anders aus!