【Java発展学習17日目】SwingによるウィンドウUI制御

# GUIの種類
GUI(Graphical User Interface)の実現方法は、以下の2通り。

| GUI | 用途 | デザイン設計 |
|:--|:--|:--|
| WebUI | Webアプリケーション | HTML |
| ウィンドウUI | OSアプリケーション | プログラム |

# Swing APIにおける3つのウィンドウ
[参考: Swing](https://www.javadrive.jp/tutorial/)
**Swing API**が扱うウィンドウは、以下の3種類に分類される。

| ウィンドウ | 内容 | API |
|:--|:--|:--|
| アプレット | ブラウザへの埋め込みウィンドウ | `JApplet` |
| フレーム | アプリケーション用ウィンドウ | `JFrame` |
| ダイアログ | 対話入力用ウィンドウ | `JDialog` |

また、これらのウィンドウはメインスレッドとは独立した、描画処理を行う**UIスレッド**によって実行される。
`UIスレッド`は複数のスレッドの**総称**であり、中でもイベントハンドラが動作するスレッドを
`イベントディスパッチスレッド(EDT; event dispatch thread)`と呼ぶ。

ただし、イベントハンドラによる処理負荷が大きい場合は、イベントディスパッチスレッドとは異なるスレッドで
負荷の大きい処理を行う`SwingUtilities.invokeLater()`メソッドを先に呼び出し、処理を進めておく手法を用いる。

## SwingUtilities.invokeLater()

```java:invokeLater()メソッド
void SwingUtilities.invokeLater(Runnable doRun)
// パラメータ
// doRun: 処理(タスク)を定義する、「Runnableインタフェースの実装クラスオブジェクト」または「関数オブジェクト」
```

# Swing APIにおける7つのレイアウトマネージャ
[参考・出典: A Visual Guide to Layout Managers](https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html)
画面サイズやウィジェットに応じて自動レイアウトを行う**レイアウトマネージャ**は、以下の7つ。

`JFrame#setLayout()`メソッドで利用するレイアウトマネージャを指定することができ、
デフォルトレイアウトとして`BorderLayout`が設定されている。

| レイアウトマネージャ | 配置 | イメージ図 |
|:--|:--|:--:|
| BorderLayout | 上下左右・中央の5箇所 | ![BorderLayout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/50da75cd-58f6-d483-3f08-4c64f9018c2b.png) |
| BoxLayout | 縦または横一列 | ![BoxLayout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/a915127c-2558-7e73-7638-01fbffdeeca1.png) |
| CardLayout | ウィジェットに応じた動的レイアウト | ![CardLayout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/55e0356f-3a14-c654-78b6-62f8ba65ed20.png) ![CardLayout-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/aceaa4b7-1e5f-85dd-469c-51632ecf63f6.png) |
| FlowLayout | 折り返し可能な横一列 | ![FlowLayout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/d0eab24f-1109-0523-5ddb-d8e46a8414d4.png) |
| GridBagLayout | 自由サイズのセル分割 | ![GridBagLayout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/2878a412-76ba-f8cf-f224-23151d733158.png) |
| GridLayout | 固定サイズのセル分割 | ![GridLayout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/7fc375bf-0265-57f1-9241-44b8db318834.png) |
| GroupLayout | 縦横のグループ配置 | ![GroupLayout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/388ce796-dfd7-9cf5-b89f-7faf6de3fa84.png) |
| SpringLayout | 比率分割 | ![SpringLayout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/14902f2f-1224-43b2-43a4-95b858cebac3.png) ![SpringLayout-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/b84a676e-c129-51b2-a3a2-6ac87319506b.png) |

***

# フレームの生成
フレームは、`JFrame`クラスで定義される。
また、`JPanel`クラスで定義される**パネル**を用いて、フレーム内に**レイアウトをネスト**することができる。

## サンプルコード

```java:SwingFrame.java
import java.awt.*;
import javax.swing.*;

public class SwingFrame {
    public static void main(String[] args) {
        // フレームタイトルを指定したフレームの生成
        JFrame frame = new JFrame("Swing");

        // 「×ボタン」によるアプリケーションの終了
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // レイアウトマネージャの指定
        frame.setLayout(new BorderLayout());

        // フレームサイズの指定
        // -> setBounds()メソッドで表示位置とまとめて指定可能
        frame.setSize(400, 200);

        // 表示位置の指定
        // -> setBounds()メソッドでフレームサイズとまとめて指定可能
        frame.setLocation(100, 100);

        // モニターサイズに合わせた表示位置の指定
        frame.setLocationRelativeTo(null);

        // フレームの表示
        frame.setVisible(true);
    }
}
```

## 実行結果

![Frame.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/aa2f7cf1-f51d-8bdd-7069-9496630b3fe1.png)

***

# イベントハンドリング
イベント検知時にリスナによって呼び出されるイベントハンドラは、関数インタフェースである`ActionListener`インタフェースの
`actionPerformed()`メソッドをオーバーライドして記述する。

ここで、イベントを検知する主なウィジェットは、以下の通り。

| ウィジェット | クラス | 検知イベント |
|:--|:--|:--|
| ボタン | `JButton` | ボタンのクリック |
| チェックボックス | `JCheckBox` | チェックボックスのクリック |
| ラジオボタン | `JRadioButton` | ラジオボタンのクリック |
| テキストフィールド | `JTextField` | テキストフィールドでのEnterの入力 |
| パスワードフィールド | `JPasswordField` | パスワードフィールドでのEnterの入力 |

また、イベントの検知を待機する主なリスナインタフェースは、以下の通り。

ただし、複数のコールバックメソッドをもつリスナインタフェースの実装クラスではコールバックメソッドを全てオーバーライドする必要があるため、
`MouseListener`・`KeyListener`・`WindowListener`は、特定のイベントのみを処理する**イベントアダプタ**(`*Adapter`)クラスが利用される。

| インタフェース | 検知イベント | コールバックメソッド |
|:--|:--|:--|
| `ActionListener` | クリック・決定・実行 | actionPerformed() |
| `ChangeListener` | 状態の変化 | stateChanged() |
| `MouseListener` | マウス操作 | クリック: mouseClicked()<br>カーソルイン: mouseEntered()<br>カーソルアウト: mouseExited()<br>ボタンのプレス: mousePressed()<br>ボタンのリリース: mouseReleased() |
| `KeyListener` | キー操作 | キーのプレス: keyPressed()<br>キーのリリース: keyReleased()<br>キーのタイプ: keyTyped() |
| `WindowListener` | ウィンドウ操作 | アクティブ時: windowActivated()<br>非アクティブ時: windowDeactivated()<br>クローズ前: windowClosing()<br>クローズ後: windowClosed()<br>最初の可視化時: windowOpened()<br>通常 → 最小化時: windowIconified()<br>最小化 → 通常時: windowDeiconified() |

## サンプルコード

```java:SwingEvent.java
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;

public class SwingEvent {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Event Handler");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.setSize(400, 400);

        // ラベルの生成・ContentPaneへの追加
        JLabel label = new JLabel("Click following buttons.");
        frame.add(label, BorderLayout.NORTH);

        // ボタンの生成・イベントリスナ定義・ContentPaneへの追加
        // -> ActionListenerインタフェースの実装クラスを利用する場合
        JButton button1 = new JButton("button 1");
        button1.addActionListener(new bActionListener());
        frame.add(button1, BorderLayout.WEST);

        // ボタンの生成・イベントリスナ定義・ContentPaneへの追加
        // -> ActionListenerインタフェースを利用する場合
        JButton button2 = new JButton("button 2");
        button2.addActionListener(new ActionListener() {
            // イベントハンドラ
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Button 2 is clicked.");
                
            }
        });
        frame.add(button2, BorderLayout.CENTER);

        // ボタンの生成・イベントリスナ定義・ContentPaneへの追加
        // -> ラムダ式で記述する場合
        JButton button3 = new JButton("button 3");
        button3.addActionListener((ActionEvent e) -> {
            System.out.println("Button 3 is clicked.");
        });
        frame.add(button3, BorderLayout.EAST);

        frame.setVisible(true);
    }
}

// リスナクラス(ActionListenerインタフェースの実装クラス)
class bActionListener implements ActionListener {
    // イベントハンドラ
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button 1 is clicked.");
    }
}
```

## 実行結果

![イベントハンドラ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/d239cf24-5e85-f109-c804-01c1eb5d0c77.png)

***

# 用語集

| 用語 | 内容 |
|:--|:--|
| キャンバス(canvas) | ウィンドウ全体を1つのグラフィック領域と捉える画面設計手法 |
| ウィジェット(widget) | ウィンドウを画面部品の集まりと捉える画面設計手法 |
| ペイン(pane) | フレームによって管理される、ウィジェットを保持する1つの枠組み |
| イベントドリブンモデル(event driven model) | イベントハンドラを定義するプログラミングモデル |