Scene Graph and Nodes in JavaFX: Complete Guide

JavaFX uses a hierarchical scene graph to organize and manage UI components. Understanding the scene graph and node hierarchy is fundamental to building JavaFX applications.

1. Scene Graph Fundamentals

Basic Scene Graph Structure

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.stage.Stage; public class BasicSceneGraph extends Application { @Override public void start(Stage primaryStage) { // Root node (Pane) Pane root = new Pane(); // Create various nodes Circle circle = new Circle(100, 100, 50); circle.setStyle("-fx-fill: red; -fx-stroke: black;"); Rectangle rectangle = new Rectangle(150, 150, 100, 80); rectangle.setStyle("-fx-fill: blue; -fx-stroke: black;"); Text text = new Text(50, 200, "Hello JavaFX Scene Graph!"); text.setStyle("-fx-font-size: 18; -fx-fill: green;"); // Add nodes to the root root.getChildren().addAll(circle, rectangle, text); // Create scene with root node Scene scene = new Scene(root, 400, 300); primaryStage.setTitle("Basic Scene Graph"); primaryStage.setScene(scene); primaryStage.show(); // Print scene graph information printSceneGraphInfo(root); } private void printSceneGraphInfo(Pane root) { System.out.println("Root node: " + root.getClass().getSimpleName()); System.out.println("Number of children: " + root.getChildren().size()); System.out.println("Children types:"); root.getChildren().forEach(node -> System.out.println(" - " + node.getClass().getSimpleName()) ); } public static void main(String[] args) { launch(args); } }

2. Node Properties and Common Methods

Working with Node Properties

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; public class NodePropertiesExample extends Application { @Override public void start(Stage primaryStage) { VBox root = new VBox(10); root.setStyle("-fx-padding: 20; -fx-alignment: center;"); // Create a rectangle and demonstrate various properties Rectangle rect = new Rectangle(100, 100, Color.LIGHTBLUE); // Basic properties rect.setId("main-rectangle"); rect.getStyleClass().add("custom-rect"); // Transformation properties rect.setRotate(45); rect.setScaleX(1.2); rect.setScaleY(1.2); rect.setTranslateX(10); rect.setTranslateY(10); // Visual properties rect.setOpacity(0.8); rect.setVisible(true); rect.setDisable(false); // Stroke properties rect.setStroke(Color.DARKBLUE); rect.setStrokeWidth(3); // Interactive properties rect.setOnMouseClicked(e -> { System.out.println("Rectangle clicked!"); rect.setFill(Color.color(Math.random(), Math.random(), Math.random())); }); // Create control buttons Button btnRotate = new Button("Rotate"); btnRotate.setOnAction(e -> rect.setRotate(rect.getRotate() + 15)); Button btnScale = new Button("Toggle Scale"); btnScale.setOnAction(e -> { rect.setScaleX(rect.getScaleX() == 1.0 ? 1.5 : 1.0); rect.setScaleY(rect.getScaleY() == 1.0 ? 1.5 : 1.0); }); Button btnOpacity = new Button("Toggle Opacity"); btnOpacity.setOnAction(e -> rect.setOpacity(rect.getOpacity() == 1.0 ? 0.5 : 1.0) ); // Display node information Button btnInfo = new Button("Show Node Info"); btnInfo.setOnAction(e -> displayNodeInfo(rect)); root.getChildren().addAll(rect, btnRotate, btnScale, btnOpacity, btnInfo); Scene scene = new Scene(root, 300, 400); primaryStage.setTitle("Node Properties"); primaryStage.setScene(scene); primaryStage.show(); } private void displayNodeInfo(Rectangle node) { System.out.println("=== Node Information ==="); System.out.println("ID: " + node.getId()); System.out.println("Style Classes: " + node.getStyleClass()); System.out.println("Bounds: " + node.getBoundsInLocal()); System.out.println("Layout Bounds: " + node.getLayoutBounds()); System.out.println("Opacity: " + node.getOpacity()); System.out.println("Rotation: " + node.getRotate()); System.out.println("Scale: " + node.getScaleX() + ", " + node.getScaleY()); System.out.println("Translate: " + node.getTranslateX() + ", " + node.getTranslateY()); System.out.println("Visible: " + node.isVisible()); System.out.println("Disabled: " + node.isDisabled()); } public static void main(String[] args) { launch(args); } }

3. Parent and Child Node Relationships

Managing Node Hierarchy

import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import java.util.List; public class NodeHierarchyExample extends Application { private Group mainGroup; private VBox infoPanel; private int nodeCounter = 0; @Override public void start(Stage primaryStage) { BorderPane root = new BorderPane(); // Create main container for shapes mainGroup = new Group(); mainGroup.setStyle("-fx-border-color: gray; -fx-border-width: 2;"); // Create control panel HBox controlPanel = createControlPanel(); // Create information panel infoPanel = new VBox(5); infoPanel.setStyle("-fx-padding: 10; -fx-background-color: lightgray;"); root.setTop(controlPanel); root.setCenter(mainGroup); root.setRight(infoPanel); Scene scene = new Scene(root, 800, 600); primaryStage.setTitle("Node Hierarchy Management"); primaryStage.setScene(scene); primaryStage.show(); } private HBox createControlPanel() { HBox controls = new HBox(10); controls.setStyle("-fx-padding: 10; -fx-alignment: center;"); Button addCircle = new Button("Add Circle"); addCircle.setOnAction(e -> addCircle()); Button addRectangle = new Button("Add Rectangle"); addRectangle.setOnAction(e -> addRectangle()); Button removeLast = new Button("Remove Last"); removeLast.setOnAction(e -> removeLastNode()); Button clearAll = new Button("Clear All"); clearAll.setOnAction(e -> clearAllNodes()); Button printHierarchy = new Button("Print Hierarchy"); printHierarchy.setOnAction(e -> printNodeHierarchy()); controls.getChildren().addAll(addCircle, addRectangle, removeLast, clearAll, printHierarchy); return controls; } private void addCircle() { nodeCounter++; Circle circle = new Circle(100 + Math.random() * 400, 100 + Math.random() * 300, 30 + Math.random() * 50); circle.setFill(Color.color(Math.random(), Math.random(), Math.random())); circle.setId("circle-" + nodeCounter); circle.setOnMouseClicked(e -> selectNode(circle)); mainGroup.getChildren().add(circle); updateInfoPanel(); } private void addRectangle() { nodeCounter++; Rectangle rect = new Rectangle(100 + Math.random() * 400, 100 + Math.random() * 300, 60 + Math.random() * 80, 40 + Math.random() * 60); rect.setFill(Color.color(Math.random(), Math.random(), Math.random())); rect.setId("rectangle-" + nodeCounter); rect.setOnMouseClicked(e -> selectNode(rect)); mainGroup.getChildren().add(rect); updateInfoPanel(); } private void removeLastNode() { List<Node> children = mainGroup.getChildren(); if (!children.isEmpty()) { children.remove(children.size() - 1); updateInfoPanel(); } } private void clearAllNodes() { mainGroup.getChildren().clear(); updateInfoPanel(); } private void selectNode(Node node) { // Highlight selected node mainGroup.getChildren().forEach(n -> { if (n instanceof Rectangle) ((Rectangle) n).setStroke(null); if (n instanceof Circle) ((Circle) n).setStroke(null); }); if (node instanceof Rectangle) ((Rectangle) node).setStroke(Color.RED); if (node instanceof Circle) ((Circle) node).setStroke(Color.RED); displayNodeDetails(node); } private void displayNodeDetails(Node node) { infoPanel.getChildren().clear(); Label title = new Label("Selected Node:"); title.setStyle("-fx-font-weight: bold; -fx-font-size: 14;"); Label idLabel = new Label("ID: " + node.getId()); Label typeLabel = new Label("Type: " + node.getClass().getSimpleName()); Label boundsLabel = new Label("Bounds: " + node.getBoundsInLocal()); infoPanel.getChildren().addAll(title, idLabel, typeLabel, boundsLabel); } private void updateInfoPanel() { Label countLabel = new Label("Total Nodes: " + mainGroup.getChildren().size()); countLabel.setStyle("-fx-font-weight: bold;"); infoPanel.getChildren().clear(); infoPanel.getChildren().add(countLabel); } private void printNodeHierarchy() { System.out.println("=== Scene Graph Hierarchy ==="); System.out.println("Root: " + mainGroup.getClass().getSimpleName()); System.out.println("Children count: " + mainGroup.getChildren().size()); mainGroup.getChildren().forEach(node -> { System.out.println(" └── " + node.getClass().getSimpleName() + " (ID: " + node.getId() + ")"); }); } public static void main(String[] args) { launch(args); } }

4. Transformations and Effects

Applying Transformations to Nodes

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Slider; import javafx.scene.effect.*; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.transform.Rotate; import javafx.scene.transform.Scale; import javafx.scene.transform.Shear; import javafx.scene.transform.Translate; import javafx.stage.Stage; public class TransformationsAndEffects extends Application { private Rectangle rectangle; private Rotate rotate; private Scale scale; private Translate translate; private Shear shear; @Override public void start(Stage primaryStage) { VBox root = new VBox(20); root.setStyle("-fx-padding: 20; -fx-alignment: center;"); // Create a rectangle to transform rectangle = new Rectangle(100, 100, Color.LIGHTBLUE); rectangle.setStroke(Color.DARKBLUE); rectangle.setStrokeWidth(2); // Initialize transformations initializeTransformations(); // Create transformation controls VBox controls = createTransformationControls(); // Create effect controls VBox effectControls = createEffectControls(); HBox controlPanels = new HBox(20, controls, effectControls); root.getChildren().addAll(rectangle, controlPanels); Scene scene = new Scene(root, 600, 500); primaryStage.setTitle("Transformations and Effects"); primaryStage.setScene(scene); primaryStage.show(); } private void initializeTransformations() { rotate = new Rotate(0, 50, 50); // pivot at center scale = new Scale(1, 1, 50, 50); translate = new Translate(0, 0); shear = new Shear(0, 0, 50, 50); rectangle.getTransforms().addAll(rotate, scale, translate, shear); } private VBox createTransformationControls() { VBox controls = new VBox(10); // Rotation control Slider rotationSlider = new Slider(0, 360, 0); rotationSlider.valueProperty().addListener((obs, oldVal, newVal) -> rotate.setAngle(newVal.doubleValue()) ); // Scale control Slider scaleSlider = new Slider(0.1, 3, 1); scaleSlider.valueProperty().addListener((obs, oldVal, newVal) -> { scale.setX(newVal.doubleValue()); scale.setY(newVal.doubleValue()); }); // Translation controls Slider translateXSlider = new Slider(-100, 100, 0); translateXSlider.valueProperty().addListener((obs, oldVal, newVal) -> translate.setX(newVal.doubleValue()) ); Slider translateYSlider = new Slider(-100, 100, 0); translateYSlider.valueProperty().addListener((obs, oldVal, newVal) -> translate.setY(newVal.doubleValue()) ); // Shear controls Slider shearXSlider = new Slider(-1, 1, 0); shearXSlider.valueProperty().addListener((obs, oldVal, newVal) -> shear.setX(newVal.doubleValue()) ); Slider shearYSlider = new Slider(-1, 1, 0); shearYSlider.valueProperty().addListener((obs, oldVal, newVal) -> shear.setY(newVal.doubleValue()) ); Button resetBtn = new Button("Reset Transformations"); resetBtn.setOnAction(e -> resetTransformations()); controls.getChildren().addAll( new VBox(5, new Label("Rotation:"), rotationSlider), new VBox(5, new Label("Scale:"), scaleSlider), new VBox(5, new Label("Translate X:"), translateXSlider), new VBox(5, new Label("Translate Y:"), translateYSlider), new VBox(5, new Label("Shear X:"), shearXSlider), new VBox(5, new Label("Shear Y:"), shearYSlider), resetBtn ); return controls; } private VBox createEffectControls() { VBox controls = new VBox(10); Button shadowBtn = new Button("Drop Shadow"); shadowBtn.setOnAction(e -> rectangle.setEffect(new DropShadow(10, Color.BLACK)) ); Button glowBtn = new Button("Glow"); glowBtn.setOnAction(e -> rectangle.setEffect(new Glow(0.8)) ); Button blurBtn = new Button("Gaussian Blur"); blurBtn.setOnAction(e -> rectangle.setEffect(new GaussianBlur(10)) ); Button reflectionBtn = new Button("Reflection"); reflectionBtn.setOnAction(e -> { Reflection reflection = new Reflection(); reflection.setFraction(0.7); rectangle.setEffect(reflection); }); Button lightingBtn = new Button("Lighting"); lightingBtn.setOnAction(e -> rectangle.setEffect(new Lighting()) ); Button noEffectBtn = new Button("No Effect"); noEffectBtn.setOnAction(e -> rectangle.setEffect(null)); controls.getChildren().addAll( shadowBtn, glowBtn, blurBtn, reflectionBtn, lightingBtn, noEffectBtn ); return controls; } private void resetTransformations() { rotate.setAngle(0); scale.setX(1); scale.setY(1); translate.setX(0); translate.setY(0); shear.setX(0); shear.setY(0); rectangle.setEffect(null); } public static void main(String[] args) { launch(args); } }

5. Custom Node Implementation

Creating Custom Nodes

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.*; import javafx.stage.Stage; // Custom node that represents a traffic light class TrafficLight extends Pane { private Circle redLight; private Circle yellowLight; private Circle greenLight; private Rectangle housing; public TrafficLight() { createTrafficLight(); setPrefSize(80, 200); } private void createTrafficLight() { // Create housing housing = new Rectangle(60, 180, Color.DARKGRAY); housing.setArcWidth(20); housing.setArcHeight(20); housing.setStroke(Color.BLACK); housing.setStrokeWidth(2); // Create lights redLight = createLight(30, 30, Color.DARKRED); yellowLight = createLight(30, 90, Color.DARKGOLDENROD); greenLight = createLight(30, 150, Color.DARKGREEN); getChildren().addAll(housing, redLight, yellowLight, greenLight); } private Circle createLight(double x, double y, Color color) { Circle light = new Circle(x, y, 20, color); light.setStroke(Color.BLACK); light.setStrokeWidth(2); return light; } public void setRed(boolean on) { redLight.setFill(on ? Color.RED : Color.DARKRED); } public void setYellow(boolean on) { yellowLight.setFill(on ? Color.YELLOW : Color.DARKGOLDENROD); } public void setGreen(boolean on) { greenLight.setFill(on ? Color.LIMEGREEN : Color.DARKGREEN); } public void setAllOff() { setRed(false); setYellow(false); setGreen(false); } } // Custom node for a progress indicator class CircularProgress extends Pane { private Circle background; private Arc progressArc; private Label percentageLabel; private double progress = 0; public CircularProgress() { createProgressIndicator(); setPrefSize(120, 120); } private void createProgressIndicator() { // Background circle background = new Circle(60, 60, 50); background.setFill(null); background.setStroke(Color.LIGHTGRAY); background.setStrokeWidth(8); // Progress arc progressArc = new Arc(60, 60, 50, 50, 90, 0); progressArc.setFill(null); progressArc.setStroke(Color.BLUE); progressArc.setStrokeWidth(8); progressArc.setType(ArcType.OPEN); // Percentage label percentageLabel = new Label("0%"); percentageLabel.setStyle("-fx-font-size: 16; -fx-font-weight: bold;"); percentageLabel.layoutXProperty().bind(widthProperty().divide(2).subtract(20)); percentageLabel.layoutYProperty().bind(heightProperty().divide(2).subtract(10)); getChildren().addAll(background, progressArc, percentageLabel); } public void setProgress(double progress) { this.progress = Math.max(0, Math.min(1, progress)); updateProgressDisplay(); } public double getProgress() { return progress; } private void updateProgressDisplay() { double angle = progress * 360; progressArc.setLength(-angle); // Negative for clockwise int percentage = (int) (progress * 100); percentageLabel.setText(percentage + "%"); // Change color based on progress if (progress < 0.3) { progressArc.setStroke(Color.RED); } else if (progress < 0.7) { progressArc.setStroke(Color.ORANGE); } else { progressArc.setStroke(Color.GREEN); } } } public class CustomNodesExample extends Application { @Override public void start(Stage primaryStage) { VBox root = new VBox(20); root.setStyle("-fx-padding: 20; -fx-alignment: center;"); // Create custom traffic light TrafficLight trafficLight = new TrafficLight(); // Create control buttons for traffic light VBox trafficLightControls = createTrafficLightControls(trafficLight); // Create custom progress indicator CircularProgress progress = new CircularProgress(); // Create control for progress VBox progressControls = createProgressControls(progress); HBox customNodes = new HBox(40, new VBox(10, trafficLight, trafficLightControls), new VBox(10, progress, progressControls) ); root.getChildren().addAll( new Label("Custom JavaFX Nodes"), customNodes ); Scene scene = new Scene(root, 500, 400); primaryStage.setTitle("Custom Nodes Example"); primaryStage.setScene(scene); primaryStage.show(); } private VBox createTrafficLightControls(TrafficLight trafficLight) { VBox controls = new VBox(5); javafx.scene.control.Button redBtn = new javafx.scene.control.Button("Red"); redBtn.setOnAction(e -> { trafficLight.setAllOff(); trafficLight.setRed(true); }); javafx.scene.control.Button yellowBtn = new javafx.scene.control.Button("Yellow"); yellowBtn.setOnAction(e -> { trafficLight.setAllOff(); trafficLight.setYellow(true); }); javafx.scene.control.Button greenBtn = new javafx.scene.control.Button("Green"); greenBtn.setOnAction(e -> { trafficLight.setAllOff(); trafficLight.setGreen(true); }); javafx.scene.control.Button offBtn = new javafx.scene.control.Button("All Off"); offBtn.setOnAction(e -> trafficLight.setAllOff()); controls.getChildren().addAll(redBtn, yellowBtn, greenBtn, offBtn); return controls; } private VBox createProgressControls(CircularProgress progress) { VBox controls = new VBox(5); javafx.scene.control.Slider progressSlider = new javafx.scene.control.Slider(0, 1, 0); progressSlider.valueProperty().addListener((obs, oldVal, newVal) -> progress.setProgress(newVal.doubleValue()) ); javafx.scene.control.Button animateBtn = new javafx.scene.control.Button("Animate"); animateBtn.setOnAction(e -> animateProgress(progress)); controls.getChildren().addAll( new Label("Progress:"), progressSlider, animateBtn ); return controls; } private void animateProgress(CircularProgress progress) { new Thread(() -> { for (int i = 0; i <= 100; i++) { double p = i / 100.0; javafx.application.Platform.runLater(() -> progress.setProgress(p)); try { Thread.sleep(30); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }).start(); } public static void main(String[] args) { launch(args); } }

6. Node Event Handling

Event Handling in Scene Graph

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.input.*; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; public class NodeEventHandling extends Application { private Label eventLog; private Pane drawingArea; private Circle draggableCircle; @Override public void start(Stage primaryStage) { VBox root = new VBox(10); root.setStyle("-fx-padding: 20;"); // Event log eventLog = new Label("Events will appear here..."); eventLog.setStyle("-fx-border-color: gray; -fx-padding: 10;"); eventLog.setPrefHeight(100); eventLog.setWrapText(true); // Drawing area for interactive nodes drawingArea = new Pane(); drawingArea.setStyle("-fx-border-color: lightgray; -fx-background-color: white;"); drawingArea.setPrefSize(400, 300); // Create interactive nodes createInteractiveNodes(); root.getChildren().addAll( new Label("Node Event Handling Demo"), eventLog, drawingArea ); Scene scene = new Scene(root, 500, 500); // Add scene-level event handlers setupSceneEventHandlers(scene); primaryStage.setTitle("Node Event Handling"); primaryStage.setScene(scene); primaryStage.show(); } private void createInteractiveNodes() { // Clickable rectangle Rectangle clickableRect = new Rectangle(50, 50, 100, 80); clickableRect.setFill(Color.LIGHTBLUE); clickableRect.setStroke(Color.DARKBLUE); clickableRect.setOnMouseClicked(e -> { logEvent("Rectangle clicked at: " + e.getX() + ", " + e.getY()); clickableRect.setFill(Color.color(Math.random(), Math.random(), Math.random())); }); clickableRect.setOnMouseEntered(e -> { logEvent("Mouse entered rectangle"); clickableRect.setStroke(Color.RED); }); clickableRect.setOnMouseExited(e -> { logEvent("Mouse exited rectangle"); clickableRect.setStroke(Color.DARKBLUE); }); // Draggable circle draggableCircle = new Circle(200, 100, 30, Color.LIGHTGREEN); draggableCircle.setStroke(Color.DARKGREEN); setupDraggableNode(draggableCircle); // Key-responsive node Rectangle keyResponsiveRect = new Rectangle(300, 50, 80, 80); keyResponsiveRect.setFill(Color.LIGHTCORAL); keyResponsiveRect.setStroke(Color.DARKRED); setupKeyResponsiveNode(keyResponsiveRect); drawingArea.getChildren().addAll(clickableRect, draggableCircle, keyResponsiveRect); } private void setupDraggableNode(Circle node) { final double[] offset = new double[2]; node.setOnMousePressed(e -> { offset[0] = node.getCenterX() - e.getSceneX(); offset[1] = node.getCenterY() - e.getSceneY(); logEvent("Started dragging circle"); }); node.setOnMouseDragged(e -> { node.setCenterX(e.getSceneX() + offset[0]); node.setCenterY(e.getSceneY() + offset[1]); }); node.setOnMouseReleased(e -> { logEvent("Stopped dragging circle"); }); } private void setupKeyResponsiveNode(Rectangle node) { node.setFocusTraversable(true); node.setOnKeyPressed(e -> { switch (e.getCode()) { case UP -> node.setY(node.getY() - 10); case DOWN -> node.setY(node.getY() + 10); case LEFT -> node.setX(node.getX() - 10); case RIGHT -> node.setX(node.getX() + 10); case R -> node.setFill(Color.color(Math.random(), Math.random(), Math.random())); } logEvent("Key pressed: " + e.getCode() + " on rectangle"); }); node.setOnMouseClicked(e -> node.requestFocus()); } private void setupSceneEventHandlers(Scene scene) { // Mouse events scene.setOnMouseMoved(e -> { // Update cursor position in status (but don't log every movement) }); scene.setOnMouseClicked(e -> { if (e.getTarget() == scene) { logEvent("Clicked on scene background"); } }); // Key events scene.setOnKeyPressed(e -> { switch (e.getCode()) { case C -> drawingArea.getChildren().clear(); case A -> addRandomNode(); case DIGIT1 -> logEvent("Pressed 1 - Scene level handler"); } }); // Scroll events scene.setOnScroll(e -> { double zoomFactor = 1.05; if (e.getDeltaY() < 0) { zoomFactor = 0.95; } drawingArea.setScaleX(drawingArea.getScaleX() * zoomFactor); drawingArea.setScaleY(drawingArea.getScaleY() * zoomFactor); logEvent("Zoom: " + String.format("%.2f", drawingArea.getScaleX())); }); } private void addRandomNode() { Circle circle = new Circle( Math.random() * 350 + 25, Math.random() * 250 + 25, 20 + Math.random() * 30, Color.color(Math.random(), Math.random(), Math.random()) ); circle.setOnMouseClicked(e -> { drawingArea.getChildren().remove(circle); logEvent("Removed circle"); }); drawingArea.getChildren().add(circle); logEvent("Added random circle"); } private void logEvent(String message) { String currentText = eventLog.getText(); String newText = "• " + message + "\n" + currentText; // Keep only last 10 events String[] lines = newText.split("\n"); if (lines.length > 10) { newText = String.join("\n", java.util.Arrays.copyOf(lines, 10)); } eventLog.setText(newText); } public static void main(String[] args) { launch(args); } }

Key Concepts Summary

  1. Scene Graph: Tree structure where each node can have children
  2. Node Properties: ID, style classes, transformations, effects
  3. Parent-Child Relationships: Nodes inherit transformations from parents
  4. Coordinate Systems: Local, parent, and scene coordinates
  5. Event Propagation: Events bubble up through the scene graph
  6. Custom Nodes: Can create reusable custom UI components

This comprehensive guide covers the essential aspects of JavaFX scene graph and nodes, from basic concepts to advanced custom implementations and event handling.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper