いままでCUIのjavaばかりでしたので、Servletを使わずにGUIプログラムを作ってみましょう
Scene Builderをインストールする
まずは、Scene Builderをインストールしましょう。このツールを入れると画面の作成をVBのようにVisualにすることができるようになります
まずは、Scene Builderをダウンロードします
https://gluonhq.com/products/scene-builder/
このサイトからwindows版をダウンロードします
それほど大きくはありません。
ダウンロードしたらダブルクリックしてインストールしてください
インストールが終わると、ユーザーの配下にインストールされます。この場所はあとで使います
scene builderをインスタールしたら、VsCodeに拡張機能で「Scene builder extension for Visual Studio Code」をインストールします。
さて、ここでこのインストールしたscenebuilderとVsCodeを接続していきます。Vscodeを起動してCTRL+SHIFT+Pでコマンドパネルを表示して
Configure Scene Builder path と入力して、表示されたその項目をクリックすると
どこにscene builder.exeがあるか聞いてくるので、さきほど見たユーザーの配下のscenebuilderをクリックします。これでvscodeから直接scene builderが起動できるようになります。
javafxをmavenで作る
さて、vscodeでmavenを利用してjavafxを作っていきます。CTRL+SHIFT+Pを押して create java projectを選びます
あとは、mavenの回でやったようにプロジェクト名等を入れていきます
今回はjavafxのサンプルを作りますので、javafxを選択します
group idを入れます。これがpackage名になりますので、自分のドメイン名(架空でかまわない)を逆順にした名前を英数小文字で入力してください。これがパッケージになりますので、フォルダーの階層になります。
続いて、プロジェクト名を英数小文字で入力します。これはフォルダー名とは別です。今回は画像とはことなりますが「scenesample」として作ってみましょう
その後に、そのフォルダーの先頭をどこに作るのか聞いてきますので、自分がmaven等で作成しているworkspaceにフォルダーを作成、あるいは選択してください。これは大文字が入っても大丈夫です。今回は「SceneSample」として作りましょう
作成したらそのフォルダーを選択します
するとmavenが働いてフォルダー作成の確認を求めてきますので、ここでこの当たりをマウスでクリックしておいてからエンターキーを押します
つづいてYの文字が出てきたらそこに小文字でyとenterを入力します
そのあと、今作った場所をウィンドウを開くか聞かれたらopenを押します。すると今作ったフォルダーが新たに開かれます。openを押さないでその画面で続けたい場合は×で終了して、enterキーを入力すればその場で作成を続けることも可能です。
新たなウィンドウではフォルダーが展開されていませんので、開かないとわかりません
srcを開くと、フォルダーが指示通りに作成されているのがわかります。
srcに3つのjavaソースが出来ているのがわかります。App.javaがmainでここからprimaryController.javaが呼び出されます。そこでボタンを押すことによってsecondaryController.javaが起動されて画面が切り替わるようになっています。つまりこのjavafxの作成の流れで2つの画面をもったサンプルが作成されることがわかります
中身を見てみましょう
package jp.co.etlab; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; /** * JavaFX App */ public class App extends Application { private static Scene scene; @Override public void start(Stage stage) throws IOException { scene = new Scene(loadFXML("primary"), 640, 480); stage.setScene(scene); stage.show(); } static void setRoot(String fxml) throws IOException { scene.setRoot(loadFXML(fxml)); } private static Parent loadFXML(String fxml) throws IOException { FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml")); return fxmlLoader.load(); } public static void main(String[] args) { launch(); } }
まずjavafxのウィンドウの構造を説明します
一番外側の枠にあたるのが「stage」です。この中の台紙の部分が「scene」になります。この上にパネルとして「pane」があり、そう上に部品が乗るという感じになります。
javaではmainが最初に起動されますのでlaunch()が起動されます。これによってApplicationクラスが動きjavafxが起動されます。startメソッドがその起動時にコールされるもので、ここで初期化を行います。
起動時の枠としてstageが渡されるので、そこにprimary.fxmlで宣言された画面構造が台紙として宣言され、その台紙をstageにsetして、表示showします。
続いて第1画面の説明です。画面ファイルはサンプルとして2つ用意されています。
resourcesの配下のパッケージ配下に2つのfxmlファイルがあります。
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.Button?> <?import javafx.geometry.Insets?> <VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.co.etlab.PrimaryController"> <children> <Label text="Primary View" /> <Button fx:id="primaryButton" text="Switch to Secondary View" onAction="#switchToSecondary"/> </children> <padding> <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" /> </padding> </VBox>
javafxで画面がshowされると、その中の「fx:controller=」で宣言されたパッケージがコントロールを受け持つことになります。
でもこれでは画像がどう出るのかわかりません。そこでscenebuilderの登場です。見たいfxmlファイル上で右クリックして「open in scene builder」をクリックします
これが表示された画面です。といっても宣言されているのはペインPaneにあたる部分から下の階層です。stageやsceneはプログラムで書きます。
中身を見ていきましょう。ボタンをクリックします。右側のcodeの中身を展開すると
「fx.id」にprimaryButtonとあります。これがjavafxで宣言したボタンの名前です。プログラム中で使用できます。また「onAction」にswicthToSecondaryとありますが、これがボタンをクリックしたときにリンクされるメソッドの名前で、これをプログラム中に記述します。
ということで、この画面のプログラムを見てみましょう
package jp.co.etlab; import java.io.IOException; import javafx.fxml.FXML; public class PrimaryController { @FXML private void switchToSecondary() throws IOException { App.setRoot("secondary"); } }
画面が呼び出されても、このプログラムはなにも動きません。ただボタンが押されるとそこに宣言してあった「switchToSecondary」メソッドが起動されて、プログラムが動きます。書かれているのは、同じstageを使って、secondary.fxmlをsceneに貼って動けという指示です。secondary.fxmlの中身を見てみましょう
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.Button?> <?import javafx.geometry.Insets?> <VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.co.etlab.SecondaryController"> <children> <Label text="Secondary View" /> <Button fx:id="secondaryButton" text="Switch to Primary View" onAction="#switchToPrimary" /> </children> <padding> <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" /> </padding> </VBox>
最初の画面とほぼ同じで、secondarycontrollerをコントローラーとすること、ボタンが押されたら switchtoprimaryが呼ばれることが書いてあります。
同様にscenebuilderで見てみましょう
同様で、ボタンを押すとprimaryに戻るようになっています。また左の階層をみるとPaneの一種のVBOXという部品を縦に並べる性質のあるPaneの中に中央揃えでlabelとbuttonが入っているので、縦に部品が並んでいるのがわかります。HBOXでは横に並びます
では、このままプログラムをコンパイル実行してみましょう
mainの上にrunがありますので、ここをクリック
ボタンを押すたびにscaneの中身が書き変わります
計算プログラムを作ってみる
まずは、前回のjavafxのサンプルを作り、そのあと不要なsecondarycontroller.javaとsecond.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.TextField?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.VBox?> <VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.co.etlab.PrimaryController"> <children> <Label text="整数の足し算を行います" /> <HBox prefHeight="0.0" prefWidth="200.0"> <children> <TextField fx:id="arg1" prefHeight="26.0" prefWidth="55.0" /> <Label text="+" /> <TextField fx:id="arg2" prefHeight="26.0" prefWidth="55.0" /> <Label text="=" /> <TextField fx:id="ans" editable="false" prefHeight="26.0" prefWidth="67.0" /> </children> </HBox> <HBox alignment="CENTER" prefHeight="0.0" prefWidth="200.0"> <children> <Button fx:id="calcButton" onAction="#calc" text="calc"> <HBox.margin> <Insets right="10.0" /> </HBox.margin> </Button> <Button fx:id="closeButton" mnemonicParsing="false" onAction="#close" text="exit" /> </children> </HBox> </children> <padding> <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" /> </padding> </VBox>
画面を作るコツは、縦に並べるVBOXと横に並べるHBOXを組み合わせて作ることです。今回はVBOXの中にHBOXを入れています。scenebuilderで作るときは左のコンテナーの中のHBOXをドラッグして左下の階層のVBOXの上にドラッグすることです。下にドラッグするとその下に入ってしまいますが、上にドロップするとその配下に入ります。間違っても何回でも移動できるのでやってみましょう。
作ったのは入力が2つ、出力が1つ、ボタンが2つの画面です。縦にVBOXが並び、その中にHBOXを入れて、その中にTextFieldやButtonが並んでいます。
calcボタンをクリックすると「calc」メソッドにつながります。
ではプログラムを作っていきましょう。まずはApp.javaから
package jp.co.etlab; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; /** * JavaFX App */ public class App extends Application { private static Scene scene; @Override public void start(Stage stage) throws IOException { scene = new Scene(loadFXML("primary"), 240, 148); stage.setScene(scene); stage.setTitle("足し算"); stage.show(); } static void setRoot(String fxml) throws IOException { scene.setRoot(loadFXML(fxml)); } private static Parent loadFXML(String fxml) throws IOException { FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml")); return fxmlLoader.load(); } public static void main(String[] args) { launch(); } }
App.javaはほとんど変わっていません。setTitleが増えただけです。
package jp.co.etlab; import java.io.IOException; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.TextField; import javafx.stage.Stage; public class PrimaryController { @FXML private TextField arg1; @FXML private TextField arg2; @FXML private TextField ans; @FXML private Button closeButton; @FXML private void calc() throws IOException { String strArg1 = arg1.getText(); String strArg2 = arg2.getText(); Integer intArg1 = 0; Integer intArg2 = 0; int intAns = 0; try { intArg1 = Integer.parseInt(strArg1); intArg2 = Integer.parseInt(strArg2); intAns = intArg1 + intArg2; ans.setText(String.valueOf(intAns)); } catch (Exception e) { System.out.println(e.getMessage()); } } @FXML private void close() throws IOException { Stage stage = (Stage)closeButton.getScene().getWindow(); stage.close(); } }
primarycontroller.javaは入力の部分、計算の部分、出力の部分、終了の部分が増えています。
@FXMLを宣言して記述した部分がjavafx用のコーディングです。入力のTextFieldやButtonを宣言します。fx:idで宣言した名前で宣言すれば画面とつながります。
またデータの取得、出力が setText,getTextで行うことができます。文字としてやり取りするので数値にコンバートしてから計算を行います。
終了で少し変なことをしています。closeButtonから逆に元のstageを求めて、stageをcloseすることでjavafxを終了しています。画面を隠したりするのにもこの方法が扱えます。
calcボタンにcalcメソッドをリンクしています。
コンパイルして実行してみましょう。
TextFieldに数値を入れてcalcボタンを押すと結果が出力されます。計算が出来ないときは画面にはでないでエラーがコマンドに出力されます。calcモジュールやcloseモジュールを書いておけば動きます。exitボタンで終了します。
画面でデータ渡しをしてみる
単純に画面が切り替わるのであればひな形でやっているように、Web同様切り替えることができます。
ただ、VBなどでやっている方法は、どれかメインになる画面を決めておいて、そこから子画面を呼び出して処理が終わったら元のメインに帰るという方法です。こうしておけばメインでの終了がプログラムの終了になります。ばらばらに作ってしまうと終了のタイミングがわからなくなります。
今回はメインとしたprimaryを作り、loginが済んでいないのならばsecondaryに飛んでloginさせてからメインに戻り、login後thirdに飛び処理をしてからprimaryに戻るのをやってみます。図にすると以下のようになります。
まずはjavafxのひな形を作ります。primary.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.layout.VBox?> <VBox alignment="CENTER" prefHeight="162.0" prefWidth="192.0" spacing="20.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.co.etlab.PrimaryController"> <children> <Label text="Main" /> <Button fx:id="loginButton" onAction="#switchToLogin" text="Login" /> <Button fx:id="helloButton" mnemonicParsing="false" onAction="#switchToThird" text="Hello" /> <Button fx:id="exitButton" mnemonicParsing="false" onAction="#exitProgram" text="exit" /> </children> <padding> <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" /> </padding> </VBox>
まずは、ボタンを3つに改造します。それぞれ名前をつけて飛び先を宣言して下さい
続いてlogin画面をsecondaryとして作ります
<?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.PasswordField?> <?import javafx.scene.control.TextField?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.VBox?> <VBox alignment="CENTER" prefHeight="235.0" prefWidth="240.0" spacing="20.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.co.etlab.SecondaryController"> <children> <Label text="login window" /> <HBox prefHeight="22.0" prefWidth="200.0"> <children> <Label text="ID"> <HBox.margin> <Insets right="11.0" /> </HBox.margin> <padding> <Insets left="20.0" /> </padding> </Label> <TextField fx:id="identer" prefHeight="26.0" prefWidth="151.0" /> </children> </HBox> <HBox prefHeight="35.0" prefWidth="200.0"> <children> <Label text="pass"> <padding> <Insets left="20.0" /> </padding></Label> <PasswordField fx:id="passenter" /> </children> </HBox> <Button fx:id="loginButton" mnemonicParsing="false" onAction="#loginCheck" text="Login" /> <Button fx:id="cancelButton" onAction="#switchToPrimary" text="cancel" /> </children> </VBox>
ID,passwordを入力します。idはTextField、passwordはpasswordField、loginボタンとcancelボタンを入れます
続いて、3番目の画面です。idをユーザー名として表示するだけです。戻るボタンでメインに戻ります
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.Label?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.VBox?> <VBox alignment="CENTER" prefHeight="162.0" prefWidth="192.0" spacing="20.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.co.etlab.ThirdController"> <children> <Label text="Third" /> <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0"> <children> <Label text="こんにちは" /> <Label fx:id="iddisp" /> <Label text="さん" /> </children></HBox> <Button fx:id="returButton" mnemonicParsing="false" onAction="#returnProgram" text="return to main" /> </children> <padding> <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" /> </padding> </VBox>
メインに戻ったらexitで終了します。
つづいて、プログラムを説明します。App.javaはひな形のままですので省略します。
つづいて、primaryController.javaです。これをメインの画面としてloginを呼び出したり、子画面を呼び出したりしてみます。
package jp.co.etlab; import java.io.IOException; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.stage.Stage; public class PrimaryController { private String id=""; private String pass="abc"; private Stage stage; private Scene scene; private Parent root; @FXML private Button exitButton; public void setId(String id) { this.id = id; } public String getId() { return this.id; } @FXML private void switchToLogin() throws IOException { FXMLLoader loader = new FXMLLoader(getClass().getResource("secondary.fxml")); root = loader.load(); SecondaryController secondaryController = loader.getController(); secondaryController.setPassword(this.pass); //check用のpassを送る secondaryController.setMain(this); stage = new Stage(); scene = new Scene(root); stage.setScene(scene); stage.showAndWait(); } @FXML private void switchToThird() throws IOException { if (this.id.isEmpty()) { System.out.println("empty"); return; } FXMLLoader loader = new FXMLLoader(getClass().getResource("third.fxml")); Parent root = loader.load(); ThirdController thirdController = loader.getController(); stage = new Stage(); scene = new Scene(root); stage.setScene(scene); thirdController.setIdtext(this.id); stage.showAndWait(); } @FXML private void exitProgram() throws IOException { Stage stage = (Stage)exitButton.getScene().getWindow(); stage.close(); } }
処理の説明をします。起動された時、特になにも行っていません。ボタンが3つ表示されていますが、この状態ではログインが済んでいないので、this.idには空文字が入っているだけです。この状態では「hello」ボタンを押しても、先に進むことはできません。
「login」ボタンが押されて、いったんlogin画面に移動します。そのために「switchTloLogin」メソッドが動きます。ここで次の画面のsecondary.fxmlを読み込んで、そのコントローラであるsecondaryControllerのインスタンスを作ります。このインスタンスを利用してsetPassメソッドを使ってpasswordの正解を送り込んで置きます。
そして、次のコードは無理やりなのですが、自分のインスタンスをsecondaryControllerに送ります。これをしないとsecondaryからprimaryのデータを返すことができなくなります。苦肉の策です。secondaryでprimaryの画面を作れば良いような気がしますが、そこで作ったprimaryとthisのインスタンスは別アドレスの別物です。なのでいまここのアドレスを渡しておく必要がありました。(良い方法を教えてください)
そしてここで作ったsecondaryは処理が移った後のsecondaryと同じものなのでデータはわたります。そしてprimaryからsecondary画面を呼び出して、終わるまでここで待ちます。
処理的にはここからsecondaryに移りますが、説明はログインが終わったとして続けます。ログインが成功するとsecondaryからidに入力された文字列がthis.idに渡ってきています。
すると先程まで「hello」で先にいけなかった部分がロックが外れて「switchToThird」ができるようになります。ThirdControllerのインスタンスを作ったら、そのインスタンスを使ってsetIdTextを行い画面に文字を表示します。ThirdControllerのthis.idは設定はしていますが、表示するタイミングがないのでsetTextを使って表示までやっておきます。こちらもThirdが終わるまでここで待ちます。
つづいて、secondaryControllerです。
package jp.co.etlab; import java.io.IOException; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.control.Button; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.stage.Stage; public class SecondaryController { @FXML private TextField identer; @FXML private PasswordField passenter; @FXML Button cancelButton; private String password; private PrimaryController main; public void setPassword(String pw) { this.password = pw; } public void setMain(PrimaryController a) { this.main = a; } @FXML private void loginCheck() throws IOException { String idname = identer.getText(); String password = passenter.getText(); //password check if (!password.equals(this.password)) { return; } this.main.setId(idname); this.switchToPrimary(); //exit } @FXML private void switchToPrimary() throws IOException { Stage stage = (Stage)cancelButton.getScene().getWindow(); stage.close(); } }
ここで行うのはidとpasswordに入力です。まず入力してもらってから「login」ボタンを押すと loginCheckメソッドが動きます。ここではあらかじめprimaryから送られているpasswordと入力された文字を比較して正しくなければ何もしません。正しければprimaryControllerのインスタンスを使って入力されたidを返します。
そして、switchToPrimaryを使ってこの画面を閉じます。
ログインに成功しなければあきらめて「cancel」を押してprimaryに戻ります。
つづいて、primaryに戻ってからの「hello」が押されてThird画面に移動します。
package jp.co.etlab; import java.io.IOException; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.stage.Stage; public class ThirdController { private String id; @FXML private Label iddisp; @FXML private Button returButton; public void setIdtext(String id) { this.id = id; iddisp.setText(id); } @FXML private void returnProgram() throws IOException { Stage stage = (Stage)returButton.getScene().getWindow(); stage.close(); } }
primaryで説明したように、Thirdにidを送って表示までしておきます。なので画面が出たときにすでにidが表示されています。これでidが確認できたので、「return to main」ボタンで画面を閉じます。すると呼び出したprimaryの画面が残っています。
primaryは、仕事が終わったので「exit」ボタンで画面を閉じて処理を終わります。
あとはコンパイルして実行です
「login」を押してlogin画面を表示します。primary画面は消えることなく残っています。
ここでidとpasswordを入れます。idはそのままユーザー名となり、パスワードは今回はabcで固定です。「login」ボタンを押します
ログインが成功すればprimaryに戻ります。間違えればなにもおきません。あきらめて「cancel」した場合ユーザー名は登録されません。
mainで「hello」ボタンを押すと今入力したidがユーザー名として表示されます。
「return to main」ボタンでmainに戻ります。そして「exit」で終了です