Libreria Java per la creazione di GUI per Rich Applications multi-piattaforma
SwipeEvent), in funzione della piattaforma in cui l’applicazione è in esecuzioneJFXPanel)SwingNode)JFrame di SwingWindowStage
Stage può mostrare una sola Scene alla volta: si imposta via Stage#setScene(Scene)Scene#setRoot(Parent)init, start, stop, …)start(Stage) che riceve lo stage primariopublic class App extends javafx.application.Application {
@Override
public void start(Stage stage) throws Exception {
Group root = new Group();
Scene scene = new Scene(root, 500, 300);
stage.setTitle("JavaFX Demo");
stage.setScene(scene);
stage.show();
}
}
import javafx.application.Application;
public class Main {
public static void main(String[] args) {
// App è la classe definita nella slide precedente
Application.launch(App.class, args);
}
}
main() dentro la classe App (che estende Application)
può risultare nel seguente errore:
Error: JavaFX runtime components are missing, and are required to run this applicationmain in una classe separata da quella dell’applicazione JavaFXL’avvio mediante Application.launch(App.class) comporta:
App (la classe specificata che estende Application)init()start(javafx.stage.Stage) dell’applicazionePlatform.exit()Platform.isImplicitExit() è true)stop() dell’applicazioneNodeParent rappresenta nodi che possono avere figli (recuperabili via getChildren())Node: SwingNode, Canvas, Parent

val javaFXModules = listOf("base", "controls", "fxml", "swing", "graphics")
val supportedPlatforms = listOf("linux", "mac", "win") // All required for OOP
dependencies {
// Suppressions for SpotBugs
compileOnly("com.github.spotbugs:spotbugs-annotations:4.9.8")
// Example library: Guava. Add what you need (and use the latest version where appropriate).
// implementation("com.google.guava:guava:28.1-jre")
// JavaFX: comment out if you do not need them
val javaFxVersion = "23.0.2"
implementation("org.openjfx:javafx:$javaFxVersion")
for (platform in supportedPlatforms) {
for (module in javaFXModules) {
implementation("org.openjfx:javafx-$module:$javaFxVersion:$platform")
}
}
// The BOM (Bill of Materials) synchronizes all the versions of Junit coherently.
testImplementation(platform("org.junit:junit-bom:6.0.2"))
// The annotations, assertions and other elements we want to have access when compiling our tests.
testImplementation("org.junit.jupiter:junit-jupiter")
// The engine that must be available at runtime to run the tests.
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
App) deve estendere la classe javafx.application.Applicationvoid start(Stage primaryStage) che è, di fatto, l’entry point dell’applicazione JavaFX (lo stage primario è creato dalla piattaforma)setScene())show()main() dell’applicazione Java, che deve chiamare Application.launch(App.class)Ogni scena ha un root node relativo a una gerarchia di nodi descrivente la GUI
Ciascun nodo (componente) espone diverse proprietà osservabili (classe Property<T>)
size, posizion, color, …)text, value, …)controller, …)Ciascun nodo genera eventi in relazione ad azioni dell’utente
public class Example1 extends Application {
@Override
public void start(Stage stage) throws Exception {
final Label lbl = new Label();
lbl.setText("Label text here...");
final Button btn = new Button();
btn.setText("Click me");
final HBox root = new HBox();
root.getChildren().add(btn);
root.getChildren().add(lbl);
stage.setTitle("JavaFX - Example 1");
stage.setScene(new Scene(root, 300, 250));
stage.show();
}
}
Property<T> è un ObservableValue<T> (un valore ottenibile con getValue() a cui possono essere associati dei ChangeListener via remove/addListener) scrivibile che può essere collegato/scollegato ad altri osservabili o proprietà attraverso
bind(ObservableValue<? extends T> observable) / bindBidirectional(Property<T> other)unbind() / unbindBidirectional(Property<T> other)xxx di tipo T ha (opzionalmente) getter/setter getXxx() e setXxx(), e un metodo xxxProperty() che restituisce un oggetto Property<T>
TextField offre getText():String, setText(String), e textProperty():Property<String>final TextField input = new TextField();
final Label mirror = new Label();
// connette la label con il valore del textfield
mirror.textProperty().bindBidirectional(input.textProperty());
mirror.setText("default");
Parent (nodo che può avere nodi figli – cf. proprietà protected children):
Group (gestisce un insieme di figli; ogni trasformazione/effetto è applicata su ogni figlio)Region (classe base per tutti i controlli UI e i layout)
Control (classe base per tutti i controlli UI)layoutX e layoutY dei Node)BorderPane, HBox/VBox, TilePane, GridPane, FLowPane, AnchorPane, StackPane
ObservableList<Node> getChildren() restituisce la lista di nodi figli di un qualunque nodo/layout
add(Node) e addAll(Node...)) e gestiti i componenti figliGroup g = new Group();
Label l1 = new Label("label");
Button b1 = new Button("a larger button");
Button b2 = new Button("small button");
g.getChildren().addAll(l1, b2, b3);
// Use binding to suitable place the components
b1.layoutXProperty().bind(l1.widthProperty().add(10));
b2.layoutXProperty().bind(b1.layoutXProperty()
.add(b1.widthProperty()).add(10));
g.setTranslateX(-5); // applies translation to all children
g.setEffect(new DropShadow()); // applies effect to all children
Text center = new Text("Center"); // ...
BorderPane bpane = new BorderPane(center, top, right, bottom, left);
bpane.setCenter(new Text("NewCenter"));
Button topLeft = new Button("Top Left");
AnchorPane.setTopAnchor(topLeft, 10.0); // 10px from the top edge
AnchorPane.setLeftAnchor(topLeft, 10.0); // 10px from the left edge
AnchorPane root = new AnchorPane(topLeft);
// An empty vertical TilePane with 5px horiz / 10px vertical spacing
TilePane tp2 = new TilePane(Orientation.VERTICAL, 5, 10);
tp2.setPrefRows(3);
tp.setPrefTileHeight(100);
for(Month m : Month.values()) { tp2.getChildren().add(new Label(m.name())); }
GridPane gp = new GridPane();
gp.setGridLinesVisible(true);
for(Month m : Month.values()) {
Label l = new Label(m.name());
gp.getChildren().add(l);
int columnIndex = (m.getValue()-1) / 4; int rowIndex = (m.getValue()-1) % 4;
GridPane.setConstraints(l, columnIndex, rowIndex);
// OR ALSO: gp.add(l, columnIndex, rowIndex);
}

javafx.geometry.Bounds che espone:
getMinX(), getMinY(), getMinZ()getWidth(), getHeight(), getDepth()getMaxX()… come getMinX()+getWidth()…Scene
Region ma non un Group), allora il ridimensionamento della scena causerà un aggiustamento del layoutStage
Node può essere “gestito” (managed) o meno: nel primo caso, il parent ne gestirà il posizionamento/dimensionamento (in base alla preferred size del nodo)javafx.event.Event) possono essere generati dall’interazione dell’utente con gli elementi grafici
consume())EventHandler<T extends Event> deve implementare il metodo void handle(T)setOn...()Buttonbtn.setOnMouseClicked(event -> {
lbl.setText("Hello, JavaFX World!");
});
// same as
btn.addEventHandler(ActionEvent.ACTION, e -> lbl.setText("Hello, JavaFX World!"));
public class App extends Application {
@Override
public final void start(final Stage mainStage) {
final Scene scene = new Scene(initSceneUI());
mainStage.setScene(scene);
mainStage.setTitle("JavaFX Example");
mainStage.show();
}
private Parent initSceneUI() {
final Label inputLbl = new Label("Input: ");
final TextField inputArea = new TextField();
final Button okBtn = new Button("Open a new Stage with the input data!");
okBtn.setOnMouseClicked(event -> {
new SecondStage(inputArea.getText()).show();
});
final BorderPane root = new BorderPane();
root.setRight(okBtn);
root.setLeft(inputLbl);
root.setCenter(inputArea);
BorderPane.setAlignment(inputLbl, Pos.CENTER_LEFT);
BorderPane.setAlignment(okBtn, Pos.CENTER_RIGHT);
return root;
}
}
public class SecondStage extends Stage {
private Label lbl;
public SecondStage(final String message) {
super();
setTitle("New Window...");
setScene(new Scene(initSceneUI(), 400, 200));
lbl.setText(message);
}
private Parent initSceneUI() {
lbl = new Label();
FlowPane root = new FlowPane();
root.setAlignment(Pos.CENTER);
root.getChildren().add(lbl);
return root;
}
}
public class Main {
public static void main(final String[] args) {
Application.launch(App.class, args);
}
}
Application sono eseguiti (ad es. start) oppure no (ad es. init) su JFXATSwingUtilities.invokeLaterScreen s = Screen.getPrimary();
double dpi = s.getDpi();
Rectangle2D sb = s.getBounds();
Ractangle2D svb = s.getVisualBounds();
ObservableList<Screen> screenList = Screen.getScreens();
Ad es., per dimensionare lo stage
stage.xProperty().addListener(x -> {
Screen s = Screen.getScreensForRectangle(
new Rectangle2D(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight())
).get(0);
stage.setMinHeight(...);
stage.setMinWidth(...);
stage.setMaxHeight(s.getVisualBounds().getHeight()/2);
stage.setMaxWidth(s.getVisualBounds().getWidth()/2);
});

Linguaggio di markup basato su XML, utilizzato per descrivere la struttura della GUI (ovvero il scene graph)
Ogni file FXML (con estensione .fxml) deve essere un file XML valido
<?xml version="1.0" encoding="UTF-8"?><?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml">
<children>
<Button fx:id="btn"
alignment="CENTER"
text="Say Hello!"
textAlignment="CENTER" />
<Label fx:id="lbl"
alignment="CENTER_LEFT"
text="Label Text Here!"
textAlignment="LEFT" />
</children>
</VBox>
Attraverso il tag <?import ... ?> è possibile specificare i package in cui recuperare le classi dei componenti d’interesse
import di JavaIl container principale (unico per il singolo file) deve specificare gli attributi xmlns e xmlns:fx
fx raccoglie nodi relativi al processamento interno del descrittore FXMLxmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
<children> e </children>fx:id
<TextField fx:id="textField1"/>javafx.fxml.FXMLLoader
load(URL location)javafx.fxml (si veda ad es. la build Gradle)FXMLLoader (esempio)layouts/main.fxml contenente una descrizione valida per la GUI da caricareParent root = FXMLLoader.load(
ClassLoader.getSystemResource("layouts/main.fxml"));
public class Example3 extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(ClassLoader.getSystemResource("layouts/main.fxml"));
Scene scene = new Scene(root, 500, 250);
stage.setTitle("JavaFX - Example 3");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Il riferimento ai componenti (nodi) inseriti nella GUI definita nel file FXML può essere recuperato tramite la scena a cui la GUI è stata collegata
Metodo Node lookup(String id)
Label lbl = (Label) scene.lookup("#lbl");
Button btn = (Button) scene.lookup("#btn");
btn.setOnMouseClicked(handler -> {
lbl.setText("Hello, FXML!");
});
lookup richiede come parametro l’id specificato per il componente (attributo fx:id nel file FXML) preceduto dal simbolo #fx:controller con valore riferito al nome pienamente qualificato della classe che fungerà da controller@FXML è possibile recuperare:
fx:id) del nodo nel file FXML e il nome della variabile d’istanza annotata nella classe controllerpublic class CompleteExample extends Application {
@Override
public void start(Stage stage) throws Exception {
VBox root = FXMLLoader.load(ClassLoader.getSystemResource("layouts/main.fxml"));
Scene scene = new Scene(root, 500, 250);
stage.setTitle("JavaFX - Complete Example");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox
xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="it.unibo.oop.lab.javafx.UIController">
<children>
<Button fx:id="btn"
alignment="CENTER"
text="Say Hello!"
onMouseClicked="#btnOnClickHandler" />
<Label fx:id="lbl"
alignment="CENTER_LEFT"
text="Label Text Here!" />
</children>
</VBox>
public class UIController {
@FXML
private Label lbl;
@FXML
private Button btn;
@FXML
public void btnOnClickHandler() {
lbl.setText("Hello, World!");
}
}

ToggleButton c’è la classe .toggle-button, e per la proprietà blendMode la proprietà CSS -fx-blend-mode (notare prefisso -fx-)#myButton { -fx-padding: 0.5em; } /* for an individual node */
.label { -fx-font-size: 30pt; } /* for all the labels */
Programmaticamente
Scene scene = new Scene(pane);
scene.getStylesheets().add(ClassLoader.getSystemResource("css/scene.css"));
HBox buttons = new HBox();
buttons.setStyle("-fx-border-color: red;");
buttons.getStyleClass().add("buttonrow");
Nel file FXML (ad esempio attaccandolo al nodo root)
<GridPane id="pane" stylesheets="css/scene.css"> ... </GridPane>
SwingNodeJFXPanelSwingNode e JFXPanel si trovano nel modulo javafx.swingjavafx.application.Platform.runLater(), per eseguire codice nel thread dedicato a JavaFXjavax.swing.SwingUtilities.invokeLater(), per eseguire codice nel thread dedicato a Swingpublic static void main(final String[] args){
initMainJFrame(new JFrame("JFrame GUI"));
}
private static void initMainJFrame(final JFrame frame) {
final JButton button = new JButton();
button.setText("Launch JavaFX Scene");
button.addActionListener(event -> {
final JFXPanel jfxPanel = new JFXPanel();
Platform.runLater(() -> {
jfxPanel.setScene(new Scene(initJavaFXSceneUI(), 300, 300));
SwingUtilities.invokeLater(() -> {
final JFrame frameWithJavaFX = new JFrame("JFrame with JavaFX embedded!");
frameWithJavaFX.add(jfxPanel);
frameWithJavaFX.pack();
frameWithJavaFX.setVisible(true);
}); }); });
final JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.add(button);
frame.setContentPane(panel);
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static Parent initJavaFXSceneUI() {
final Label lbl = new Label();
lbl.setText("Hello, JavaFX World!");
final Button btn = new Button();
btn.setText("Say Hello");
btn.setOnMouseClicked(event -> {
lbl.setText("Hello from Button!");
});
final VBox root = new VBox();
root.getChildren().add(lbl);
root.getChildren().add(btn);
return root;
}
public final class JavaFXAppWithSwing extends Application {
@Override
public void start(final Stage primaryStage) throws Exception {
final SwingNode msg = new SwingNode();
SwingUtilities.invokeLater(() ->
msg.setContent(new JLabel("Hello by Swing JLabel")));
HBox pane = new HBox();
pane.getChildren().add(msg);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
// ...
}