Part.1 -プロジェクト作成からUI再現(コードベース)-
Part.0からの続き
※見ていない方は以下を参照ください。
初めに今回のコードでは左のようなUIを作成することが可能です。
Part.0で少し触れていますが、SwingではコードベースでUIを構築するか、 「Swing UI Designer」というGUIベースでUI構築の二通り選択できます。UIにこだわる場合GUIベース構築のSwing UI Designerは多少UIをコードで編集することは可能だが、コードベースに比べ出来ることが制限されます。なので今回はコードベースでUIを構築する必要がありました。
「Swing UI Designer」についてintelliJが公式チュートリアルを公開しているので興味がある方は以下URLを参照ください。
プロジェクトの作成
プロジェクトの作成についてはある程度割愛して進めます。
私はIntelliJ IDEA のCommunity版(無料版)を利用していますので
他エディタを利用していて同じようにintteliJ IDEAを利用したい場合は準備をお願いします。
名前:Calculator
場所:任意
ビルドシステム:Maven
JDK:JDK17 ※バージョンもJava8以降であれば任意です。
ディレクトリ構成
少し進めた後のスクショなので余計なものやフォルダ名を変更していますが、
こだわりがなければデフォルトでもよいです。またUIフォルダ、LOGICフォルダも準備していますがファイルが大量になることもないので、こだわりがなければMainと同階層で問題ないです。

オブジェクト生成について軽く触れる
ソースコード説明に移る前のオブジェクト生成についてざっくり解説します。
基本以下2点を抑えておけば問題ないかと。
public void setLayout(java.awt.LayoutManager manager)
コンテナ(JFrame, JPanel など)に対して「部品をどう並べるか」というルールを設定するメソッド。
例setLayout(new BorderLayout());
BorderLayout()を指定した場合、.NORTH(上部) .SOUTH(下部) .WEST(左) .EAST(右) .CENTER(中央)を指定してオブジェクトを配置できる。
public void add( @NotNull java.awt.Component comp,Object constraints)
メソッド名の通りadd(追加)を行うメソッド。
生成しただけでは自動でオブジェクトの配置を行ってくれません。
例1 add(display, BorderLayout.NORTH);
第一引数に作成したオブジェクト変数名、第二引数に配置箇所のパラメータを指定する。
例2
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(gridPanel, BorderLayout.CENTER);
要素の中に要素を入れ子にすることも可能。
ソースコードの説明
以上で前置きが終わり、次にソースコードの説明に移ります。
Main.java
Mainクラスに記述することはほぼありません。作成クラスを呼び出すのみです。
Part.1ではCalculatorUIを作成するので以下1文を記述します。
※そもそも 「public static...」のおまじないを知らない方への説明は省略します。
import CalculatorUI;
public class Main {
public static void main(String[] args) {
new CalculatorUI();
}
}
CalculatorUI.java
CalculatorUI.javaファイルを作成し、UI周りの処理を記述します。
ロジックについては次回以降出てきますが、CalculatorLogic.javaに記載します。
基本的にコード内にコメントを記載しているので迷うことは無いと思います。
ここでは特に押さえておくとよい部分のみ説明します。
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
public class CalculatorUI extends JFrame {
public CalculatorUI() {
//メニューバー上の名称を設定
setTitle("電卓");
//初期表示の画面サイズ設定
setSize(350, 500);
// ウィンドウを閉じた際にJavaを完全終了させる。
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
// ===== 表示部分 =====
JTextField display = new JTextField("0"); //初期表示を0設定
display.setHorizontalAlignment(JTextField.RIGHT); //右詰め表示
display.setBackground(Color.decode("#2e2e2e")); //背景色を指定
display.setForeground(Color.WHITE); //文字色を指定
display.setFont(new Font("Segoe UI", Font.BOLD, 42)); //フォント形式を指定
display.setEditable(false); //直接入力を制限
// display.setMargin(new Insets(20, 12, 20, 12)); //備忘録:setMarginでは機能しなかった。↓で解決
display.setBorder(BorderFactory.createEmptyBorder(20, 12, 20, 12)); //ボーダーに対して余白設定
add(display, BorderLayout.NORTH);
// ===== 全体パネル =====
JPanel mainPanel = new JPanel(new BorderLayout());
add(mainPanel, BorderLayout.CENTER);
// ===== 右側:6×4 のボタンパネル =====
JPanel gridPanel = new JPanel(new GridLayout(6, 4, 0, 1)); //GridLayoutにて rows:6 cols:4のマス目を作成する。hgap,vgapはマージン
gridPanel.setBackground(Color.decode("#2e2e2e")); //背景色を指定
String[] buttons = {
"%", "CE", "C", "BACK",
"¹/X", "x²", "√", "÷",
"7", "8", "9", "×",
"4", "5", "6", "-",
"1", "2", "3", "+",
"+/-", "0", ".", "="
};
//各ボタンの背景色設定を行う
int index = 0;
for (int row = 0; row < 6; row++) {
for (int col = 0; col < 4; col++) {
String text = buttons[index++]; //テキストを代入
String color = "#3b3b3b"; //初期値は濃いグレー
// "="ボタンの場合には背景色をピンクに設定する。
if (text.equals("=")) {
color = "#ff7eb9";
// 3〜6行目の1〜3列目(数字・記号)は薄いグレー配色を指定する。
} else if (row >= 2 && col <= 2) {
color = "#4a4a4a"; // 薄いグレー
}
gridPanel.add(createButton(text, color));
}
}
mainPanel.add(gridPanel, BorderLayout.CENTER);
setVisible(true); //おまじない。これがないと表示されない。
}
private JButton createButton(String text, String colorHex) {
JButton btn = new JButton(text);
btn.setFont(new Font("Segoe UI", Font.BOLD, 18));
btn.setBackground(Color.decode(colorHex));
// テキストが"="の場合は背景色を黒に設定する
if (text.equals("=")) {
btn.setForeground(Color.BLACK);
}else {
btn.setForeground(Color.WHITE);
}
btn.setFocusPainted(false); //マウスクリック時に出る枠線を非表示にする
btn.setBorder(new RoundedBorder(8)); //Borderを継承したクラスで角丸を作成する。
return btn;
}
// ===== 角丸ボーダークラス =====
class RoundedBorder implements Border {
private int radius;
RoundedBorder(int radius) {this.radius = radius;}
@Override
public Insets getBorderInsets(Component c) {return new Insets(radius, radius, radius, radius);} //実装必須:余白設定
@Override
public boolean isBorderOpaque() {return false;} //実装メソッド:ボーダーが不透明かどうか
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
g.setColor(Color.decode("#2e2e2e")); // ボタン間の隙間をディスプレイ背景色と同じに設定
g.drawRoundRect(x, y, width - 1, height - 1, radius, radius); //角を丸くする
}
}
}
①表示部分
計算結果を表示する部分のUI

詳しい挙動は設定値やコメントアウトで確認してみてください。
1点上下の余白で1つ覚え書きを残しているのでそこだけ説明を追記しておきます。
// ===== 表示部分 =====
JTextField display = new JTextField("0"); //初期表示を0設定
display.setHorizontalAlignment(JTextField.RIGHT); //右詰め表示
display.setBackground(Color.decode("#2e2e2e")); //背景色を指定
display.setForeground(Color.WHITE); //文字色を指定
display.setFont(new Font("Segoe UI", Font.BOLD, 42)); //フォント形式を指定
display.setEditable(false); //直接入力を制限
// display.setMargin(new Insets(20, 12, 20, 12)); //備忘録:setMarginでは機能しなかった。↓で解決
display.setBorder(BorderFactory.createEmptyBorder(20, 12, 20, 12)); //マージン(余白)設定
add(display, BorderLayout.NORTH);
// display.setMargin(new Insets(20, 12, 20, 12));
//備忘録:setMarginでは機能しなかった。↓で解決
display.setBorder(BorderFactory.createEmptyBorder(20, 12, 20, 12));
//ボーダーに対して余白設定
上記について、Swing では margin は LAF に潰されることがあるけど、border は LAF に上書きされないので確実に余白が作れるみたいです。
[ コンポーネント全体 ]
+--------------------------------------+
| ← border(外側の余白) → |
| +--------------------------------+ |
| | ← margin(内側の余白) → | |
| | +--------------------------+ | |
| | | 実際の描画領域 | | |
| | +--------------------------+ | |
| +--------------------------------+ |
+--------------------------------------+
②テンキー部分
ここではグリッドレイアウトで新規パネルを作成し、6×4のマスを作成しています。
buttons配列を用意し、条件式にて背景色を切り分ける。各ボタンのフォントと角を丸くする処理も実施します。
角を丸くするには標準で行うことができないため、Borderクラスを継承したRoundedBorderクラスを作成しpaintBorderをオーバーライドして処理しています。多少の丸みを持たせるだけで幾分か見た目に影響があるので外せないポイントです。細かい所はコメントアウトやパラメータを変更して確認してみてください。
// ===== 右側:6×4 のボタンパネル =====
JPanel gridPanel = new JPanel(new GridLayout(6, 4, 0, 1)); //GridLayoutにて rows:6 cols:4のマス目を作成する。hgap,vgapはマージン
gridPanel.setBackground(Color.decode("#2e2e2e")); //背景色を指定
String[] buttons = {
"%", "CE", "C", "BACK",
"¹/X", "x²", "√", "÷",
"7", "8", "9", "×",
"4", "5", "6", "-",
"1", "2", "3", "+",
"+/-", "0", ".", "="
};
//ボタンによる背景色の設定を行う
int index = 0;
for (int row = 0; row < 6; row++) {
for (int col = 0; col < 4; col++) {
String text = buttons[index++]; //テキストを代入
String color = "#3b3b3b"; //初期値は濃いグレー
// "="ボタンの場合には背景色をピンクに設定する。
if (text.equals("=")) {
color = "#ff7eb9";
// 3〜6行目の1〜3列目(数字・記号)は薄いグレー配色を指定する。
} else if (row >= 2 && col <= 2) {
color = "#4a4a4a"; // 薄いグレー
}
gridPanel.add(createButton(text, color));
}
}
mainPanel.add(gridPanel, BorderLayout.CENTER);
setVisible(true); //おまじない。これがないと表示されない。
}
private JButton createButton(String text, String colorHex) {
//スタイルの修正
JButton btn = new JButton(text);
btn.setFont(new Font("Segoe UI", Font.BOLD, 18));
btn.setBorder(new RoundedBorder(8)); //Borderを継承したクラスで角丸を作成する。
btn.setBackground(Color.decode(colorHex));
// テキストが"="の場合は背景色を黒に設定する
if (text.equals("=")) {
btn.setForeground(Color.BLACK);
}else {
btn.setForeground(Color.WHITE);
}
btn.setFocusPainted(false); //マウスクリック時に出る枠線を非表示にする
return btn;
}
// ===== 角丸ボーダークラス =====
class RoundedBorder implements Border {
private int radius;
RoundedBorder(int radius) {this.radius = radius;}
@Override
public Insets getBorderInsets(Component c) {return new Insets(radius, radius, radius, radius);} //実装必須:余白設定
@Override
public boolean isBorderOpaque() {return false;} //実装メソッド:ボーダーが不透明かどうか
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
g.setColor(Color.decode("#2e2e2e")); // ボタン間の隙間をディスプレイ背景色と同じに設定
g.drawRoundRect(x, y, width - 1, height - 1, radius, radius); //角を丸くする
}
}
次回予告
以上でUIが完了しました。
次のPart.2では各ボタンに動きを持たせる為、イベント登録とPart4.テスト駆動開発の前段準備に着手したいと思います。



