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 介面配置檔中