🐥

JavaFXを使ったGUIアプリ(4) 電卓アプリを作成する

2023/09/06に公開

目次

はじめに

この記事では、前回の記事で作成した加減乗除アプリを拡張して、通常の電卓と同じようにボタンで数値の入力が可能な電卓アプリを作成します。また、レイアウト上のスペースと位置合わせ方法と、TilePaneについて説明します。

今回作成する電卓アプリは、以下のような画面を表示するプログラムです。加減乗除算を行う計算ボタンに加えて、数値を入力する入力ボタンと、リセットを行うクリアボタンが追加されています。
Calculator

レイアウト上のスペースと位置合せ

レイアウトペイン内の余白スペースには、以下の3種類があります。

  • スペーシング(spacing)およびギャップ(gap): ペイン内に配置されるオブジェクト間の間隔
  • パディング(padding): ペインの外枠とオブジェクトの間隔
  • マージン(margin): 個別のオブジェクトの外側に設ける間隔

これらのうち、パディングとマージンは上下左右の4方向の余白スペースをInsetsクラスで設定します。また、オブジェクトの位置合わせはPos列挙型で指定します。

Insetsクラス(javafx.geometry.Insets)

四角形領域の周辺のオフセット(余白スペース)を定義します。

  • コンストラクタ
    • Insets(double topRightBottomLeft) --- 上右下左の4辺すべてに同じ大きさのオフセットを設定する
    • Insets(double top, double right, double bottom, double left) --- 上右下左の4辺に異なる大きさのオフセットを設定する

Pos列挙型(javafx.geometry.Pos)

垂直方向および水平方向の位置決めおよび位置合せを示す定数の集まりです。

  • 列挙型定数
    • VPos_HPos --- 垂直方向がVPos、水平方向がHPosの位置に合わせる
      VPos: { TOP | BOTTOM | CENTER | BASELINE }
      HPos: { RIGHT | LEFT | CENTER }
    • CENTER --- 垂直方向、水平方向ともに中心の位置に合わせる

具体例として、3つのボタンをHBoxにレイアウトする場合の指定例を示します。

    HBox hbox = new HBox(button1, button2, button3); // デフォルト

default

    hbox.setSpacing(20); // スペーシングを20ピクセルに設定

spacing

    hbox.setPadding(new Insets(20)); // パディングを上下左右20ピクセルに設定

padding

    HBox.setMargin(button2, new Insets(20)); // button2のマージンだけを上下左右20ピクセルに設定

margin

    hbox.setPrefSize(240, 80); // HBoxの優先幅を240ピクセル、優先高を80ピクセルに設定
    hbox.setAlignment(Pos.TOP_LEFT); // 位置合わせを上左に設定

TOP_LEFT

    hbox.setAlignment(Pos.BOTTOM_RIGHT); // 位置合わせを下右に設定

BOTTOM_RIGHT

TilePaneクラス(javafx.scene.layout.TilePane)

タイルのような均一サイズの2次元グリッドにオブジェクトを配置します。タイルの大きさは配置するオブジェクトの中の最大のサイズで決まります。配置は左上を起点として水平方向または垂直方向にタイルを並べていき、ペインの外枠に到達したら折り返します。主要なコンストラクタとメソッドを以下に示します。

  • コンストラクタ
    • TilePane() --- 空のTilePaneレイアウトを作成する(水平方向、優先列数 5、水平垂直間隔 0)
    • TilePane(double hgap, double vgap, Node... children) --- 指定した水平垂直間隔で要素を含むTilePaneレイアウトを作成する
  • インスタンスメソッド
    • ObservableList<Node> getChildren() --- レイアウト内の要素のリストを取得する

電卓アプリの設計

ボタンの種類と機能

全部で20個のボタンを作成します。計算ボタンは加減乗除の計算を行い、入力ボタンは数値の入力を行い、クリアボタンは数値をゼロに戻します。なお、計算ボタンは前回作成したコードをそのまま使用するため、電卓アプリは加減乗除アプリのサブクラスにします。

種類 ラベル 機能
計算 + - × ÷ = の5種類 加減乗除算を実行する
入力 0 から 9 の10種類 数字1文字を入力する
· (中黒) 小数点を入力する
+/- 正負符号を反転する (正符号は表示しない)
BS 末尾1文字を削除する (数字がなくなった場合は 0 にする)
クリア CE 入力をクリアして、表示を 0 にする(計算結果は保持する)
AC 計算結果をクリアして、表示を 0 にする

レイアウト構造

画面のレイアウトは以下の構造とします。HBox_1は、加減乗除アプリと同じです。TilePaneには、20個のボタンを4行5列の配列に並べます。

電卓アプリの実装

上記の設計に基づいて実装した電卓アプリのソースコードを以下に示します。なお、Calculatorクラスは前回の加減乗除アプリの実装クラスです。

package pkg1;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
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.TilePane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class ExCalculator extends Calculator {

    static boolean imode = false; // 入力中かどうか

    @Override
    public void start(Stage primaryStage) {
        // ラベルを作成する
        Label label = new Label("Calculator");
        // テキストフィールドを作成する
        TextField textField = new TextField("0");
        // テキストフィールドを右詰めにする
        textField.setAlignment(Pos.CENTER_RIGHT);
        // テキストフィールドのフォントサイズを18に、優先高を40にする
        textField.setFont(new Font(18));
        textField.setPrefHeight(40);
        // ボタンの表示記号を準備する
        String[] keyTop = {
                "7", "8", "9", "CE", "AC",
                "4", "5", "6", "+", "-",
                "1", "2", "3", "\u00D7", "\u00F7",
                "0", "\u00B7", "+/-", "BS", "=",
        };
        // 20個のボタンを作成する
        Button[] button = new Button[20];
        for (int i = 0; i < 20; i++) {
            // ボタンをインスタンス化する
            button[i] = new Button(keyTop[i]);
            //  ボタンのアクションを登録する
            if (i == 8 || i == 9 || i == 13 || i == 14 || i == 19) { // 計算ボタン
                button[i].setOnAction(event -> {
                    // 入力中を解除する
                    imode = false; // 注1
                    // 計算ボタンの処理を行う
                    calculate(event, textField);
                });
            } else { // 入力ボタンとクリアボタン
                button[i].setOnAction(event -> {
                    // 入力中でなければ、クリアエントリをして入力状態にする
                    if (!imode) { // 注2
                        imode = true;
                        textField.setText("0");
                    }
                    // 入力ボタンの処理を行う
                    input(event, textField);
                });
            }
            // ボタンのフォントサイズを14に、最小サイズを40x40に設定する
            button[i].setFont(new Font(14));
            button[i].setMinSize(40, 40);
        }
        // タイルペイン(hgap=vgap=6)を作成する
        TilePane tilePane = new TilePane(6, 6, button);
        // 直前に押されたボタンを表示するラベルを作成する
        pressed = new Label("");
        // HBoxペインを作成し、2つのラベルを配置する
        HBox hbox1 = new HBox(20, label, pressed);
        // HBoxをセンタリングする
        hbox1.setAlignment(Pos.CENTER);
        // VBoxペイン(spacing=6)を作成し、作成した部品を配置する
        VBox vbox = new VBox(6, hbox1, textField, tilePane);
        // VBoxペインのパディングを6にする
        vbox.setPadding(new Insets(6));
        // シーンを作成し、ペインに入れる
        Scene scene = new Scene(vbox);
        // ステージにVBoxペインを入れる
        primaryStage.setScene(scene);
        // ステージのタイトルバーを設定する
        primaryStage.setTitle("Calculator");
        // ステージを表示する
        primaryStage.show();
    }

    // 入力ボタンとクリアボタンの処理を行う
    public static void input(ActionEvent event, TextField textField) {
        // 今回のボタンを識別する
        Button nowPressed = (Button) event.getSource();
        String key = nowPressed.getText();
        // クリアボタの処理を行う
        switch (key) {
        case "AC": // オールクリア
            result = 0.;
            pressed.setText("");
        case "CE": // クリアエントリ
            textField.setText("0");
            return;
        }
        // 現在のテキストフィールドの内容を取得する
        String s = textField.getText();
        // 英文字が含まれていれば、何もしない
        if (s.matches(".*[a-zA-Z].*")) { // 注3
            return;
        }
        // 入力ボタンの処理を行う
        switch (key) {
        case "BS": // バックスペース
            if (s.length() == 1 || s.length() == 2 && s.charAt(0) == '-') {
                textField.setText("0");
            } else {
                textField.setText(s.substring(0, s.length() - 1));
            }
            return;
        case "+/-": // 符号反転
            if (s.charAt(0) == '-') {
                textField.setText(s.substring(1));
            } else if (!s.equals("0")) {
                textField.setText("-" + s);
            }
            return;
        case "\u00B7": // 小数点
            if (!s.contains(".")) {
                textField.setText(s + ".");
            }
            return;
        default: // 0-9の数字
            if (!s.equals("0")) {
                textField.setText(s + key);
            } else {
                textField.setText(key);
            }
        }
    }

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

}

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

  1. 計算ボタンが押されると、表示されている数値の入力を終了させます
  2. 計算ボタンの直後に入力ボタンが押されたときは、表示されている数値をクリアしてから入力します
  3. 表示されている数値に英文字が含まれている場合は、指数表示またはNaNであるため、これ以上の入力はできないようにします

おわりに

この記事では、ボタンで数値の入力が可能な電卓アプリを作成しました。このアプリではレイアウトの余白スペースやフォントサイズ等をハードコーディングしていますが、JavaFXではスタイルシート(CSS)を使って指定するのが一般的です。次の記事ではスタイルシートを使って、もう少し見栄えの良い電卓アプリに仕上げる予定です。

Discussion