2026/01/12

JavaFX 08 webview

WebView 是一個可以嵌入網頁瀏覽功能的元件,使用內建的 WebKit 引擎來顯示 HTML 內容(包括 JavaScript、CSS 等)。這對於桌面應用程式需要嵌入網頁內容(如:顯示文件、使用 Web UI 元件)時非常有用。

但該 WebKit engine 不是最新版本,雖然支援 html5/css/javascript,但不支援新的標準,不支援多個分頁,沒有 js console 開發者工具,無法使用 react/vue 這些框架,不能跟 javafx node 直接互動(無法將 node 插入 html DOM)。

webview 裡面的頁面跟 js,可以跟 java code 互動

以下是一個雙向互動的 sample

js 可可

package javafx.webview;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

public class WebViewDemo extends Application {

    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        WebEngine webEngine = webView.getEngine();

        // 載入 HTML 字串
        String html = """
            <html>
            <head>
                <script>
                    function callJava() {
                        javaApp.showMessage("Hello from JavaScript!");
                    }

                    function fromJava(data) {
                        document.getElementById("result").innerText = "Java says: " + data;
                    }
                </script>
            </head>
            <body>
                <h2>JavaFX WebView 雙向互動</h2>
                <button onclick="callJava()">呼叫 Java 方法</button>
                <p id="result"></p>
            </body>
            </html>
            """;

        webEngine.loadContent(html);

        // 頁面載入完成後建立 bridge
        webEngine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
            if (newState == javafx.concurrent.Worker.State.SUCCEEDED) {
                JSObject window = (JSObject) webEngine.executeScript("window");
                window.setMember("javaApp", new JavaBridge(webEngine));
            }
        });

        BorderPane root = new BorderPane(webView);
        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.setTitle("JavaFX WebView 雙向互動範例");
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    // Java Bridge 類別
    public static class JavaBridge {
        private final WebEngine webEngine;

        public JavaBridge(WebEngine webEngine) {
            this.webEngine = webEngine;
        }

        // JS 呼叫這個方法
        public void showMessage(String msg) {
            System.out.println("JavaScript 傳來訊息: " + msg);
            // Java 反呼叫 JS 的 function
            webEngine.executeScript("fromJava('收到: " + msg + "')");
        }
    }
}

2026/01/05

JavaFX 07 chart

用 javafx 繪製圖表的工具

  • 圓餅圖 PieChart 無需軸
  • 折線圖 LineChart<X,Y> 通常 CategoryAxis/NumberAxis
  • 面積圖 AreaChart<X,Y> 同折線圖
  • 長條圖 BarChart<X,Y> 通常 CategoryAxis/NumberAxis
  • 氣泡圖 BubbleChart<Number,Number> 用第三值表示半徑
  • 散點圖 ScatterChart<Number,Number> 點陣圖
  • 累積面積圖 StackedAreaChart<X,Y> 多組堆疊數據區域
  • 累積長條圖 StackedBarChart<X,Y> 長條堆疊比較
package javafx.chart;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

//圖表類型    JavaFX 類別名稱    X/Y 軸類型
//圓餅圖    PieChart    無需軸
//折線圖    LineChart<X,Y>    通常 CategoryAxis/NumberAxis
//面積圖    AreaChart<X,Y>    同折線圖
//長條圖    BarChart<X,Y>    通常 CategoryAxis/NumberAxis
//氣泡圖    BubbleChart<Number,Number>    用第三值表示半徑
//散點圖    ScatterChart<Number,Number>    點陣圖
//累積面積圖    StackedAreaChart<X,Y>    多組堆疊數據區域
//累積長條圖    StackedBarChart<X,Y>    長條堆疊比較

public class AllChartsDemo extends Application {

    private BorderPane root;
    private ComboBox<String> chartSelector;

    @Override
    public void start(Stage primaryStage) {
        root = new BorderPane();
        root.setPadding(new Insets(10));

        chartSelector = new ComboBox<>();
        chartSelector.getItems().addAll(
                "PieChart", "LineChart", "AreaChart", "BarChart",
                "BubbleChart", "ScatterChart", "StackedAreaChart", "StackedBarChart"
        );
        chartSelector.setValue("PieChart");
        chartSelector.setOnAction(e -> updateChart(chartSelector.getValue()));

        root.setTop(chartSelector);
        updateChart(chartSelector.getValue());

        Scene scene = new Scene(root, 800, 600);
        primaryStage.setTitle("JavaFX Chart Selector Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void updateChart(String type) {
        switch (type) {
            case "PieChart":
                PieChart pieChart = new PieChart();
                pieChart.setTitle("Pie Chart");
                pieChart.getData().addAll(
                        new PieChart.Data("Java", 30),
                        new PieChart.Data("Python", 25),
                        new PieChart.Data("C++", 20),
                        new PieChart.Data("Kotlin", 15),
                        new PieChart.Data("Other", 10)
                );
                root.setCenter(pieChart);
                break;

            case "LineChart":
                CategoryAxis x1 = new CategoryAxis();
                NumberAxis y1 = new NumberAxis();
                LineChart<String, Number> lineChart = new LineChart<>(x1, y1);
                lineChart.setTitle("Line Chart");
                XYChart.Series<String, Number> lineSeries = new XYChart.Series<>();
                lineSeries.setName("Sales");
                lineSeries.getData().add(new XYChart.Data<>("Mon", 10));
                lineSeries.getData().add(new XYChart.Data<>("Tue", 20));
                lineSeries.getData().add(new XYChart.Data<>("Wed", 15));
                lineSeries.getData().add(new XYChart.Data<>("Thu", 25));
                lineSeries.getData().add(new XYChart.Data<>("Fri", 18));
                lineChart.getData().add(lineSeries);
                root.setCenter(lineChart);
                break;

            case "AreaChart":
                AreaChart<String, Number> areaChart = new AreaChart<>(new CategoryAxis(), new NumberAxis());
                areaChart.setTitle("Area Chart");
                XYChart.Series<String, Number> areaSeries = new XYChart.Series<>();
                areaSeries.setName("Traffic");
                areaSeries.getData().add(new XYChart.Data<>("Jan", 200));
                areaSeries.getData().add(new XYChart.Data<>("Feb", 300));
                areaSeries.getData().add(new XYChart.Data<>("Mar", 150));
                areaSeries.getData().add(new XYChart.Data<>("Apr", 250));
                areaChart.getData().add(areaSeries);
                root.setCenter(areaChart);
                break;

            case "BarChart":
                BarChart<String, Number> barChart = new BarChart<>(new CategoryAxis(), new NumberAxis());
                barChart.setTitle("Bar Chart");
                XYChart.Series<String, Number> barSeries = new XYChart.Series<>();
                barSeries.setName("Downloads");
                barSeries.getData().add(new XYChart.Data<>("Chrome", 60));
                barSeries.getData().add(new XYChart.Data<>("Firefox", 40));
                barSeries.getData().add(new XYChart.Data<>("Edge", 30));
                barChart.getData().add(barSeries);
                root.setCenter(barChart);
                break;

            case "BubbleChart":
                BubbleChart<Number, Number> bubbleChart = new BubbleChart<>(new NumberAxis(), new NumberAxis());
                bubbleChart.setTitle("Bubble Chart");
                XYChart.Series<Number, Number> bubbleSeries = new XYChart.Series<>();
                bubbleSeries.setName("Bubbles");
                bubbleSeries.getData().add(new XYChart.Data<>(10, 20, 5));
                bubbleSeries.getData().add(new XYChart.Data<>(15, 30, 10));
                bubbleSeries.getData().add(new XYChart.Data<>(25, 10, 7));
                bubbleChart.getData().add(bubbleSeries);
                root.setCenter(bubbleChart);
                break;

            case "ScatterChart":
                ScatterChart<Number, Number> scatterChart = new ScatterChart<>(new NumberAxis(), new NumberAxis());
                scatterChart.setTitle("Scatter Chart");
                XYChart.Series<Number, Number> scatterSeries = new XYChart.Series<>();
                scatterSeries.setName("Points");
                scatterSeries.getData().add(new XYChart.Data<>(5, 20));
                scatterSeries.getData().add(new XYChart.Data<>(10, 40));
                scatterSeries.getData().add(new XYChart.Data<>(15, 25));
                scatterChart.getData().add(scatterSeries);
                root.setCenter(scatterChart);
                break;

            case "StackedAreaChart":
                StackedAreaChart<String, Number> stackedAreaChart = new StackedAreaChart<>(new CategoryAxis(), new NumberAxis());
                stackedAreaChart.setTitle("Stacked Area Chart");
                XYChart.Series<String, Number> sa1 = new XYChart.Series<>();
                sa1.setName("Team A");
                sa1.getData().add(new XYChart.Data<>("Q1", 100));
                sa1.getData().add(new XYChart.Data<>("Q2", 120));
                sa1.getData().add(new XYChart.Data<>("Q3", 90));
                sa1.getData().add(new XYChart.Data<>("Q4", 110));
                XYChart.Series<String, Number> sa2 = new XYChart.Series<>();
                sa2.setName("Team B");
                sa2.getData().add(new XYChart.Data<>("Q1", 80));
                sa2.getData().add(new XYChart.Data<>("Q2", 95));
                sa2.getData().add(new XYChart.Data<>("Q3", 70));
                sa2.getData().add(new XYChart.Data<>("Q4", 85));
                stackedAreaChart.getData().addAll(sa1, sa2);
                root.setCenter(stackedAreaChart);
                break;

            case "StackedBarChart":
                StackedBarChart<String, Number> stackedBarChart = new StackedBarChart<>(new CategoryAxis(), new NumberAxis());
                stackedBarChart.setTitle("Stacked Bar Chart");
                XYChart.Series<String, Number> sb1 = new XYChart.Series<>();
                sb1.setName("Male");
                sb1.getData().add(new XYChart.Data<>("2021", 60));
                sb1.getData().add(new XYChart.Data<>("2022", 70));
                XYChart.Series<String, Number> sb2 = new XYChart.Series<>();
                sb2.setName("Female");
                sb2.getData().add(new XYChart.Data<>("2021", 40));
                sb2.getData().add(new XYChart.Data<>("2022", 55));
                stackedBarChart.getData().addAll(sb1, sb2);
                root.setCenter(stackedBarChart);
                break;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

2025/12/29

JavaFX 06 tranformation, transition

javafx 可套用 transformation 在單一或一組 nodes,也可以針對某一個 node,同時套用多個 transformations。

transformation

transformation 有以下這幾種

  • Rotation:旋轉
  • Scaling:縮放
  • Translation:平移
  • Shearing(斜切):用仿射變換實現
package javafx.transformation;

import javafx.application.Application;
import javafx.geometry.Insets;
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.scene.transform.*;
import javafx.stage.Stage;

//Rotation:旋轉
//Scaling:縮放
//Translation:平移
//Shearing(斜切):用仿射變換實現
public class TransformDemo extends Application {

    @Override
    public void start(Stage primaryStage) {
        // === 建立形狀 ===
        Rectangle rect = new Rectangle(100, 60, Color.CORNFLOWERBLUE);
        rect.setArcWidth(15);
        rect.setArcHeight(15);

        // === 各種變換按鈕 ===
        Button btnTranslate = new Button("Translate (+100, +50)");
        btnTranslate.setOnAction(e -> {
            rect.getTransforms().add(new Translate(100, 50));
        });

        Button btnRotate = new Button("Rotate 45°");
        btnRotate.setOnAction(e -> {
            rect.getTransforms().add(new Rotate(45, rect.getX() + rect.getWidth() / 2, rect.getY() + rect.getHeight() / 2));
        });

        Button btnScale = new Button("Scale x1.5");
        btnScale.setOnAction(e -> {
            rect.getTransforms().add(new Scale(1.5, 1.5, rect.getX() + rect.getWidth() / 2, rect.getY() + rect.getHeight() / 2));
        });

        Button btnShear = new Button("Shear x=0.5, y=0");
        btnShear.setOnAction(e -> {
            rect.getTransforms().add(new Shear(0.5, 0));
        });

        // === Reset 按鈕 ===
        Button btnReset = new Button("Reset Transforms");
        btnReset.setStyle("-fx-background-color: tomato; -fx-text-fill: white;");
        btnReset.setOnAction(e -> {
            rect.getTransforms().clear(); // 移除所有變換
        });

        // === 佈局 ===
        VBox root = new VBox(15,
                rect,
                btnTranslate,
                btnRotate,
                btnScale,
                btnShear,
                btnReset
        );
        root.setPadding(new Insets(20));

        Scene scene = new Scene(root, 500, 400);
        primaryStage.setTitle("JavaFX Transformations with Reset");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

transition

transform 是一種即時的直接的轉換,會直接改變 node 的屬性,立刻生效。用作 node 的幾何變換。

transition 是一種動畫,需要一段時間,才會轉換到結果的動畫,可以 delay, repeat,有移動、縮放、旋轉、淡入淡出、路徑移動、視覺強化這些動態轉換的效果。

package javafx.transition;

import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;

public class AllTransitionsDemo extends Application {

    private Rectangle rect;

    // 紀錄狀態
    private double currentTranslateX = 0;
    private double currentTranslateY = 0;
    private double currentRotate = 0;
    private double currentScaleX = 1;
    private double currentScaleY = 1;

    @Override
    public void start(Stage primaryStage) {
        rect = new Rectangle(100, 60, Color.CORNFLOWERBLUE);
        rect.setArcWidth(15);
        rect.setArcHeight(15);
        rect.setStroke(Color.DARKBLUE);
        rect.setStrokeWidth(3);

        // RotateTransition
        Button btnRotate = new Button("Rotate +45°");
        btnRotate.setOnAction(e -> {
            currentRotate += 45;
            RotateTransition rt = new RotateTransition(Duration.seconds(1), rect);
            rt.setToAngle(currentRotate);
            rt.play();
        });

        // ScaleTransition
        Button btnScale = new Button("Scale x1.5");
        btnScale.setOnAction(e -> {
            currentScaleX *= 1.5;
            currentScaleY *= 1.5;
            ScaleTransition st = new ScaleTransition(Duration.seconds(1), rect);
            st.setToX(currentScaleX);
            st.setToY(currentScaleY);
            st.play();
        });

        // TranslateTransition
        Button btnTranslate = new Button("Translate (+100, +50)");
        btnTranslate.setOnAction(e -> {
            currentTranslateX += 100;
            currentTranslateY += 50;
            TranslateTransition tt = new TranslateTransition(Duration.seconds(1), rect);
            tt.setToX(currentTranslateX);
            tt.setToY(currentTranslateY);
            tt.play();
        });

        // FadeTransition
        Button btnFade = new Button("Fade Out / In");
        btnFade.setOnAction(e -> {
            FadeTransition ft = new FadeTransition(Duration.seconds(1), rect);
            ft.setFromValue(1.0);
            ft.setToValue(0.2);
            ft.setAutoReverse(true);
            ft.setCycleCount(2);
            ft.play();
        });

        // FillTransition (填充色)
        Button btnFill = new Button("Fill Color Change");
        btnFill.setOnAction(e -> {
            FillTransition fillTransition = new FillTransition(Duration.seconds(2), rect);
            fillTransition.setFromValue((Color) rect.getFill());
            fillTransition.setToValue(Color.ORANGERED);
            fillTransition.setAutoReverse(true);
            fillTransition.setCycleCount(2);
            fillTransition.play();
        });

        // StrokeTransition (邊框色)
        Button btnStroke = new Button("Stroke Color Change");
        btnStroke.setOnAction(e -> {
            StrokeTransition strokeTransition = new StrokeTransition(Duration.seconds(2), rect);
            strokeTransition.setFromValue((Color) rect.getStroke());
            strokeTransition.setToValue(Color.GREEN);
            strokeTransition.setAutoReverse(true);
            strokeTransition.setCycleCount(2);
            strokeTransition.play();
        });

        // SequentialTransition
        Button btnSequential = new Button("Sequential Animation");
        btnSequential.setOnAction(e -> {
            RotateTransition rotate = new RotateTransition(Duration.seconds(1), rect);
            rotate.setByAngle(90);

            TranslateTransition translate = new TranslateTransition(Duration.seconds(1), rect);
            translate.setByX(100);
            translate.setByY(50);

            ScaleTransition scale = new ScaleTransition(Duration.seconds(1), rect);
            scale.setToX(1);
            scale.setToY(1);

            SequentialTransition seq = new SequentialTransition(rect, rotate, translate, scale);
            seq.play();
        });

        // ParallelTransition
        Button btnParallel = new Button("Parallel Animation");
        btnParallel.setOnAction(e -> {
            RotateTransition rotate = new RotateTransition(Duration.seconds(2), rect);
            rotate.setByAngle(180);

            FadeTransition fade = new FadeTransition(Duration.seconds(2), rect);
            fade.setFromValue(1);
            fade.setToValue(0.3);

            ParallelTransition parallel = new ParallelTransition(rect, rotate, fade);
            parallel.play();
        });

        // PauseTransition
        Button btnPause = new Button("Pause 2 Seconds");
        btnPause.setOnAction(e -> {
            PauseTransition pause = new PauseTransition(Duration.seconds(2));
            pause.setOnFinished(ev -> System.out.println("Pause finished"));
            pause.play();
        });

        // PathTransition
        Button btnPath = new Button("Path Animation");
        btnPath.setOnAction(e -> {
            Path path = new Path();
            path.getElements().add(new MoveTo(0, 0));
            path.getElements().add(new CubicCurveTo(150, 100, 300, 0, 400, 100));

            PathTransition pathTransition = new PathTransition(Duration.seconds(3), path, rect);
            pathTransition.setCycleCount(1);
            pathTransition.play();
        });

        // Reset 按鈕
        Button btnReset = new Button("Reset All");
        btnReset.setStyle("-fx-background-color: tomato; -fx-text-fill: white;");
        btnReset.setOnAction(e -> {
            rect.getTransforms().clear();
            rect.setTranslateX(0);
            rect.setTranslateY(0);
            rect.setRotate(0);
            rect.setScaleX(1);
            rect.setScaleY(1);
            rect.setOpacity(1);
            rect.setFill(Color.CORNFLOWERBLUE);
            rect.setStroke(Color.DARKBLUE);

            currentTranslateX = 0;
            currentTranslateY = 0;
            currentRotate = 0;
            currentScaleX = 1;
            currentScaleY = 1;
        });

        VBox root = new VBox(10,
                rect,
                btnRotate,
                btnScale,
                btnTranslate,
                btnFade,
                btnFill,
                btnStroke,
                btnSequential,
                btnParallel,
                btnPause,
                btnPath,
                btnReset
        );
        root.setPadding(new Insets(15));

        Scene scene = new Scene(root, 600, 700);
        primaryStage.setTitle("JavaFX All Transitions Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}