import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.text.Text; import javafx.stage.Stage; public class FXDemo extends Application { @Override public void start(Stage stage) { Text text = new Text("Hello World!"); text.setY(text.getFont().getSize()); Scene scene = new Scene(new Group(text)); stage.setTitle("Welcome to JavaFX!"); stage.setScene(scene); stage.sizeToScene(); stage.show(); } public static void main(String[] args) { launch(args); } }
Node
. Branch nodes are of type Parent
, whose concrete subclasses are Group
, Region
, and Control
, or subclasses thereof.
Leaf nodes are classes such as Rectangle
, Text
, ImageView
, MediaView
, or other such leaf classes which cannot have children. Only a single node within each scene graph tree will have no parent, which is referred to as the "root" node.
There may be several trees in the scene graph. Some trees may be part of a Scene
, in which case they are eligible to be displayed. Other trees might not be part of any Scene
.
A node may occur at most once anywhere in the scene graph. Specifically, a node must appear no more than once in all of the following: as the root node of a Scene
, the children ObservableList
of a Parent
, or as the clip of a Node
.
The scene graph must not have cycles. A cycle would exist if a node is an ancestor of itself in the tree, considering the Group
content ObservableList
, Parent
children ObservableList
, and Node
clip relationships mentioned above.
If a program adds a child node to a Parent (including Group, Region, etc) and that node is already a child of a different Parent or the root of a Scene, the node is automatically (and silently) removed from its former parent. If a program attempts to modify the scene graph in any other way that violates the above rules, an exception is thrown, the modification attempt is ignored and the scene graph is restored to its previous state.
<groupId>de.gsi.cs.co<groupId> <artifactId>csco-java-fx-template<artifactId>The archetype uses our usual "csco-parent-java-bundle". So far all the packaging seems to work fine. There is also a maven plugin dedicated to build JavaFX applications for different platforms and has also support for packaging native installers/executables:
<dependency> <groupId>com.zenjava</groupId> <artifactId>javafx-maven-plugin</artifactId> <version>8.1.2</version> </dependency>So far it seems not to be necessary to use this plugin to deploy a JavaFX application in the CSCO environment.
// We can modify the trees of the scene after it is shown // Lets draw a line and add it to a group Line line = new Line(10, 10, 50, 50); line.setStroke(Color.RED); group.getChildren().add(line);
HBox
- Horizontal layout of children.
VBox
- Vertical layout of children.
FlowPane
- Nodes flow horizontal and wrap to the next line if not enough space is available.
BorderPane
- Allows child nodes to be places top, bottom, left, right or centered.
GridPane
- Allows to align children in a grid, constraints can be specified on a row, column or cell level.
@Override public void start(Stage stage) { HBox hBox = new HBox(); hBox.setBackground(new Background(new BackgroundFill (Color.BEIGE, null, null))); hBox.getChildren().add(new Label("hBox")); VBox vBox = new VBox(); vBox.setBackground(new Background(new BackgroundFill (Color.RED, null, null))); vBox.getChildren().add(new Label("vBox1")); vBox.getChildren().add(new Label("vBox2")); vBox.getChildren().add(new Label("vBox3")); hBox.getChildren().add(vBox); FlowPane flowPane = new FlowPane (); flowPane.setBackground(new Background(new BackgroundFill (Color.GREEN, null, null))); for (int i = 1; i < 20; i ++) { flowPane.getChildren().add(new Label(" flowPane" + i)); } hBox.getChildren().add(flowPane); BorderPane borderPane = new BorderPane (); borderPane.setBackground(new Background(new BackgroundFill (Color.BLUE, null, null))); borderPane.getChildren().add(new Label("borderPane")); hBox.getChildren().add(borderPane); GridPane gridPane = new GridPane (); gridPane.setBackground(new Background(new BackgroundFill (Color.YELLOW, null, null))); gridPane.getChildren().add(new Label("gridPane")); hBox.getChildren().add(gridPane); Scene scene = new Scene(hBox, 800, 600); stage.setTitle("Welcome to JavaFX!"); stage.setScene(scene); stage.sizeToScene(); stage.centerOnScreen(); stage.show(); }
final static class Counter { private static int buttonPressedCount = 0; public static int buttonPressed() { buttonPressedCount++; return buttonPressedCount; } } @Override public void start(Stage stage) { HBox hBox = new HBox(); hBox.setBackground(new Background(new BackgroundFill (Color.BEIGE, null, null))); hBox.setAlignment(Pos.CENTER); Button button = new Button("Press me!"); button.setTextAlignment(TextAlignment.CENTER); button.setRotate(-45); Label label = new Label("Button pressed 0 times"); label.setTextAlignment(TextAlignment.CENTER); label.setScaleX(2); label.setScaleY(2); label.setRotate(45); button.setOnAction((event) -> { label.setText("Button pressed " + Counter.buttonPressed() + " times"); }); hBox.getChildren().add(button); hBox.getChildren().add(label); Scene scene = new Scene(hBox, 800, 600); stage.setTitle("Welcome to JavaFX!"); stage.setScene(scene); stage.sizeToScene(); stage.centerOnScreen(); stage.show(); }
<!--?xml version="1.0" encoding="UTF-8"?--> <!--?import java.lang.*?--> <!--?import javafx.scene.control.*?--> <!--?import javafx.scene.layout.*?--> <!--?import javafx.scene.layout.AnchorPane?--> <AnchorPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="de.gsi.cs.co.TestController"> <children> <Button fx:id="idOfMyButton" mnemonicParsing="false" onAction="#buttonActionHappened" text="Button " /> </children> </AnchorPane>This file holds an
AnchorPane
as the top most element. The AnchorPane
has a Button as its only child.
The 'import'
instructions at the top of the file inform the consumer which classes need to be available on the class path.
The 'fx:controller'
attribute defines the Java Class that holds the application logic.
The 'fx:id'
attribute allows us to assign an identifier to an element so we can later access it in the Java code.
Attributes that are not starting with 'fx'
or are namespace attributes 'xmlns'
are usually properties that are set, like e.g. the 'text'
of the button.
package de.gsi.cs.co; import java.net.URL; import java.util.ResourceBundle; import javafx.fxml.FXML; import javafx.scene.control.Button; public class TestController { @FXML private ResourceBundle resources; @FXML private URL location; @FXML private Button idOfMyButton; @FXML void buttonActionHappened(ActionEvent event) { // Event handling happens here } @FXML void initialize() { // Initialization happens here assert idOfMyButton != null : "fx:id=\"idOfMyButton\" was not injected: check your FXML file."; } }It must have fields that match the fx:ids that we used in the FXML file, as well as methods and signature that match the ones we specified as event handlers in the FXML. JavaFX needs to now which fields and methods should be used. If the
@FXML
annotation is applied to a field it tells JavaFX that this field can be used as an fx:id
. If it is applied to a method this method can be used as an event handler.
Properly annotated fields and methods are also shown in the SceneBuilder, this way the controller can be 'wired' with the UI.
public void start(Stage stage) throws Exception { // Load the FXML (instantiates also the controller) String fxmlFile = "/de/gsi/cs/co/test.fxml"; FXMLLoader loader = new FXMLLoader(); Parent rootNode = (Parent) loader.load(getClass().getResourceAsStream(fxmlFile)); TestController testController = loader.getController(); // Add the FXML nodes to a scene and show them Scene scene = new Scene(rootNode); stage.setScene(scene); stage.show(); }Since we specified a controller in the FXML file the load also creates an instance of TestController and assignes the nodes to the fields according to the fx:id that we specified. This is called 'injecting' the nodes into the controller and also hooks up the event handler methods (for a short introduction look at http://en.wikipedia.org/wiki/Dependency_injection ).
.button { -fx-background-color: #DD0000; } .button:hover{ -fx-background-color: #FF0000; }
// ... // creating the (root)Nodes was omitted here // ... Scene scene = new Scene(rootNode); scene.getStylesheets().add("/de/gsi/cs/co/testStyles.css"); stage.setScene(scene); stage.show();'getStylesheets().add(...)' only applies a stylesheet to one Scene. If you want that a stylesheet is applied by default to all scenes owned by an application you have to use 'Appliation.setUserAgentStylesheet(...)' instead. A detailed information which style attributes are available can be accessed at the Oracle site: http://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html
SimpleBooleanProperty
and =ReadOnlyBooleanWrapper.
// Read/Write Property StringProperty password = new SimpleStringProperty ("initialpassword"); password.set("newpassword"); System.out.println("Current password is " + password.get() ); // Readonly Property ReadOnlyStringWrapper userName = new ReadOnlyStringWrapper ("sheldon"); ReadOnlyProperty readOnlyUserName = userName.getReadOnlyProperty();
private StringProperty password; // Initialize this in the constructor public final String getPassword() { return password.get(); } public final void setPassword(String password) { this.password.set(password) } public StringProperty passwordProperty() { return password; }
addListener(...)
method. This enables the programmer to add handler code that will respond when a property changes. The method will accept two functional interfaces, ChangeListener
and InvalidationListener
.
private StringProperty example; // Initialize this in the constructor // Anonymous class example.addListener(new ChangeListener () { @Override public void changed(ObservableValue o, Object oldVal, Object newVal) { System.out.println("example has changed!"); } }); // Lambda example.addListener( (observableValue, oldValue, newValue) -> { System.out.println("example has changed!"); });
public final static class Contact { private static StringProperty firstName = new SimpleStringProperty (); private static StringProperty lastName = new SimpleStringProperty (); public final static StringProperty firstNameProperty() { return firstName; } public final static StringProperty lastNameProperty() { return lastName; } } public final static void main(String[] args) { // We bind the name of "Contact" with a local variable SimpleStringProperty localName = new SimpleStringProperty (); localName.bindBidirectional(Contact.firstNameProperty()); // We change the local variable localName.set("Raphael"); // and the bound variable of "Contact" changed System.out.println(Contact.firstNameProperty()); }
// Area = width * height IntegerProperty width = new SimpleIntegerProperty (2); IntegerProperty height= new SimpleIntegerProperty (5); NumberBinding area = width.multiply(height);
DoubleProperty radius = new SimpleDoubleProperty (2); DoubleBinding volumeOfSphere = new DoubleBinding () { { // Bind double to radius super.bind(radius); } @Override protected double computeValue() { return (4/3 * Math.PI * Math.pow(radius.get()), 3)); } };
// Inject observable into ListView ListView<String> listView = new ListView<String>(); ObservableList<String> observableContactList = FXCollections.observableArrayList(); listView.setItems(observableContactList); // Automatically updates UI observableContactList.add("Raphael");
// when the first JFXPanel object is created, it implicitly initializes the JavaFX runtime. JFXPanel jfxPanel = new JFXPanel(); jfxPanel.setScene(scene); // Create a swing frame and add the panel JFrame jFrame = new JFrame(); jFrame.add(jfxPanel); jFrame.pack(); jFrame.setVisible(true);
// Create a SwingNode with a Button as content SwingNode swingNode = new SwingNode (); swingNode.setContent(new JButton("Click me!")); // Add it to a scenen and display it Scene scene = new Scene(new AnchorPane (swingNode), 800, 600); stage.setScene(scene); stage.sizeToScene(); stage.centerOnScreen(); stage.show();