2024年12月11日 星期三

Diagnosing the Error: Module javafx.fxml not found

在 NetBeans 使用 JavaFX 時,常遇到如下錯誤

  Error occurred during initialization of boot layer
  java.lang.module.FindException: Module javafx.fxml not found 

表示啟動層初始化時 Java 執行環境無法找到 javafx.fxml 模組。
這可能是由於 NetBeans 模組路徑的設定錯誤 或 根本未安裝模組所致。
以下為診斷和解決這個問題的步驟。

1.檢查 JavaFX SDK 安裝 及 NetBeans 設定正確否
   依據如下提示逐項檢查:
     how to set up NetBeans environment for JavaFX applications

2.打開 NetBeans 列印 Ant 腳本功能
   依據如下設定選項 
     Tools/Options/Java/Ant 或者 NetBeans/Settings...
        Ant Home: ...
        [v] Always Show Output

     Verbosity Level: Quiet/Normal/[Verbose]/Debug

    即可在執行 Run File 時,於 Output 視窗
    觀看Ant Target (build.xml) Output

3.檢查 NetBeans 執行 Ant run 腳本的參數填對否
   核對如下重要 java 執行參數
   run:
    Executing '/path/to/bin/java' with arguments:
    '-Dfile.encoding=UTF-8'
    
    '--add-modules'
    'javafx.controls,javafx.fxml,javafx.media'
    
    '-classpath'
    '/path/to/javafx-sdk-xx/lib/javafx-swt.jar:
     /path/to/javafx-sdk-xx/lib/javafx.base.jar:
     /path/to/javafx-sdk-xx/lib/javafx.controls.jar:
     /path/to/javafx-sdk-xx/lib/javafx.fxml.jar:
     /path/to/javafx-sdk-xx/lib/javafx.graphics.jar:
     /path/to/javafx-sdk-xx/lib/javafx.media.jar:
     /path/to/javafx-sdk-xx/lib/javafx.swing.jar:
     /path/to/javafx-sdk-xx/lib/javafx.web.jar:
     /path/to/MyProject/build/classes'
     
    '--module-path'
    '/path/to/MyProject/build/classes:
     /path/to/javafx-sdk-xx/lib/javafx-swt.jar:
     /path/to/javafx-sdk-xx/lib/javafx.base.jar:
     /path/to/javafx-sdk-xx/lib/javafx.controls.jar:
     /path/to/javafx-sdk-xx/lib/javafx.fxml.jar:
     /path/to/javafx-sdk-xx/lib/javafx.graphics.jar:
     /path/to/javafx-sdk-xx/lib/javafx.media.jar:
     /path/to/javafx-sdk-xx/lib/javafx.swing.jar:
     /path/to/javafx-sdk-xx/lib/javafx.web.jar'
     
    'MyJavaFXApp'

4.任何一處 Ant run 腳本的參數有缺,可對應到如下 NetBeans 設定有誤:
     Project Properties
       Libraries/
           Java Platform: JDK xx (Default)
           Compile/Compile-time Libraries:
               Classpath: JavaFX yy
           Run/Run-time Libraries:
               Modulepath: JavaFX yy

       Run/
           Configuration: <default config>
           VM Options:
               --add-modules javafx.controls,javafx.fxml,javafx.media
               
註1: 其實 Ant build 及 run 腳本的工作原理就是由 NetBeans 設定抽取相關參數,
    組合成如下命令列編譯及執行指令。故執行有問題可找回相關設定哪裏出問題。

    命令列編譯指令
    > javac -classpath "...\javafx-sdk-xx\lib\javafx.fxml.jar;...;." \
      MyJavaFXApp.java

    命令列執行指令
    > java --add-modules javafx.controls,javafx.fxml,javafx.media \
      -classpath . \
      --module-path "...\javafx-sdk-xx\lib\javafx.fxml.jar;..." \
      MyJavaFXApp

註2: NetBeans 執行 Ant run 腳本時,依序參考如下檔案
       MyProject/
         build.xml
         nbproject/
           build-impl.xml
           project.properties
     其中,project.properties有專案屬性如下,存放設定執行參數,亦可對照檢視設定錯誤原因。
       run.classpath=\
         ${javac.classpath}:\
         ${build.classes.dir}
       run.jvmargs=--add-modules javafx.controls,javafx.fxml,javafx.media
       run.modulepath=\
         ${javac.modulepath}:\
         ${libs.JavaFX_xx.classpath}

2024年12月10日 星期二

Two ways to draw shapes in JavaFX

 JavaFX 有 2 種畫形狀的寫法如下:

A. Pane容器放 Shape 形狀

import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;

....

        Pane pane = new Pane();

        Circle circle = new Circle(50, 50, 30);
        circle.setFill(Color.BLUE);

        Rectangle rectangle = new Rectangle(100, 100, 80, 40);
        rectangle.setFill(Color.RED);

        pane.getChildren().addAll(circle, rectangle);

註1: Pane小孩清單會記住所有加入形狀元件,Pane大小調整後也會重新顯示所有形狀
註2: javafx.scene.shape支援形狀有
         Circle, Rectangle, Ellipse, Line, Polygon, Polyline, 
         Arc, CubicCurve, QuadCurve, Path 等

B. Canvas元件上畫出形狀

import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;

.....

        Canvas canvas = new Canvas(300, 200);
        GraphicsContext gc = canvas.getGraphicsContext2D();

        gc.setFill(Color.BLUE);
        gc.fillOval(50, 50, 60, 60); // Draw a circle

        gc.setFill(Color.RED);
        gc.fillRect(150, 100, 80, 40); // Draw a rectangle

        
註1: javafx.scene.canvas.GraphicsContext會暫時記住所有畫圖區內容,直到大小異動才清空
註2: javafx.scene.canvas.GraphicsContext支援形狀有

// 矩形
 fillRect(double x, double y, double w, double h) // 前景色填滿
 strokeRect(double x, double y, double w, double h) // 前景色畫框
 clearRect(double x, double y, double w, double h)  // 背景色填滿

// 圓形
 fillOval(double x, double y, double w, double h)
 strokeOval(double x, double y, double w, double h)

// 弧形
 fillArc(double x, double y, double w, double h, 
         double startAngle, double arcExtent, ArcType closure)
 strokeArc(double x, double y, double w, double h, 
           double startAngle, double arcExtent, ArcType closure)

// 多邊形,多邊線
 fillPolygon(double[] xPoints, double[] yPoints, int nPoints)
 strokePolygon(double[] xPoints, double[] yPoints, int nPoints)
 fillPolyline(double[] xPoints, double[] yPoints, int nPoints)
 strokePolyline(double[] xPoints, double[] yPoints, int nPoints)
 strokeLine(double x1, double y1, double x2, double y2)

// 路徑模式, 一次只能維護一條路徑
 beginPath()
 moveTo(double x, double y)
 lineTo(double x, double y)
 closePath()
 stroke()  // 畫路徑框
 fill() // 畫路徑內部

2024年12月1日 星期日

how to write change event handlers for JavaFX components

在 javafx.scene.control 套件路徑下,如下元件有更動屬性都會產生異動事件(Change Event),

    TextField/CheckBox/ComboBox/RadioButton/ChoiceBox/Slider/ListView

可對元件屬性註冊異動事件處理器(Change Event Handler),接收異動事件,進行處理。

註冊異動事件處理器寫法,和註冊動作事件處理器寫法不同,
須利用.xxxProperty()方法先取得元件屬性,
再利用.addListener 方法為屬性添加異動監聽器,寫法如下:

textField.textProperty().addListener((observable, oldValue, newValue) -> {
  System.out.println("value changed from " + oldValue + " to " + newValue);
});

textField.textProperty().addListener(new ChangeListener<String>() {
  @Override
  public void changed(ObservableValue observable, 
                      String oldValue, String newValue) {
    System.out.println("value changed from " + oldValue + " to " + newValue);
  }
});


註1: 至於其他元件的異動事件處理器註冊,須搭配各元件的屬性取法不同,寫法如下:
    checkBox.selectedProperty().addListener(...)
    comboBox.valueProperty().addListener(...)
    radioButton.selectedProperty().addListener(...)
    choiceBox.getSelectionModel().selectedItemProperty().addListener(...)
    slider.valueProperty().addListener(...)
    listView.getSelectionModel().selectedItemProperty().addListener(...)

註2: JavaFX 有 javafx.event.ActionEvent 動作事件類別及物件,但沒有異動事件類別。
     元件一旦有動作事件,通常屬於高階離散事件,立即一次性處理完即可,
     故設計上註冊處理器寫法較簡單,只允許對元件呼叫如下方法,掛上單一動作監聽器。
         setOnAction(event -> {...}); 

註3: 元件屬性一旦有異動事件,通常屬於低階連續事件,須要密集多次處理狀態變化,
     故設計上註冊處理器寫法較細緻彈性,可對屬性呼叫如下方法,掛上多個異動監聽器。
        addListener(
           new javafx.beans.value.ChangeListener(
              (ob, oldV, newV) -> {...}
           )
        );

Two ways to write action event handlers in JavaFX

在 javafx.scene.control 套件路徑下,如下元件受點選都會產生動作事件(ActionEvent),

    Button/TextField/CheckBox/ComboBox/RadioButton/MenuItem/Hyperlink

可對元件註冊動作事件處理器(ActionEvent Handler),接收動作事件,進行處理。

註冊動作事件處理器有兩種寫法:

1.利用元件的.setOnAction方法註冊,寫法如下

button.setOnAction(event -> System.out.println("Button clicked!"));button.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        System.out.println("Button clicked!");
    }
});


2. 利用.fxml檔的元件屬性註冊,寫法如下

<Button text="Click Me" onAction="#handleButtonAction"/>

public class Controller {

    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("Button clicked!");
    }
}

註: 方法前的 @ 標註旨在通知編譯器作防呆檢查,減少可能錯誤
1. @Override 標註將提醒編譯器檢查該方法簽名是否有覆蓋上一代方法
2. @FXML 標註將提醒編譯器檢查該方法或屬性是否出現於 .fxml 介面配置檔中

2024年11月30日 星期六

How to interpret the caused by sections of a Java stack trace?

Java執行出錯丟出例外時,常會列印一串 Caused by 訊息,其格式為
   java.lang.Exception: Exception in xxx
       at ......... (....java: ..)
       .......
       at ......... (....java: ..)
   Caused by: java.lang.Exception: Exception in yyy
       at ......... (....java: ..)
       .......
       at ......... (....java: ..)
   Caused by: java.lang.Exception: Exception in zzz
       at ......... (....java: ..)
       .......
       at ......... (....java: ..)

這表示先有 zzz 錯誤,然後造成 yyy 錯誤,然後造成 xxx 錯誤。因此,最初錯誤原因為最後Caused by 指出的 zzz 錯誤。至於每個錯誤後面都會跟著很多 at,印出丟出例外當時的方法堆疊內容,越後面的 at 程式碼越早執行。


 public class CausedByExample {
    public static void main(String[] args) {
        try {
            method1();  // line 4
        } catch (Exception e) {
            // 此行指令表明 執行方法main出現例外 將列印丟出例外時的堆疊記錄內容
            e.printStackTrace();
        }
    }

    public static void method1() throws Exception {
        try {
            method2();  // line 13
        } catch (Exception e) {
            // 此行指令表明 執行方法1出現例外 是由 執行方法2的例外e造成,將列印
            // java.lang.Exception: Exception in method1
            //  逐層列印丟出方法1例外時的堆疊記錄內容
            throw new Exception("Exception in method1", e);  // line 18
            //  public Exception(String message, Throwable cause) 
            //  產生新例外,包含例外說明字串 message,及造成本例外的原因 cause
        }
    }

    public static void method2() throws Exception {
        // 此行指令表明 執行方法2出現例外,將列印
        // java.lang.Exception: Exception in method2
        //  逐層列印丟出方法2例外時的堆疊記錄內容
        throw new Exception("Exception in method2");  // line 26
    }
}

Output:

上面程式在method2產生例外,由method1接收,再包裝成原因產生新例外,由main接收,列印e.printStackTrace。其列印內容說明,Exception in method2 造成 Exception in method1

java.lang.Exception: Exception in method1
	at CausedByExample.method1(CausedByExample.java:18)
	at CausedByExample.main(CausedByExample.java:4)
Caused by: java.lang.Exception: Exception in method2
	at CausedByExample.method2(CausedByExample.java:26)
	at CausedByExample.method1(CausedByExample.java:13)
	... 1 more

2024年11月12日 星期二

how to set up NetBeans environment for JavaFX applications

JavaFX 為 Java 繼 Awt, Swing 之後推出的第 3 代圖形介面 (GUI) 套件,多了場景建立器 (Scene Builder),排版配置檔 CSS 等支援能力。 利用 NetBeans 整合開發環境 (IDE) 撰寫 JavaFX 應用時,常遇到開發環境如何建立的問題。很容易會遇到如下錯誤:

   Error occurred during initialization of boot layer
   java.lang.module.FindException: Module javafx.controls not found
或
   java.lang.module.FindException: Module javafx.fxml not found

以下整理幾點 NetBeans 整合 JavaFX SDK,SceneBuilder 場景建立器,詳查 Build 腳本的方法,供除錯參考。

✅ NetBeans 整合 JavaFX SDK 方法
     Project Properties
       Libraries/
           Java Platform: JDK xx (Default)
           Compile/Compile-time Libraries:
               Classpath: JavaFX yy
           Run/Run-time Libraries:
               Modulepath: JavaFX yy

       Run/
           Configuration: <default config>
           VM Options:
               --add-modules javafx.controls,javafx.fxml,javafx.media
         
✅ NetBeans 整合 SceneBuilder 方法
     Tools/Options/Java/JavaFX
        JavaFX Scene Builder Integration
          Scene Builder Home: C:\Users\zz\AppData\Local\SceneBuilder
    
✅ NetBeans除錯想看Build執行腳本內容
     Tools/Options/Java/Ant:
        Ant Home: ...
        [v] Always Show Output

     Verbosity Level: Quiet/Normal/[Verbose]/Debug

    即可觀看Ant Target (build.xml) Output

註1: 建立 JavaFX 開發環境所須安裝套件如下
1.OpenJDK
  https://learn.microsoft.com/zh-tw/java/openjdk/download
    microsoft-jdk-21.0.5-windows-x64.msi (不含JavaFX)
    microsoft-jdk-21.0.5-macos-aarch64.pkg
  https://www.azul.com/downloads/?package=jdk-fx#zulu
    zulu21.38.21-ca-fx-jdk21.0.5-win_x64.msi (含JavaFX)

2.NetBeans
  https://netbeans.apache.org/download/index.html
    Apache-NetBeans-22-bin-windows-x64.exe
    Apache-NetBeans-22.pkg

3.SceneBuilder
  https://gluonhq.com/products/scene-builder/
    SceneBuilder-23.0.1.msi
    SceneBuilder-23.0.1-aarch64.dmg

4.JavaFX SDK
  https://gluonhq.com/products/javafx/
    openjfx-17.0.13_windows-x64_bin-sdk.zip
    openjfx-17.0.13_osx-aarch64_bin-sdk.zip
    
    
註2: NetBeans 要看到 Scene Builder Home,須要安裝且啟動(Activate)如下任一插件
     Tools/Plugins:
       JavaFX 2
       或
       JavaFX Implementation for Windows

     安裝之後,針對專案 .fxml 按右鍵,才會看到如下選項
       Open 連動開啟場景建立器畫面
       Edit 開啟.fxml文字畫面
       Make Controller 產生 .fxml 中 fx:controller 屬性指定的控制器.java類別檔

註3: NetBeans 要能看到 JavaFX yy 類別庫,須設定
     Tools/Libraries/Libraries:
       Library Name: JavaFX yy
       Classpath/Add JAR/Folder...:
         ...\javafx-sdk-yy\lib\javafx-swt.jar    
         ...\javafx-sdk-yy\lib\javafx.base.jar    
         ...\javafx-sdk-yy\lib\javafx.controls.jar    
         ...\javafx-sdk-yy\lib\javafx.fxml.jar    
         ...\javafx-sdk-yy\lib\javafx.graphics.jar    
         ...\javafx-sdk-yy\lib\javafx.media.jar    
         ...\javafx-sdk-yy\lib\javafx.swing.jar    
         ...\javafx-sdk-yy\lib\javafx.web.jar    

註4: NetBeans 要能點選原始碼類別,按右鍵點選
       Show Javadoc (Alt-F1) 看到類別 註解說明,須設定
     Tools/Libraries/Libraries:
       Library Name: JavaFX yy
       Javadoc/Add URL...: https://docs.oracle.com/javafx/2/api/

註5: NetBeans 要能點選原始碼類別,按右鍵點選 
       Navigate> Go to Source (Ctrl-Shift-B) 看到類別 原始碼,須設定
     Tools/Libraries/Libraries:
       Library Name: JavaFX yy
       Sources/Add JAR/Folder...: ...\javafx-sdk-yy\src.zip    

2024年11月11日 星期一

controller class not found or fxml load error when running JavaFX applications

執行 JavaFX 圖形介面程式,使用視窗配置檔 .fxml 時,若出現 fxml載入錯誤控制器類別找不到 錯誤,很可能是因為 .fxml 配置檔的控制器指定值沒有寫對。可打開 .fxml 檔,檢查容器 (XXPane) 標籤的 fx:controller 控制器欄位值是否正確。若有使用套件包裝,其套件路徑是否正確。

如下 fxml 配置檔範例中,假設 DrawShapesController.java 控制器宣告歸屬套件 package com.abc;,則其值前面要加上 com.abc 套件路徑,在依據 fxml 配置檔載入控制器時,才找得到控制器類別。

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" 
xmlns="http://javafx.com/javafx/8.0.60" 
xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="com.abc.DrawShapesController">
...
</BorderPane>

註1: 常見 fxml載入錯誤 及 控制器類別找不到錯誤  例子。

Exception in Application start method
java.lang.reflect.InvocationTargetException
  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  .....
  at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)

Caused by: java.lang.RuntimeException: Exception in Application start method
  at javafx.graphics@19/.....LauncherImpl.launchApplication1(LauncherImpl.java:901)
  at javafx.graphics@19/.....LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
  at java.base/java.lang.Thread.run(Thread.java:834)

Caused by: javafx.fxml.LoadException: ..../DrawShapes.fxml:8
  at javafx.fxml@19/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2707)
  ......
  at ......DrawShapes.start(DrawShapes.java:16)
  ......

Caused by: java.lang.ClassNotFoundException: DrawShapesController
  at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
  ......

Exception running application .....DrawShapes

註2: 有時也會看到如下 找不到類別定義錯誤 例子。

Caused by: java.lang.NoClassDefFoundError: DrawRandomLinesController (wrong name: DrawRandomLinesController)