JavaFX 8

JavaFX is het platform voor Java rich client applicaties. Onderdeel van de release van Java 8 is ook de nieuwe versie van JavaFX. Wat is er zoal veranderd?

Geschiedenisles
JavaFX gaat inmiddels al een aantal jaren mee. Versie 1 werd al in 2008 vrijgegeven door Sun en vereiste nog het gebruik van een eigen programmeertaal, genaamd JavaFX Script. Ondanks dat deze taal erg geschikt was voor de ontwikkeling van user interfaces, was het leren van een nieuwe taal toch een groot obstakel voor veel Java-ontwikkelaars. Dit, in combinatie met het feit dat de eerste paar officiële releases van JavaFX nog erg buggy en incompleet waren, zorgde ervoor dat JavaFX 1 nooit een groot succes is geworden.

 

Toen Sun goed en wel overgenomen was door Oracle, besloot men dat het JavaFX-roer omgegooid moest worden. JavaFX Script werd verlaten en versie 2 van JavaFX werd een pure Java API. Hierdoor werd het mogelijk direct gebruik te maken van JavaFX vanuit Java-code, of vanuit andere talen die draaiden op de JVM, zoals Scala. Tevens kwam er een losstaande tool, de Scene Builder, die het mogelijk maakte om met klikken en slepen een user interface te ontwerpen en te testen. Vanaf het moment dat JavaFX 2 feature complete was, werd het meegeleverd met nieuwe releases van JDK7.

 

Parallel met de doorontwikkeling van JavaFX 2, is alweer geruime tijd de  nieuwe major versie in ontwikkeling. Deze nieuwe versie had als codenaam Lombard, vernoemd naar Lombard Street in San Francisco. Met de release van Java 8 wordt ook deze nieuwe versie van JavaFX vrijgegeven. Het versienummer wordt deze keer niet opgehoogd tot 3, maar gelijkgetrokken met de versie van Java zelf: JavaFX 8. De bedoeling is dat de major releases van JavaFX voortaan ook in de pas blijven lopen met die van Java.

Project Lambda
Eén van de grote verbeteringen in JavaFX 8 staat eigenlijk helemaal los van de veranderingen in het platform zelf, maar is een gevolg van het feit dat het draait op Java 8. Dit betekent namelijk dat de functionaliteit van Project Lambda beschikbaar is.

Bij desktop-user-interface-applicaties worden relatief vaak inner classes gebruikt, voor zaken als action handlers en listeners. Dit zijn over het algemeen heel goede voorbeelden van functionele interfaces, die in Java 8 dus veel korter en krachtiger op te schrijven zijn.

Het toevoegen van een action handler aan een button zou bijvoorbeeld voorheen over het algemeen gebeuren met een anonymous inner class, op de volgende manier:


    Label label = new Label("Button has not been clicked.");
    Button button = new Button("Click me!");
    button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
label.setText("Button clicked.");
}
});

Listing 1

 

Nu wordt dit simpelweg:

 


    Label label = new Label("Button has not been clicked.");
    Button button = new Button("Click me!");
    button.setOnAction(event -> label.setText("Button clicked."));

Listing 2

 

Zodra je enigszins gewend bent aan de nieuwe syntax, zorgt dit ervoor dat de code veel beknopter en leesbaarder wordt. Alle overige voordelen van de nieuwe streaming API en de mogelijkheden tot parallellisme in Java 8 zijn natuurlijk ook beschikbaar in JavaFX-desktop-applicaties.

WebView
Een sterke koppeling met HTML en het internet is altijd onderdeel geweest van de designfilosofie van JavaFX. Zo was het in JavaFX 2 al mogelijk om de layout van een JavaFX-applicatie te specificeren door middel van cascading style sheets (CSS). Een ander voorbeeld hiervan is de WebView-component.

WebView bestond al in JavaFX 2, maar is voor versie 8 sterk verbeterd. WebView bevat in feite een volwaardige internetbrowser, wat het mogelijk maakt om HTML-pagina's te laten zien, embedded in je JavaFX-applicatie. Dit kan een lokale HTML-pagina zijn, maar ook gewoon een pagina van het internet:


    WebView view = new WebView();
view.getEngine().load("http://www.google.com"); primaryStage.titleProperty().bind(view.getEngine().titleProperty());
primaryStage.setScene(new Scene(view));|
primaryStage.show();

Listing 3

 

 

 

Ook JavaScript-functionaliteit wordt hierbij volledig ondersteund. Vanaf Java 8 gebeurt dit met de nieuwe JavaScript-engine Nashorn, die stukken efficiënter is dan zijn voorganger Rhino.

 

Afbeelding 1: De Google homepage, inclusief JavaScript-functionaliteit, in een JavaFX WebView

In de WebView in JavaFX 8 is verder ondersteuning toegevoegd voor een aantal nieuwe brokken functionaliteit uit HTML 5: Web Sockets en Web Workers.

Deze verbeteringen in de WebView maken het een stuk eenvoudiger om (delen van een) HTML5-applicatie te integreren met de user interface van een desktop-applicatie.

 

Afbeelding 2: Pagina met Web Sockets in een JavaFX WebView

 

Afbeelding 3: Pagina met Web Workers in een JavaFX WebView

 

Naast de WebView is ook de bestaande HTML Editor-component verbeterd. Deze editor stelt de gebruiker in staat om tekst in te voeren en te bewerken, inclusief opmaak. Middels de methode getHTMLText is het mogelijk de tekst, inclusief opmaak, op te vragen in HTML-formaat.


    HTMLEditor editor = new HTMLEditor();
   primaryStage.setTitle("HTML Editor In JavaFX");
primaryStage.setScene(new Scene(editor));
primaryStage.show(); // print the html text to sysout when exiting the application
primaryStage.setOnHiding(event -> System.out.println(editor.getHtmlText()));

Listing 4

 

Afbeelding 4: Een HTMLEditor

 

De aanroep van getHTMLText() levert vervolgens het volgende op:


<html dir="ltr">
<head></head>
<body contenteditable="true">
<h1><font face="Verdana" size="6">Header</font></h1>
<p><font face="Verdana">Tekst met custom opmaak, bijvoorbeeld <b>vet</b>, <i>schuingedrukt</i>, <strike>doorgestreept</strike> of <span style="background-color: rgb(128, 102, 204);"><font color="#ffcc66">kleurtjes</font></span></font></p>
<p><ul>
<li><font face="Verdana">en een</font></li>
<li><font face="Verdana">lijst met</font></li>
<li><font face="Verdana">bolletjes</font></li>
</ul><p><font face="Verdana"><br></font></p></p>
</body>
</html>

Listing 5

 

3D graphics
JavaFX 8 maakt het mogelijk driedimensionale scènes te definiëren. Bij het renderen van deze 3D-scènes wordt gebruik gemaakt van de hardware-acceleratie van de PC. Standaard is een drietal simpele driedimensionale vormen beschikbaar: de bol, de cylinder en de balk.

 


        Sphere sphere = new Sphere(100);
sphere.setTranslateX(200);
sphere.setTranslateY(200);
sphere.setMaterial(new PhongMaterial(Color.BLUE)); Box box = new Box(120, 120, 120);
box.setTranslateX(700);
box.setTranslateY(400);
box.setMaterial(new PhongMaterial(Color.RED)); Cylinder cylinder = new Cylinder(100, 200);
cylinder.setTranslateX(400);
cylinder.setTranslateY(400);
cylinder.setMaterial(new PhongMaterial(Color.ORANGE)); Group root = new Group();
root.getChildren().addAll(sphere, box, cylinder); Scene scene = new Scene(root, 1024, 768);
scene.setFill(Color.GRAY); Camera camera = new PerspectiveCamera();
scene.setCamera(camera); primaryStage.setTitle("3D");
primaryStage.setScene(scene);
primaryStage.show();

Listing 6

 

 

Afbeelding 5: Bol, cylinder en kubus in 3D

 

Daarnaast is het ook mogelijk zelf gedefinieerde polygonale 3D-meshes te gebruiken. Bij de JavaFX 8 samples is een voorbeeldapplicatie opgenomen waarmee het mogelijk is 3D-modellen te laden in een standaardformaat, zoals dat van 3D Studio Max, en weg te schrijven als FXML. Vervolgens zijn deze te gebruiken in een JavaFX-applicatie.

 

Belichting is ook vrij eenvoudig. Er zijn vooralsnog twee soorten lichtbronnen: AmbientLight en PointLight. Deze kunnen, net als alle andere JavaFX-componenten, simpelweg toegevoegd worden aan de scene graph.

 

*** listing 7 ***

 


    PointLight light = new PointLight(Color.WHITE);
light.setTranslateX(100);
light.setTranslateY(200);
light.setTranslateZ(-300); root.getChildren().add(light);

listing 7

Ik had eerder al eens een eenvoudig Tetris-spelletje geschreven met behulp van JavaFX 2.2. Doordat het gedrag van 3D-objecten in JavaFX vrijwel hetzelfde is als alle overige JavaFX-componenten, was het erg eenvoudig om de Tetris-blokjes om te zetten naar 3D-kubussen.

Afbeelding 6: Tetris met realtime 3D-blokken

Het is overigens wel te merken dat de 3D-API nog in de kinderschoenen staat. Zo is er bijvoorbeeld nog geen ondersteuning voor transparantie-effecten of anti-aliasing.

DatePicker
Wanneer het nodig is de gebruiker een datum in te laten voeren, ligt het voor de hand dit te doen met behulp van een zogenaamde date picker. De meeste user interface frameworks bieden wel een date picker aan, maar in zowel Swing als in eerdere versies van JavaFX ontbrak deze component. In JavaFX 8 is deze dan toch eindelijk toegevoegd.

 


        LocalDate java8Release = LocalDate.of(2014, 3, 18);
DatePicker datePicker = new DatePicker(java8Release);

listing 8

 

De API van de DatePicker-component is volledig geënt op de nieuwe Date/Time API, ook nieuw voor de release van Java 8.

 

 

Afbeelding 7: Een date picker met de Nederlandse locale

 

Verbeterde integratie met Swing
Oracle ziet JavaFX als de opvolger van Swing. Op Swing wordt dan ook nog maar minimaal onderhoud gepleegd. Er zijn nog veel bestaande Swing-applicaties in gebruik en in onderhoud. Het is over het algemeen geen kleine klus zo'n hele applicatie ineens om te schrijven om gebruik te maken van JavaFX.

Gelukkig hoeft dat ook niet. Het was in JavaFX 2 al goed mogelijk om JavaFX-componenten in een Swing-applicatie op te nemen. Met de release van JavaFX 8 is ook het omgekeerde gemakkelijk geworden, met behulp van de SwingNode-klasse. Een SwingNode is een JavaFX-component, waarbinnen Swing-componenten opgenomen kunnen worden. Het volgende codevoorbeeld laat bijvoorbeeld zien hoe een JButton opgenomen kan worden in een JavaFX-applicatie.


    JButton swingButton = new JButton("Swing Button!");
swingButton.addActionListener(event -> System.out.println
("Swing button clicked.")); SwingNode swingNode = new SwingNode();
swingNode.setContent(swingButton);

Listing 9

Verder is er een (experimentele) JVM-optie toegevoegd: -Djavafx.embed.singleThread=true. In Swing-applicaties is het normaal gesproken zo, dat alle operaties op user interface componenten dienen te gebeuren in de Swing Event Dispatch Thread. Om te forceren dat een stuk code op die thread uitgevoerd wordt, is er de hulpmethode SwingUtilities.invokeLater. JavaFX heeft een vergelijkbaar mechanisme, met een eigen JavaFX-thread en de hulpmethode Platform.runLater. Doordat beide frameworks hun eigen thread hadden, was het in applicaties met zowel Swing als JavaFX over het algemeen nodig om veel gebruik te maken van SwingUtilities.invokeLater en Platform.runLater om over en weer te wisselen.

De nieuwe JVM-optie zorgt ervoor dat JavaFX en Swing allebei gebruik maken van de JavaFX application thread voor het verwerken van hun events. Dit maakt het wisselen tussen de twee threads overbodig. Alles gebeurt nu standaard op dezelfde thread. Door deze verbeterde integratie wordt het steeds beter mogelijk om bestaande Swing-applicaties geleidelijk te migreren naar JavaFX.

Verbeterde exception handling
In JavaFX 2 was het zo, dat de JavaFX thread alle onopgevangen runtime exceptions zonder meer afving, de stack trace wegschreef naar System.err en de exceptie verder negeerde. Het was natuurlijk mogelijk om een uncaught exception handler toe te voegen aan de JavaFX-thread, maar deze werd vervolgens nooit aangeroepen, omdat alle runtime exceptions al op een hoger niveau door JavaFX werden ingeslikt. Hierdoor was het onmogelijk om bijvoorbeeld stack traces van onverwachte excepties te loggen met behulp van een logging framework als log4j. In JavaFX 8 kan dit gelukkig wel met een normale uncaught exception handler.

 


    Thread.currentThread().setUncaughtExceptionHandler(
(thread, throwable) -> log.error("An exception occurred!", throwable));

listing 10

 

Open source
Hoewel het al geruime tijd de bedoeling was om heel JavaFX een open source license mee te geven, waren er in JavaFX 2 nog altijd enkele brokjes closed-source code aanwezig in het platform, die dit verhinderden. Met de release van JavaFX 8 zijn deze allemaal verwijderd en vervangen door open source software.

Een belangrijk praktisch voordeel hiervan is dat de source van JavaFX nu ook met de JDK mee gedistribueerd wordt, wat het een stuk makkelijker maakt om in een IDE te debuggen en de Javadoc na te lezen.

Backwards compatibility
Qua API zou JavaFX 8 backwards compatible moeten zijn. Om dit uit te proberen, heb ik enkele JavaFX-2-applicaties geprobeerd te migreren naar JavaFX 8. Dit is gelukt, maar er zaten wel wat addertjes onder het gras.

Bij het uitvoeren van een met JavaFX 2 gecompileerde applicatie, viel het op dat sommige user interface componenten ineens enigszins schots en scheef uitgelijnd waren. Ook hercompileren op basis van JavaFX 8 verhielp dit probleem niet. Om de user interface te repareren was het nodig de FXML te openen in de nieuwe Scene Builder en wat kleine aanpassingen te maken. Dit was zo gebeurd, maar vergde dus wel een aanpassing.

Bij het builden op basis van JavaFX 8 compileerde alles zonder problemen, maar enkele bestaande unit tests faalden ineens met een IllegalStateException. Sommige JavaFX-componenten, zoals bijvoorbeeld de ComboBox, vereisen in JavaFX 8 dat er eerst een expliciete initialisatieslag wordt uitgevoerd. Bij het starten van een applicatie gebeurt dit vanzelf binnen de Application.launch-methode, maar juist vanuit unit tests is het wel noodzakelijk hiervoor wat expliciete initialisatie uit te voeren. De eenvoudigste manier om dit te doen is door een JFXPanel te instantiëren:

 


    @Before
public void setUp() {|
new JFXPanel();|
}

listing 11

Na het toevoegen van deze setup-methode slaagden alle test cases weer.

Het is dus verstandig de backwards compatibility van JavaFX 8 met een korrel zout te nemen. De applicatie zal blijven compileren, maar het is wel degelijk verstandig deze goed door te testen en er rekening mee te houden dat het nodig kan zijn om kleine aanpassingen door te voeren.

Builder-API deprecated
In de begintijd van JavaFX 2 waren er twee manieren om JavaFX-componenten te instantiëren. Met behulp van de constructor


    Button button = new Button("Click me!");
button.setDefaultButton(true);
button.setOnAction(event -> System.out.println(„clicked"));

listing 12

of met behulp van een builder


    Button button = ButtonBuilder.create()
.text("Click me!")
.defaultButton(true)
.onAction(event -> System.out.println("clicked"))
.build();

listing 13

Door gebruik te maken van de builder API wordt de definitie van user interface componenten op een wat meer declaratieve manier opgeschreven. Dit sloot indertijd ook beter aan op wat de gebruikers van JavaFX 1 en JavaFX Script gewend waren.

Later is er een derde manier bij gekomen: FXML. Daarbij staat de definitie van de componenten zelf in een xml-bestand, en de bijbehorende logica in een aparte controller-klasse.


    Button button = (Button)FXMLLoader.load(getClass().getResource("/fxmlapplication.fxml"));
    primaryStage.setTitle("Button");
primaryStage.setScene(new Scene(button));
primaryStage.show();

 

listing 14

Het bestand fxmlapplication.fxml ziet er dan als volgt uit.


    <?xml version="1.0" encoding="UTF-8"?>
    <?import javafx.scene.control.Button?>
    <Button text="Click me!" defaultButton="true" xmlns:fx="http://javafx.com/fxml" fx:controller="ButtonController" onAction="#handleButtonAction" />

listing 15

De klasse ButtonController heeft alleen de volgende methode.


    @FXML
protected void handleButtonAction() {
System.out.println("clicked");
}

Listing 16

 

De introductie van FXML maakte het (misschien nog wel beter dan de builder-API) mogelijk om de definitie van user interface componenten op een declaratieve manier op te schrijven. Daarnaast maakt FXML het makkelijk om de user interface en de bijbehorende logica op te delen, volgens het model-view-controller-patroon.

 

Door de toevoeging van FXML is de builder API zo ongeveer overbodig geworden. Daarnaast bleek de manier waarop het JavaFX-team de builder-API-classes genereerde afhankelijk te zijn van een aantal obscure bugs in de JVM, die in JDK8 opgelost zijn.

Om deze redenen is besloten de builder API deprecated te maken. Voorlopig blijven de builders nog wel bestaan, maar de intentie is om deze bij de release van JavaFX 9 helemaal te verwijderen.

Packaging en deployment
JavaFX 2 werd weliswaar meegeleverd met JDK 7, maar de runtime (jfxrt.jar) stond standaard niet op het classpath. Deze jar was ook niet beschikbaar in de standaard Maven-repositories, mede omdat deze code op dat moment nog niet volledig open source was. Het vergde dus altijd wat trucs om JavaFX beschikbaar te maken in Maven-projecten. Ook bij de distributie van software was het altijd nodig om na te denken over de vraag hoe deze jar op het classpath moest komen.

Met de release van JavaFX 8 is dit gelukkig voorbij. De jfxrt.jar wordt nu standaard meegeleverd met de JRE in de directory /lib/ext/. Het is dus veilig om er vanuit te gaan dat deze standaard aanwezig is op het classpath.

JavaFX op de Raspberry Pi
Met de release van Java 8 is er een hernieuwde interesse bij Oracle om Java weer aantrekkelijk te laten worden voor embedded devices. Ook bij JavaFX is hier aandacht aan besteed. JavaFX 8 zou moeten werken op ARM-devices. De referentie-hardware hierbij is de Raspberry Pi.

Niet alle functionaliteit van JavaFX is beschikbaar in de embedded variant. De twee zaken die ontbreken zijn de web- en media-modules. Van de andere kant heeft JavaFX standaard wel support voor touchscreens en multitouchscreens.

Conclusie
Bij de ontwikkeling van JavaFX 8 is het roer niet zo dramatisch omgegooid als bij die van JavaFX 2. Het gaat om een (grotendeels) backwards compatible release, waarbij veel, kleinere maar nuttige, nieuwe features zijn toegevoegd. JavaFX heeft zich inmiddels bewezen als een meer dan waardige opvolger voor Swing en het team is nog steeds bezig waardevolle nieuwe features toe te voegen. Het is dan ook niet verrassend dat JavaFX 8 inmiddels is opgenomen als onderdeel van de JRE zelf.

De codevoorbeelden uit dit artikel zijn ook te vinden op Github: https://github.com/TinusTinus/javafx8sandbox.