[備忘録]JavaFX/Scene Builderで画面遷移するシステムを作ってみる

# 1. はじめに

今回は、JavaFXで学んだことをアウトプット、備忘録としてここに記します。

# 使用環境

windows10

Java - 17.0.2
Eclipse - 2021-12 (4.22.0)
JavaFX - 17.0.3（2022年4月）
Scene Builder - JavaFX SceneBuilder1.1




# 2. JavaFXの導入

◆JavaFXの安定バージョンをダウンロードします。

2022/5/23時点では、17.0.3がサポート対象でした。

[JavaFX公式 - ダウンロードページ](https://gluonhq.com/products/javafx/)
<br>

ダウンロードしたものを展開する場所は分かりやすい作業フォルダに置く

◆Windowsのシステム環境変数とEclipseにパスを通しましょう
> Windowsシステム環境変数に追加
> 変数名は適当に、JavaFX_path
> 
> ```
> ドライブ名:\保存先\javafx-sdk-18.0.1\lib
> ```
> 
> のように/libファイルの中身を見れるようにシステムに教えてあげましょう。

:::note warn
システムにパスを通さないとEclipseからパスを通せません。
:::


※EcelipseはJavaFXプロジェクトを作ってから外部jarファイルとして追加します。

# 3. Scene Builderをインストール

[Oracle - Scene Builder ダウンロードページ](https://www.oracle.com/java/technologies/javafxscenebuilder-1x-archive-downloads.html)

上記のページから一番下のあたりにある
[JavaFX SceneBuilder1.1ダウンロード]
から各自OSのバージョンをダウンロードしてください

# 4. JavaFX Projectを作成

### 4.1. JavaFXを作る前に

JavaFXは画面を見た目を構築する　 .fxmlファイル
とその画面をコントロールする　　.javaのコントローラーファイル

の２つのセットで構築されています。

:::note info 
Scene Builderはその.fxmlをGUI表示して直感的に画面構成を変えることでScene Builderが.fxmlを書き換えてくれるツールです。
:::

以下のようにEclipseからMain.javaを実行すると、新しいウインドウでアプリが起動する状態を目指します。

![javafx画面.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676891/ed5e6c3d-78bf-2255-3d2f-e56f3857e615.png)

では、実際に作っていきます。

### 4.2. Eclipseにて、JavaFXプロジェクトを作る

Eclipseのプロジェクト作成から、JavaFX/JavaFXプロジェクトを選択します。

1. Eclipse画面上部のリボンにある
ウインドウ > 設定 > Java > ビルド・パス > ユーザー・ライブラリー > 新規 

2. 新規作成ボタンで JavaFX と入力し、フォルダを作成

3. 外部jarファイルの追加

4. システム環境変数で設定したパスと同じ場所 /lib　の中にある すべての.jarファイルを選択して、適用ボタンを押す。

以下のような状態になっていればok

![eclipseにjarファイル追加.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676891/676ae395-5400-b324-e73d-b21bf4e479d8.png)

1. ロジェクトの作成 > その他 > JavaFX > JavaFXプロジェクト

2. プロジェクト名を決めて、JREがJavaSE-17になっていれば、次へ

3. ライブラリータブ > クラスパスを選択した状態で > 外部jarファイルの追加(先ほどEclipseでパスを通した手順と同じことをします)、次へ

4. 宣言的UI > 言語を[FXML]にする

ルートタイプ: 変更しない
ファイル名: Main
コントローラー名: MainController

完了ボタンでプロジェクトが作成される

参考ページ
[【超初心者向け】JavaFX超入門](https://qiita.com/tarosa0001/items/05ac653a091b7d1290f9#23-%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E6%A7%8B%E7%AF%89)


# 5. 画面を作ろう

src/application/内の○○Controller.javaと○○.fxmlをパッケージ化します。

```
src/application/main/MainScreen.fxml
src/application/main/MainController.java
```

:::note info
Screen.fxmlはそのままの階層においてください
Main.javaは実行ファイルなのでそのままの階層においてください
:::

### 5.1. Main.java
```Main.java
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            //FXMLからのシーングラフの読み込み
            FXMLLoader loader = new FXMLLoader(getClass().getResource("main/MainScreen.fxml"));
            Parent root = loader.load();

            //シーングラフのルートノードを設定したシーンの作成
            Scene scene = new Scene(root, 1000, 600);
            //ステージへのシーンの設定
            primaryStage.setScene(scene);
            primaryStage.setTitle("[ウィンドウの名称を変更できます]");
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
```
### 5.2. Scene Builderでログイン画面の見た目を作る
- MainScreen.fxmlを右クリックしてScene Builderで開く

![loginform.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676891/6d066a48-de5a-8377-098f-61759231f2d5.png)


このような画面を作ります。

- Containers / vBoxを黒い画面上に置くと、真っ白な画面ができると思います、

赤①
Containers / Text 
- Properties: 好きな文章をいれてください
- Code: fx:idはつけなくてもいいです

橙②
Containers / TextField
- Properties: Prompt Text - に[user_name](文字列)を入れると、Text Fieldの背後にうっすらと文字が表示されます。
- Code - [user_name] というidを付けます。

黄③
Containers / Button
- Properties: Text - [System exit]を入力
- Code: fx:id - に[closeButton]
- Code: On Action - に[OnClickedCloseButton]を入力

Containers / Button
- Properties - Text [login]を入力
- Code: fx:id - [nextButton]
- Code: On Action - [OnClickedNextButton]

これで更新を書けると注意マークが上部にでます

⇒　Controllerでこれらの動作について定義すればこの警告は消えます。




### 5.3. MainScreen.fxml

```MainScreen.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.main.MainController">
   <children>
      <Button fx:id="nextButton" layoutX="411.0" layoutY="323.0" mnemonicParsing="false" onAction="#OnclickedNextButton" prefHeight="33.0" prefWidth="91.0" text="login" />
      <Button fx:id="closeButton" layoutX="109.0" layoutY="323.0" mnemonicParsing="false" onAction="#OnclickedCloseButton" prefHeight="33.0" prefWidth="91.0" text="system exit" />
      <Label alignment="CENTER" layoutX="200.0" layoutY="75.0" prefHeight="33.0" prefWidth="200.0" text="Question-Maker">
         <font>
            <Font size="20.0" />
         </font>
      </Label>
      <TextField fx:id="user_name" layoutX="226.0" layoutY="168.0" promptText="user name" />
      <TextField fx:id="password" layoutX="226.0" layoutY="213.0" promptText="password" />
      <Text layoutX="142.0" layoutY="185.0" strokeType="OUTSIDE" strokeWidth="0.0" text="User Name: " />
      <Text layoutX="146.0" layoutY="230.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Password: " />
      <TextField fx:id="again_pass" layoutX="226.0" layoutY="258.0" promptText="again_pass" />
      <Text layoutX="136.0" layoutY="275.0" strokeType="OUTSIDE" strokeWidth="0.0" text="again_pass: " />
   </children>
</Pane>

```
### 5.4. コントローラーに処理を書いていく

MainController.javaを編集します。

その前に、Scene Builderにて上部のタブから、

表示 > サンプル・コントローラー・スケルトンの表示を押すと

MainController.javaに書くべき各コンテナの定義情報が記載されますので丸ごとコピーしてこの状態から書き始めます。

:::note info
Controllerで、fxmlの各要素を参照しているわけです.
:::

以下のようにTextFieldにある文字列を受け取って、データベースの情報と照合する処理を
書けば、ログイン機能のような画面遷移ができます。

### 5.5. MainController.java

```MainController.java
package application.main;

import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.ResourceBundle;

import application.Screen;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;

public class MainController {

    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    private Button nextButton;

    @FXML
    private Button closeButton;
    
    @FXML
    private TextField user_name;
    
    @FXML
    private TextField password;
    
    @FXML
    private TextField again_pass;

	@FXML
    void OnclickedCloseButton(ActionEvent event) {
        closeButton.getScene().getWindow().hide();          //システム終了
    }

    @FXML
    void OnclickedNextButton(ActionEvent event) throws SQLException, ClassNotFoundException {
    	// データベースから情報を持ってくる処理
    	
    	if (１回目のパスワード入力と２回目のパスワード入力の文字列が同じか) { 
    		if (データベースの情報と合っているか) {
    			// 画面遷移処理
    			nextButton.getScene().getWindow().hide();
                Screen screen = new Screen();                 
                // 引数 (次の画面の.fxml, "画面名");
                screen.transitionScreen("home/SubScreen.fxml", "HOME");
    		}
    	} else {
    		System.out.println("user login missed");
    	}
        
    }

    @FXML
    void initialize() {
        assert nextButton != null : "fx:id=\"nextButton\" was not injected: check your FXML file 'screen.fxml'.";
        assert closeButton != null : "fx:id=\"closeButton\" was not injected: check your FXML file 'screen.fxml'.";

    }
   
}
```
### 5.5. 遷移先の画面
遷移先の画面も作っておきます。


### 5.6.  SubScreen.fxml

```SubScreen.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.text.Font?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" style="-fx-border-radius: 20;" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.home.SubController">
   <children>
      <Button fx:id="backButton" layoutX="458.0" layoutY="21.0" mnemonicParsing="false" onAction="#OnclickedBackButton" prefHeight="33.0" prefWidth="110.0" text="Back to Login">
         <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </padding></Button>
      <Label alignment="CENTER" layoutX="39.0" layoutY="6.0" prefHeight="25.0" prefWidth="200.0" text="Question-Maker">
         <font>
            <Font size="20.0" />
         </font>
      </Label>
      <TextArea layoutX="64.0" layoutY="81.0" prefHeight="279.0" prefWidth="503.0" text="*** このアプリについて ***&#10;主に、問題の作成、編集、削除、コピー&#10;を行うアプリです。&#10;&#10;ぜひ、あなたの勉強に活用してみてください。&#10;&#10;This app is Beta ver 1.0&#10;© Powered by hoge.net &#10;">
         <font>
            <Font size="18.0" />
         </font>
      </TextArea>
      <Label fx:id="displayName" layoutX="357.0" layoutY="31.0" text="ようこそ、○○さん" />
      <Button fx:id="myList" layoutX="64.0" layoutY="46.0" mnemonicParsing="false" onAction="#onClickedMyList" text="myList" />
      <Button fx:id="latestShow" layoutX="128.0" layoutY="46.0" mnemonicParsing="false" onAction="#onClickedLatestShow" text="latestShow" />
      <Button fx:id="doTest" layoutX="213.0" layoutY="46.0" mnemonicParsing="false" onAction="#OnClickedDoTest" text="DoTest" />
   </children>
   <padding>
      <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>
</Pane>

```

### 5.7. SubController.java

```SubController.java
package application.home;

import application.Screen;
import application.main.MainController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

public class SubController {

	@FXML
    private Button backButton;

    @FXML
    private Label displayName;
    
    @FXML
    private Button doTest;

    @FXML
    private Button latestShow;

    @FXML
    private Button myList;
    
    // hold user
    String current_user_name;

    @FXML
    void OnclickedBackButton(ActionEvent event) {
        backButton.getScene().getWindow().hide();           //画面を閉じる
        Screen screen = new Screen();                       //画面を開く(メソッド呼び出し)の準備
        screen.transitionScreen("main/MainScreen.fxml", "Login Form");  //画面を開く
    }
    
    public void getUserName() {
    	MainController mainC = new MainController();
    	String name = mainC.getCurrent_user_name();
    	current_user_name = name; 
    }
    
    @FXML
    void onClickedLatestShow(ActionEvent event) {
    	latestShow.getScene().getWindow().hide();           //画面を閉じる
        Screen screen = new Screen();                       //画面を開く(メソッド呼び出し)の準備
        screen.transitionScreen("aaa/aaa.fxml", "toAAA");  //画面を開く
    }
    
    @FXML
    void OnClickedDoTest(ActionEvent event) {
    	doTest.getScene().getWindow().hide();           //画面を閉じる
        Screen screen = new Screen();                       //画面を開く(メソッド呼び出し)の準備
        screen.transitionScreen("bbb/bbb.fxml", "toBBB");  //画面を開く
    }

    @FXML
    void onClickedMyList(ActionEvent event) {
    	myList.getScene().getWindow().hide();           //画面を閉じる
        Screen screen = new Screen();                       //画面を開く(メソッド呼び出し)の準備
        screen.transitionScreen("ccc/ccc.fxml", "toCCC");  //画面を開く
    }

    @FXML
    void initialize() {
        assert backButton != null : "fx:id=\"backButton\" was not injected: check your FXML file 'subScreen.fxml'.";
        assert doTest != null : "fx:id=\"doTest\" was not injected: check your FXML file 'subScreen.fxml'.";
        assert myList != null : "fx:id=\"myList\" was not injected: check your FXML file 'subScreen.fxml'.";
        assert latestShow != null : "fx:id=\"latestShow\" was not injected: check your FXML file 'subScreen.fxml'.";
        assert doTest != null : "fx:id=\"doTest\" was not injected: check your FXML file 'subScreen.fxml'.";
    }
}
```
### 5.8. Screen.java
自分で作るファイルです
```Screen.java
package application;

import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Screen {
    public void transitionScreen(String fxmlName, String fxmlTitle) {
        try {
            //FXMLからのシーングラフの読み込み
            FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlName));
            Parent root = loader.load();

            //シーングラフのルートノードを設定したシーンの作成
            Scene scene = new Scene(root, 1000, 600);
            //ステージへのシーンの設定
            Stage stage = new Stage();

            stage.setScene(scene);
            stage.setTitle(fxmlTitle);
            stage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

### 5.9. Java Application として実行

VM引数に

```
--module-path "(ダウンロードしたJavaFXのlibフォルダへのフルパス)" --add-modules javafx.controls,javafx.fxml
```

を追加して実行してください

MainController.javaのOnClickedNextButtonの処理を書いて、
画面遷移できたと思います。

.fxmlを作る > Scene Builderで画面を作って、コンテナにidやActionをつける > Controllerで制御メソッドやフィールドを追加して処理を書く

のサイクルでどんどん画面ができていくと思います。

### 6. おわり

以上で記事を終了しますが、初学者ゆえとりあえず動く状態でこの記事を書いてますので
誤った情報や重複した内容がありましたら申し訳ありません。

以降のTableViewなど機能を追加するにあたりよさげな資料をここに挙げます。




[RandomCode - TableView-TableColumn](https://www.youtube.com/watch?v=fnU1AlyuguE)

[thenewboston - JavaFX Java GUI Tutorial - 12 - ChoiceBox (Drop Down Menu)](https://www.youtube.com/watch?v=K3CenJ2bMok)

[RandomCode - Toggle Button](https://www.youtube.com/watch?v=4YzgNdT-f-s)