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