🐤

JavaFXを使ったGUIアプリ(3) 加減乗除アプリを作成する

2023/08/27に公開

目次

はじめに

この記事では、前回の記事で説明できなかったJavaFXのイベントとレイアウトについて説明し、さらにカウンタアプリケーションをベースに加減乗除アプリを作成します。

今回作成する加減乗除アプリケーションは、以下のような画面を表示するプログラムです。
Calculator

ユーザがテキストフィールドに数値を入力して計算ボタンをクリックすることにより、加減乗除の計算を行います。なお、数値の入力はボタンではなく、キーボードから行います。

JavaFXのイベント

イベントは、ユーザーが行ったアクションをアプリケーションが検知し、それに対応できるようにするためのオブジェクトです。イベントには、ActionEvent、KeyEvent、MouseEventなど様々なタイプがあり、これらのイベントを処理するための総称型インタフェースがイベントハンドラ(EventHandler)です。以下、EventHandlerと広く利用可能なActionEventの概要を述べます。

EventHandlerインタフェース(javafx.event.EventHandler)

様々なイベントを処理できる関数型インタフェースで、ラムダ式の参照先として使用できます。

  • 定義
    • EventHandler<T extends Event> --- ハンドラが処理できるイベントクラスを定義する
  • メソッド
    • void handle(T event) --- 登録されたタイプのイベントが発生した場合に呼び出される

ActionEventクラス(javafx.event.ActionEvent)

ユーザの様々なアクションを表すイベントで、ボタンをクリックして指を離したときや、テキストフィールドでEnterキーを押したときなどのタイミングで発生します。

  • インスタンスメソッド
    • EventType<? extends ActionEvent> getEventType() --- イベントのタイプを取得する
    • Object getSource() --- イベントが最初に発生したオブジェクト(ノード)を取得する

イベントハンドラは、匿名クラスまたはラムダ式を使って定義する必要があります。ボタンがクリックされたときの処理を行うソースコードの構造を以下に示します。なお、匿名クラスを使うとコードが冗長になるので、通常はラムダ式を使います。

Button btn = new Button("ボタンのラベル");
btn.setOnAction(
    new EventHandler<ActionEvent>() { // 匿名クラス
    @Override
    public void handle(ActionEvent event) {
        // ボタンがクリックされたときの処理を記述する
    }
});
Button btn = new Button("ボタンのラベル");
btn.setOnAction(event -> { // ラムダ式
    // ボタンがクリックされたときの処理を記述する
});

JavaFXのレイアウト

JavaFXのレイアウトペインには複数のノードを自由に追加、削除できます。それを実現するのがオブザーバブルリスト(ObservableList)インタフェースです。これは内容の変更を監視可能なリストで、例えば新しいノードをリストに追加するとその結果をすぐに画面に反映させることができます。Javaで一般に使用されるListのサブインタフェースなのでListのメソッドが使用でき、さらに独自のメソッドが追加されています。なお、ObservableListはレイアウト以外でも使用されるJavaFXの重要なインタフェースです。

ObservableListインタフェース(javafx.collections.ObservableList)

様々な要素を保持して変更を監視可能なリストです。

  • 定義
    • ObservableList<E> --- リストで保持する要素の型を定義する
  • インスタンスメソッド
    • boolean addAll(E... elements) --- 可変引数で与えられた要素を追加する
    • boolean removeAll(E... elements) --- 可変引数で与えられた要素を削除する

VBoxにbtn1とbtn2の2つのボタンを配置する場合、以下の3つのコードは同じ結果になります。

VBox vbox = new VBox(btn1, btn2);
VBox vbox = new VBox(btn1);
vbox.getChildren().addAll(btn2); // addAllの代わりにaddも使用可能
VBox vbox = new VBox();
ObservableList<Node> oList = vbox.getChildren();
oList.addAll(btn1, btn2);

加減乗除アプリの設計

加減乗除の計算方法

加減乗除アプリは以下の条件で設計します。

  • エンドユーザは、テキストフィールドに数値を入力し、計算ボタンをクリックする
  • 加減乗除の計算は、数学的な優先順位ではなく、エンドユーザが入力した順に行う
  • 数値は整数と浮動小数点数のどちらも入力可とし、内部の演算は浮動小数点で行う
  • 入力および計算で生じるエラーの処理は特に何もせず、Javaの標準の処理に委ねる

一例として、以下の計算を行う場合を考えます。

5 \times 10 + 20 = 70

エンドユーザは、以下の順序で操作を行うことにより計算結果を得ることができるようにします。

操作内容 操作結果
初期状態 0
テキストフィールドに 5 を入力 5
\times ボタンをクリック 5
テキストフィールドに 10 を入力 10
+ ボタンをクリック 50
テキストフィールドに 20 を入力 20
= ボタンをクリック 70

ここでのポイントは、それまでの計算結果とユーザが押したボタンを記憶しておき、ユーザが次の数値を入力してボタンを押した直後に、記憶しておいた計算結果とボタンを参照して計算を行うことです。

レイアウト構造

画面のレイアウトは以下の構造とします。HBox_1には、前回押されたボタンを表示するラベルを追加します。HBox_2には、加減乗除と等号の5つのボタン(Unicode文字)を配置します。

加減乗除アプリの実装

上記の設計に基づいて実装した加減乗除アプリのソースコードを以下に示します。

// Basic Calculator
package pkg1;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
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;
import javafx.stage.Stage;

public class Calculator extends Application {

    static double result; // 直前の計算結果
    static Label pressed; // 直前に押された計算ボタンの種類

    @Override
    public void start(Stage primaryStage) {
        // ラベルを作成する
        Label label = new Label("Calculator");
        // ラベルの優先幅を100にする
        label.setPrefWidth(100); // 注1
        // テキストフィールドを作成する
        TextField textField = new TextField("0");
        // テキストフィールドの優先桁数を20に設定する
        textField.setPrefColumnCount(20); // 注2
        // 計算ボタンの記号を準備する
        String[] operator = { "+", "-", "\u00D7", "\u00F7", "=" }; // +-×÷=
        // 5個の計算ボタンを作成する
        Button[] button = new Button[5];
        for (int i = 0; i < 5; i++) {
            // ボタンをインスタンス化する
            button[i] = new Button(operator[i]);
            //  ボタンのアクションを登録する
            button[i].setOnAction(event -> calculate(event, textField));
            // ボタンの優先幅を30に設定する
            button[i].setPrefWidth(30); // 注3
        }
        // 直前に押されたボタンを表示するラベルを作成する
        pressed = new Label();
        // HBoxペインを作成し、2つのラベルを配置する
        HBox hbox1 = new HBox(label, pressed);
        // HBoxペインを作成し、計算ボタンを配置する
        HBox hbox2 = new HBox(button);
        // VBoxペインを作成し、作成した部品を配置する
        VBox vbox = new VBox(hbox1, textField, hbox2);
        // シーンを作成し、ペインに入れる
        Scene scene = new Scene(vbox);
        // ステージにVBoxペインを入れる
        primaryStage.setScene(scene);
        // ステージのタイトルバーを設定する
        primaryStage.setTitle("Calculator");
        // ステージを表示する
        primaryStage.show();
    }

    // 計算をする
    public static void calculate(ActionEvent event, TextField textField) {
        // テキストフィールドの内容を取得して、数値に変換する
        double value = Double.parseDouble(textField.getText()); // 注4
        // 前回のボタンの計算を行う
        switch (pressed.getText()) {
        case "+": // 加算
            result += value;
            break;
        case "-": // 減算
            result -= value;
            break;
        case "\u00D7": // 乗算
            result *= value;
            break;
        case "\u00F7": // 除算
            result /= value;
            break;
        default: // 入力値
            result = value;
        }
        // 計算結果を文字列に変換してテキストフィールドに書き込む
        if (result == (long) result) { // 整数の場合
            textField.setText(String.format("%d", (long) result));
        } else { // 整数以外の場合
            textField.setText(String.format("%g", result)); // 注5
        }
        // テキストフィールドにフォーカスする
        textField.requestFocus(); // 注6
        // 今回のボタンを識別する
        Button nowPressed = (Button) event.getSource();
        // 今回のボタンを記憶する
        pressed.setText(nowPressed.getText());
    }

    public static void main(String[] args) {
        // アプリケーションを起動する
        Application.launch(args);
    }

}

ソースコード上に「注」を記した部分について補足説明をしておきます。

  1. ラベルの幅は、デフォルトではテキストが表示可能な最小の大きさになりますが、優先幅を100ピクセルに指定することによって大きさを広げています
  2. テキストフィールドの幅は、20文字を表示可能なように優先桁数を20文字に指定していますが、内部の計算をdouble型で行っているため、精度は15桁程度になります
  3. ボタンの幅も表示するテキストによって変化するため、優先幅を30ピクセルに指定することによって大きさを揃えています
  4. テキストフィールドの内容はノーチェックなので、double型に変換する際に例外が発生したり、桁落ちが生じたりする可能性があります
  5. 計算結果が整数でない場合は、有効数字6桁しか表示されませんが、内部ではdouble型で保持している値を次の計算で使用します
  6. ボタンが押された後もテキストフィールドを選択しておくことにより、次の数値をすぐに入力できるようにしています

おわりに

この記事では、JavaFXのイベントとレイアウトについて説明し、四則計算アプリを作成しました。次の記事では、普通の電卓と同じように数字のボタンによる数値の入力が可能な計算機アプリに拡張していきます。

Discussion