🐓

JavaFXを使ったGUIアプリ(5) 電卓アプリのスタイルを設定する

2023/09/20に公開

目次

はじめに

この記事では、前回の記事で作成した電卓アプリにスタイルシートを適用することにより、GUIの見栄えを良くします。そのために、JavaFXにおけるカスケード・スタイルシート(CSS)の基本的な使い方について説明します。

今回作成する電卓アプリは、以下のような画面を表示するプログラムです。文字のフォント、大きさ、背景色等をスタイルシートで設定しています。
Calculator

スタイルの設定方法

JavaFXアプリケーションのデフォルトのスタイルシートは、JavaFXランタイムのJARファイル(jrt-fs.jar)内にある modena.css(javafx.scene.control.skin.modena)が使用されます。ユーザは独自のスタイルシートを作成することにより、デフォルトのスタイルを上書きできます。詳細については、以下のリファレンスガイドに詳しく記載されていますので、この記事では具体的な使い方を示していきます。
https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/doc-files/cssref.html

CSSの基本形式

CSSは、セレクタ、プロパティ、値の3つで構成されます。

セレクタ { プロパティ:; プロパティ:; ... }

セレクタは、スタイルを適用する対象オブジェクトを指定するもので、以下のものがあります。

種類 記述例 説明
ID #first オブジェクトにユーザが設定した一意の識別子
Javaクラス .button オブジェクトが属するJavaクラス名から生成されたスタイルクラス名(.root はシーン全体に適用)
スタイルクラス .button1 オブジェクトにユーザが設定したスタイルクラス名(複数設定可)
疑似クラス .button:hover オブジェクトのスタイルクラス名と状態

プロパティと値は、HTML向けのCSSに準拠していますが、すべてのプロパティに接頭辞(-fx-)が付加されています。いくつかの例を示します。

分類 記述例 説明
テキストの設定 -fx-font: bold 12pt serif; 太字でサイズ12ポイントのserif書体
-fx-text-fill: red; 赤色の文字
-fx-text-alignment: right; 文字列の右詰め
領域の設定 -fx-border-width: 2px; 線幅2ピクセルの境界線
-fx-background-color: yellow; 黄色の背景色
-fx-padding: 6px; 境界からの上下左右6ピクセルのパディング

setStyleメソッドによるスタイル設定

個別のオブジェクトにスタイルを設定したい場合は、Nodeクラス共通のsetStyleメソッドでインラインのCSSスタイルを利用できます。インラインのCSSスタイルはプロパティと値のペアで構成され、1つ以上のペアをセミコロン(;)で区切った文字列で指定します。例えば、3つのボタンのスタイルをそれぞれ以下のように指定します。

    Button button1 = new Button("Button-1");
    Button button2 = new Button("Button-2");
    Button button3 = new Button("Button-3");
    button1.setStyle("-fx-font: bold 14pt serif; -fx-text-fill: blue;");
    button2.setStyle("-fx-border-color: red; -fx-padding: 6 12 6 12;");
    button3.setStyle("-fx-background-color: orange; -fx-background-radius: 12px;");
  • button1は、文字のフォントが太字で14ポイントのserif書体、文字色が青色
  • button2は、境界線が赤色、パディングが上右下左の順に6,12,6,12ピクセル
  • button3は、背景色が橙色、角の丸みが12ピクセル

setStyle使用例

CSSファイルによるスタイル設定

複数のオブジェクトに一括してスタイルを設定したい場合は、CSSファイルを利用できます。CSSファイルは、シーンに対して以下の方法でURI文字列として追加します。なお、rootは最上位のレイアウトペインで、1つのシーンに複数のCSSファイルを追加できます。

File cssFile = new File("CSSファイルのパス");
Scene scene = new Scene(root);
scene.getStylesheets().add(cssFile.toURI().toString());

CSSファイルには、例えば以下のような内容を記述します。

.root { -fx-font: normal 12pt serif }
.button { -fx-pref-width: 100px; }
#button1 { -fx-border-color: red; -fx-text-fill: red; }
.button2 { -fx-background-color: cyan; -fx-font-style: italic; }
.button:hover { -fx-border-style: solid; -fx-border-width: 2px; }
  • デフォルトは、文字のフォントが標準の太さで12ポイントのserif書体
  • ボタンクラスは、優先幅が100ピクセル
  • IDがbutton1のノードは、境界線が赤色、文字色が赤色
  • スタイルクラスがbutton2のノードは、背景色がシアンで文字のフォントがイタリック
  • ボタンをホバーしたときは、境界線が幅2ピクセルの実線

上記のCSSファイル(test.css)を使ったJavaのコード(一部)です。

    Button button1 = new Button("Button-1");
    Button button2 = new Button("Button-2");
    Button button3 = new Button("Button-3");
    button1.setId("button1");
    button2.getStyleClass().add("button2");

    HBox hbox = new HBox(6, button1, button2, button3);
    hbox.setPadding(new Insets(6));
    Scene scene = new Scene(hbox);

    File cssFile = new File("src/pkg1/test.css");
    scene.getStylesheets().add(cssFile.toURI().toString());

3つのボタンに適用されるスタイルは、以下のようになります。

  • button1は、.root, .button, .button:hover, #button1
  • button2は、.root, .button, .button:hover, .button2
  • button3は、.root, .button, .button:hover

StyleSheet使用例

電卓アプリの改良

CSSファイル

以下のCSSファイル(CalcStyle.css)を作成します。

/*
 * Style for ExCalculator2
 */

.label {
    -fx-font: italic 10pt serif;
}
.text-field {
    -fx-font: bold 20pt sans-serif;
    -fx-alignment: center-right;
}
.button {
    -fx-font: 16pt sans-serif;
    -fx-text-fill: white;
    -fx-background-color: black;
}
.button:hover {
    -fx-background-color: gray;
}
.button1 {
    -fx-background-color: navy;
}
.button2 {
    -fx-font-size: 10pt;
    -fx-background-color: firebrick;
}
.button3 {
    -fx-font-size: 10pt;
}
.vbox {
    -fx-padding: 6px;
    -fx-background-color: silver;
}

電卓アプリのソースコード

上記のCSSファイルを使用した電卓アプリのソースコードを以下に示します。なお、ボタンの動作は前回の電卓アプリ(ExCalculatorクラス)と同じメソッドを使用するため、ExCalculatorのサブクラスとして実装しています。

package pkg1;

import java.io.File;

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

public class ExCalculator2 extends ExCalculator {

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

    @Override
    public void start(Stage primaryStage) {
        Label label = new Label("Calculator");
        TextField textField = new TextField("0");
        // テキストフィールドを編集不可にする
        textField.setEditable(false); // 注1
        // テキストフィールドの優先幅を200に、優先高を50にする
        textField.setPrefSize(200, 50);

        String[] keyTop = {
                "7", "8", "9", "CE", "AC",
                "4", "5", "6", "+", "-",
                "1", "2", "3", "\u00D7", "\u00F7",
                "0", "\u00B7", "+/-", "BS", "=",
        };
        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;
                    calculate(event, textField);
                });
                // 計算ボタンのスタイルクラスをbutton1にする
                button[i].getStyleClass().add("button1");
            } else { // 入力ボタンとクリアボタン
                button[i].setOnAction(event -> {
                    if (!imode) {
                        imode = true;
                        textField.setText("0");
                    }
                    input(event, textField);
                });
                if (i == 3 || i == 4) {
                    // クリアボタンのスタイルクラスをbutton2にする
                    button[i].getStyleClass().add("button2");
                } else if (i == 17 || i == 18) {
                    // +/-とBSボタンのスタイルクラスをbutton3にする
                    button[i].getStyleClass().add("button3");
                }
            }
            button[i].setMinSize(40, 40);
        }
        TilePane tilePane = new TilePane(6, 6, button);
        pressed = new Label("");
        HBox hbox1 = new HBox(20, label, pressed);
        hbox1.setAlignment(Pos.CENTER);
        VBox vbox = new VBox(6, hbox1, textField, tilePane);
        // VBoxのスタイルクラスをvboxに設定する
        vbox.getStyleClass().add("vbox"); // 注2
        Scene scene = new Scene(vbox);
        // CSSファイルを設定する
        File cssFile = new File("src/pkg1/CalcStyle.css"); // 注3
        scene.getStylesheets().add(cssFile.toURI().toString());
        primaryStage.setScene(scene);
        primaryStage.setTitle("Calculator");
        primaryStage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }

}

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

  1. テキストフィールドにキーボードから直接入力できないように、編集不可にしています
  2. レイアウトペインにはスタイルクラスが登録されていないため、自分で登録しています
  3. CSSファイルのパスはEclipseの実行環境のものです。各自の実行環境に合わせてパスを変更してください

おわりに

この記事では、JavaFXのスタイルシートを使って電卓アプリの見栄えを改良しました。スタイルシートを使用することにより、GUIのデザインとプログラムのロジックを分離して開発することが可能になります。ただし、スタイルの適用規則が複雑なため、意図した通りのスタイルにならない場合があります。動作確認は必ず行ってください。

Discussion