Swingのフレームで画面遷移するアプリケーションを作った

Swingのフレーム(JFrame)とパネル(JPanel)で画面遷移するアプリケーションを作ってみた。アプリケーションとは言ってもハイアンドローという簡単な数当てゲームなのだが、今後本格的にSwingで開発するようになったときのために自分用にメモしておく。

Swingで画面遷移するアプリケーション

Swingでハイアンドローを作った。ハイアンドローは、現在の数字が次に来る数字より大きいか小さいかを当てるゲームである。数字は0~100までが使用される。次に来る数字が大きいと思ったらHigh、小さいと思ったらLowのボタンをクリックする。クリックしたボタンに応じて結果が分岐し、自分の答えが当たっていれば「正解」のメッセージ、外れていれば「不正解」のメッセージを表示する。

ソースコードは以下である。

import java.awt.Container;
import javax.swing.JFrame;
import frame.BaseFrame;
public class HighAndLow {
    private static JFrame mainFrame;
    private static Container mainContentPane;
    private static final int DEFAULT_NUMBER = 50;
    private static int currentNumber = DEFAULT_NUMBER;
    private static int nextNumber = DEFAULT_NUMBER;
    
    public static void main(String[] args) {
        mainFrame = new BaseFrame("ハイアンドロー").getBaseFrame();
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainContentPane = mainFrame.getContentPane();
        new SelectHighOrLow();
    }
    
    public static JFrame getMainFrame() {
        return mainFrame;
    }
    
    public static Container getMainContentPane() {
        return mainContentPane;
    }
    
    public static void setCurrentNumber(int num) {
        currentNumber = num;
    }
    
    public static int getCurrentNumber() {
        return currentNumber;
    }
    
    public static void setNextNumber(int num) {
        nextNumber = num;
    }
    
    public static int getNextNumber() {
        return nextNumber;
    }
}

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import panel.BasePanel;
import button.BaseButton;

public class SelectHighOrLow implements ActionListener {
    private JPanel selectHighOrLowPanel;
    private JPanel buttonBasePanel;
    private JButton highButton;
    private JButton lowButton;
    public SelectHighOrLow() {
        selectHighOrLowPanel = createSelectHighOrLowPanel();
        HighAndLow.getMainContentPane().add(selectHighOrLowPanel, BorderLayout.CENTER);
        //再描画
        HighAndLow.getMainFrame().validate();
    }
    
    public JPanel createSelectHighOrLowPanel() {
        JPanel selectHighOrLowPanel = new BasePanel().getBasePanel();
        JLabel currentNumberLabel = new JLabel(String.valueOf(HighAndLow.getCurrentNumber()));
        currentNumberLabel.setFont(new Font("MS ゴシック", Font.PLAIN, 30));
        
        //水平方向
        currentNumberLabel.setHorizontalAlignment(JLabel.CENTER);
        //垂直方向
        currentNumberLabel.setVerticalAlignment(JLabel.CENTER);
        
        selectHighOrLowPanel.setLayout(new BorderLayout());
        currentNumberLabel.setLayout(new BorderLayout());
        selectHighOrLowPanel.add(currentNumberLabel, BorderLayout.CENTER);
        buttonBasePanel = new BasePanel().getBasePanel();
        buttonBasePanel.setLayout(new FlowLayout());
        selectHighOrLowPanel.add(buttonBasePanel, BorderLayout.PAGE_END);
        highButton = new BaseButton("High").getBaseButton();
        lowButton = new BaseButton("Low").getBaseButton();
        buttonBasePanel.add(lowButton);
        buttonBasePanel.add(highButton);
        
        //再描画
        HighAndLow.getMainFrame().validate();
        lowButton.addActionListener(this);
        lowButton.setActionCommand("lowButton");
        highButton.addActionListener(this);
        highButton.setActionCommand("highButton");
        
        return selectHighOrLowPanel;
    }
    
    public void actionPerformed(ActionEvent e) {
        String cmd = e.getActionCommand();
        lowButton.removeActionListener(this);
        highButton.removeActionListener(this);
        buttonBasePanel.remove(lowButton);
        buttonBasePanel.remove(highButton);
        selectHighOrLowPanel.remove(buttonBasePanel);
        HighAndLow.getMainContentPane().remove(selectHighOrLowPanel);
        new Result(cmd);
    }
}

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import panel.BasePanel;
import button.BaseButton;
public class Result implements ActionListener {
    private JPanel resultPanel;
    private JPanel buttonBasePanel;
    private JButton newGameButton;
    private String correctAnswerMessage;
    private String wrongAnswerMessage;
    private String buttonCommand;
    
    public Result(String cmd) {
        pushButton(cmd);
        resultPanel = createResultPanel();
        HighAndLow.getMainContentPane().add(resultPanel, BorderLayout.CENTER);
        
        //再描画
        HighAndLow.getMainFrame().validate();
    }
    
    public JPanel getResultPanel() {
        return resultPanel;
    }
    
    public String getCorrectAnswerMessage() {
        return correctAnswerMessage;
    }
    
    public String getWrongAnswerMessage() {
        return wrongAnswerMessage;
    }
    
    public void pushButton(String command) {
        if(command.equals("lowButton")) {
            buttonCommand = command;
        }
        if(command.equals("highButton")) {
            buttonCommand = command;
        }
    }
    
    public boolean judgementHighAndLow() {
        Random rnd = new Random();
        int randomNum = rnd.nextInt(101);
        int currentNumber = HighAndLow.getCurrentNumber();

        //前回の数字と一緒だった場合は乱数再生成
        if(randomNum == currentNumber) {
            this.judgementHighAndLow();
        }

        HighAndLow.setNextNumber(randomNum);
        int nextNumber = HighAndLow.getNextNumber();
        correctAnswerMessage = "正解です。今回の数は" + nextNumber + "でした。";
        wrongAnswerMessage = "不正解です。今回の数は" + nextNumber + "でした。";
        if(currentNumber < nextNumber) {
            if(! buttonCommand.equals("highButton")) {
                return false;
            }
        } else {
            if(! buttonCommand.equals("lowButton")) {
                return false;
            }
        }
        
        return true;
    }
    
    public JPanel createResultPanel() {
        JPanel resultPanel = new BasePanel().getBasePanel();
        resultPanel.setLayout(new BorderLayout());
        boolean isWinOrLose = judgementHighAndLow();
        JLabel resultMessageLabel = new JLabel(String.valueOf(getWrongAnswerMessage()));
        if(isWinOrLose) {
            resultMessageLabel = new JLabel(String.valueOf(getCorrectAnswerMessage()));
        }
        resultMessageLabel.setFont(new Font("MS ゴシック", Font.PLAIN, 15));
        
        //水平方向
        resultMessageLabel.setHorizontalAlignment(JLabel.CENTER);
        //垂直方向
        resultMessageLabel.setVerticalAlignment(JLabel.CENTER);
        
        resultPanel.add(resultMessageLabel, BorderLayout.CENTER);
        buttonBasePanel = new BasePanel().getBasePanel();
        buttonBasePanel.setLayout(new FlowLayout());
        newGameButton = new BaseButton("NewGame").getBaseButton();
        resultPanel.add(buttonBasePanel, BorderLayout.PAGE_END);
        
        //再描画
        buttonBasePanel.add(newGameButton);
        HighAndLow.getMainFrame().validate();
        newGameButton.addActionListener(this);
        newGameButton.setActionCommand("newGameButton");
        
        return resultPanel;
    }
    
    public void actionPerformed(ActionEvent e) {
        newGameButton.removeActionListener(this);
        buttonBasePanel.remove(newGameButton);
        resultPanel.remove(buttonBasePanel);
        HighAndLow.getMainContentPane().remove(resultPanel);
        HighAndLow.setCurrentNumber(HighAndLow.getNextNumber());
        new SelectHighOrLow();
    }
}

ソースコードの解説

解説に入る前に、まずは実際のプログラムをこちらからダウンロードして動作を確認してもらうと理解が捗るはず。ダウンロードが終わったらコマンドライン(コマンドプロンプト)から以下のコマンドを打ち込むことで実行することが出来る。

java -jar HighAndLow.jar

それぞれのクラスの役割を説明する。

HighAndLowクラス

HighAndlowクラスは、最上位の親となるコンテナや現在の数字・次に来る数字などを作成・管理するクラスである。この親となるコンテナはフレーム(mainFrame)として宣言している。このmainFrameは至る所で使い回すので上書きなどの間違いが起らないようにprivate、このフレームは一つしか存在してはいけないので更にstaticとして宣言している(フレームのインスタンスが複数作成されると、新しいフレームとして別のウィンドウが作成される)。
mainFrameを作成するのに使用しているBaseFrameクラスはJFrameクラスを継承したクラスである。”Base”と名前の付くクラスはそれぞれのコンテナ・コンポーネントクラスを継承したクラスであり、これらは単にオブジェクト(コンテナ・コンポーネント)に対してプロパティを設定している。
BaseFrameクラスによって作成されたmainFrameは何枚もの階層化されたペインで構成されており、このうちコンポーネントを配置していくペインをmainContentPaneとしてmainFrameから取得している。
mainFrameの作成が終わったらSelectHighOrLowクラスを呼び出す。

SelectHighOrLowクラス

SelectHighOrLowクラスは、ハイアンドローの中核となる数字、Highボタン、Lowボタンを表示し、プレイヤーに次に来る数字が現在表示している数字より上か下かを選ばせるクラスである。現在の数字ラベル(currentNumberLabel)、Highボタン(highButton)、Lowボタン(lowButton)、そしてこれらを格納してレイアウトを設定するボタンベースパネル(buttonBasePanel)、更にbuttonBasePanelを格納してレイアウトを設定するセレクトハイORローパネル(selectHighOrLowPanel)を作成する。selectHighOrLowPanelは、更にHighAndLowクラスで作成したmainContentPaneに格納される。
作成されたhighButtonとlowButtonにはイベントリスナが設定される。イベントリスナによって、これらのボタンがクリックされたことが検知されると、actionPerformedメソッドが実行され、highButtonかlowButtonのどちらのボタンがクリックされたのかを取得し、Resultクラスに渡す。

Resultクラス

Resultクラスは、ハイアンドローの結果を表示するクラスである。結果メッセージ(correctAnswerMessage、wrongAnswerMessage)とNewGameボタン(newGameButton)を表示し、次のゲームに進むか、ゲームを辞めるかを選ばせるクラスである。
結果メッセージ(correctAnswerMessage、wrongAnswerMessage)、NewGameボタン(newGameButton)、そしてこれらを格納してレイアウトを設定するボタンベースパネル(buttonBasePanel)、更にbuttonBasePanelを格納してレイアウトを設定するリザルトパネル(resultPanel)を作成する。resultPanelは、更にHighAndLowクラスで作成したmainContentPaneに格納される。作成されたnewGameButtonにはイベントリスナが設定される。イベントリスナによって、ボタンがクリックされたことが検知されると、actionPerformedメソッドが実行され、selectHighAndLowクラスを呼び出す。

ハマったところ

  • コンテナに対して.add()したあとに.validate()を呼んでいなくてコンポーネントが再描画されなかった
  • ボタンに対してレイアウトが思ったように反映されないと思ったら、親のパネルに対してレイアウトを設定していなかった(パネルのレイアウトマネージャーはデフォルトではFlowLayoutなので)

参考
Swingを使ってみよう – Java GUIプログラミング