Java Swing ゲーム作りや学習に画面をつくる

イントロダクション

Javaを学習して、基本文法がわかると今度は、Java API、つまりはListインターフェースなど既存のクラスを使用する事を学習すると思います。
ここら辺から、学習がうまく進まなくなる。自分がそうでした。。。

今思い返すと、何かしらのアプリケーションを作ってみるのが一番良いのだけど、「何かを作るほどの理解がない」「イマイチ自身が。。。」などと感じる人のもいるかもしれません。

しかし、まずはプログラミングを楽しみましょうという気持ちでSwingしてみませんか?
ちなみに、Jazzの世界ではカッコイイ演奏をすることを「Swingしてるねぇ!」といいます。関連は全くありません(笑)

ちなに、インスタンス=コンポーネントです。つまりラベルが表示されているというのはラベルがインスタンス化されているということです。
目に見えないインスタンスが目に見えマス。※目に見えないものもありますので注意が必要です。

Java Swing

Oracleのページでは、下のように書いていました。
Swingはグラフィカル・ユーザー・インタフェース(GUI)を構築し、豊富なグラフィック機能および双方向性をJavaアプリケーションに追加するコンポーネントのセットを実装しています。

Swingコンポーネントは、全体がJavaプログラミング言語により実装されています。プラガブルなルック・アンド・フィールにより、プラットフォーム間で同じ外観になるGUI、または現在のOSプラットフォーム(Microsoft Windows、Solaris(tm)、Linuxなど)のルック・アンド・フィールを想定したGUIが作成できます。

そして、チュートリアルもあるようです。

まずは、ここのチュートリアルを行い自分で学習していく土台を作っていくのも一つだと思います。

Swingの仕組み

Swingフレームワークでは、コンテナー(Container)にコンポーネント(Component)をセットして、それを表示するという仕組みを持っています。
単純に、画面上に「Hello World」と表示するプログラムを書くと下のようになります。

<プログラムコード1>

public class SwingMain {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.getContentPane().add(new JLabel("Hello World."));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
}

処理内容

JFrame frame = new JFrame();の部分は、トップレベルコンテナーのJFrameクラスをインスタンス化しています。
そして、トップレベルコンテナーは以下の3つがあります。

JFrameについて

このうちJFrameクラスを使用するサンプルコードが良く見受けられます。上記のサンプルもこのJFrameを使用しています。
ちょっと長いですが、オラクルのドキュメントの説明が良いと思います。


Frame は、タイトルと境界線を持つトップレベル ウィンドウです。フレームのサイズには、ボーダー用に指定されたすべての領域が含まれます。この方法を使用して境界領域の寸法を得ることができるgetInsets。境界領域はフレーム全体のサイズに含まれているため、境界はフレームの一部を効果的に覆い隠し、サブコンポーネントのレンダリングおよび/または表示に使用できる領域を、左上隅の位置が 、 、および の長方形に制限し(insets.leftますinsets.top)。width - (insets.left + insets.right)のサイズは ですheight - (insets.top + insets.bottom)。
クラスのインスタンスとして実装されるフレームは JFrame、境界線やタイトルなどの装飾を持ち、ウィンドウを閉じるかアイコン化するボタン コンポーネントをサポートするウィンドウです。通常、GUI を使用するアプリケーションには、少なくとも 1 つのフレームが含まれます。アプレットもフレームを使用することがあります。
別のウィンドウに依存するウィンドウを作成するには (たとえば、別のウィンドウがアイコン化されると非表示になります)、dialogの代わりにa を使用しますframe.。別のウィンドウ内に表示されるウィンドウを作成するには、内部フレームを使用します。

まぁなんとなくの理解でもよいと思います。

コンポーネントの追加

frame.getContentPane().add(new JLabel("Hello World."));はラベル、テキストフィールド、ボタンなどのコンポーネントを追加する
時のコードです。つまり、このコードの引数にラベル以外のものもセットできるというわけです。

ちなみに、色付きのコードと下のコードは同じことを行っています。

Container cont = frame.getContentPane();
cont.add(new Label("Hello World."));

一行で書くか2行で書くかの違いです。細かいところでは、Containerのインスタンスを変数「cont」にセットしています。
複数回同じ処理をするのであれば、変数に取り出したインスタンスを使用するのがエコなコードになります。

処理の内容としては、以下の通りです。

  1. JFrameからコンテナーを取得する
  2. 取得したコンテナーにコンポーネント(ラベル)を追加する

閉じるときの処理設定

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);画面を閉じるときの処理です。
のコードを書くことで画面を閉じるとアプリケーションが終わります。

単純に追加

単純にコンポーネントを追加すると下のようになります。
<プログラムコード2>

public class SwingMain {
    public static void main(String[] args) {
        JFrame frame = new JFrame();

        Container cont = frame.getContentPane();
        cont.add(new JLabel("Hello World."));
        cont.add(new JLabel("*************"));
        cont.add(new JLabel("**        **"));
        cont.add(new JLabel("**        **"));
        cont.add(new JLabel("**        **"));
        cont.add(new JLabel("*************"));

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
}

<実行結果>

一番最後に追加したコンポーネントのみが表示されています。おそらく、コンテナーに一つのコンポーネントしか登録できないのでしょう。
なので、JPanelクラスを使用してコンポーネントを複数追加します。

JPanelを使う

上のコードを下の様に書き換えます。
<プログラムコード3>

public class SwingMain {
    public static void main(String[] args) {
        JFrame frame = new JFrame();

        Container cont = frame.getContentPane();
        JPanel panel = new JPanel();
        cont.add(panel);

        panel.add(new JLabel("Hello World."));
        panel.add(new JLabel("*************"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("*************"));

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
}

<実行結果>

コードのように四角っぽいのを表示したかったのですが、横並びですね。。。

これは、デフォルトで設定されているレイアウトマネージャ(XXXLayoutクラス)がFlowLayoutになっているので、横並びになります。

BorderLayoutについて

デフォルトで設定されているレイアウトクラスBorderLayoutクラスです。下の図のようにレイアウトを組むことができます。
<レイアウト図1>

上のコードでは、レイアウトの指定(NORTH, CENTERなど)を使用していないので、余計変な形になっています。

レイアウトを使う

<プログラムコード4>

public class SwingMain {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Window Test");
        frame.setLayout(new BorderLayout());

        Container cont = frame.getContentPane();
        cont.add(new JLabel("Hello World."), BorderLayout.NORTH);

        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        panel.add(new JLabel("*************"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("*************"));
        cont.add(panel, BorderLayout.CENTER);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(new Dimension(500, 200));
        frame.setVisible(true);
    }
}

BoxLayoutというクラスも出てきましたが、このクラスに関しては、さておきにして下のように表示されました。
<実行結果>

ポイントは、次のような部分です。

  1. JFrameのコンストラクターの引数に「Window Test」と追加したのでウィンドウのタイトル部分に表示されている。
  2. JPanelにBoxレイアウトを設定して、縦に追加する形を作ったので壮丁通りの表示になった。
  3. setSize()メソッドを使用して、JFrameのサイズを指定している。

レイアウトを色々試す

上記の表示コンポーネントに、いろいろと装飾を加えてみることにします。

コンポーネントにボーダーを入れてみる。

コンポーネント(表示している部品)に、ボーダーを入れるには次のメソッドを使用します。
JComponent#setBorder()を使用してボーダーを追加します。しかし表示しているJPanelのメソッドではありません。
それでも問題はありません。JavaDocを見ればわかるように、JPanelはJComponentのサブクラス(子供)になっています。
つまり、JComponentのメソッドを使用することができます。※privateのメソッドは使えません。

パネルにボーダーを入れる

プログラムコード4を修正して次のプログラムコード5を作成します。

<プログラムコード5>

public class SwingMain {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Window Test");
        frame.setLayout(new BorderLayout());

        Container cont = frame.getContentPane();
        cont.add(new JLabel("Hello World."), BorderLayout.NORTH);

        JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createLineBorder(Color.black));
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        panel.add(new JLabel("*************"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("*************"));
        cont.add(panel, BorderLayout.CENTER);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(new Dimension(500, 200));
        frame.setVisible(true);
    }
}

実行結果は下の通りです。

ボーダーが真ん中らへんに1本入っただけです。。。これは、レイアウト図1のNORTHとCENTERにそれぞれ、ラベルとパネルを設定しているためNORTHとCENTERの境目にボーダーが票う辞された程度になっています。もっとわかりやすくほかのものも追加してみます。

<プログラムコード6>

public class SwingMain {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Window Test");
        frame.setLayout(new BorderLayout());

        Container cont = frame.getContentPane();
        cont.add(new JLabel("Hello World."), BorderLayout.NORTH);

        JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createLineBorder(Color.black));
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        panel.add(new JLabel("*************"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("*************"));
        cont.add(panel, BorderLayout.CENTER);

        JLabel west = new JLabel("東");
        west.setBorder(BorderFactory.createLineBorder(Color.BLUE));
        cont.add(west, BorderLayout.WEST);

        JLabel east = new JLabel("西");
        east.setBorder(BorderFactory.createLineBorder(Color.RED));
        cont.add(east, BorderLayout.EAST);

        JLabel south = new JLabel("南");
        south.setBorder(BorderFactory.createLineBorder(Color.YELLOW));
        cont.add(south, BorderLayout.SOUTH);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(new Dimension(500, 200));
        frame.setVisible(true);
    }
}

上記のように、東西南北と中心の5つの領域にそれぞれのコンポーネントをセットしてやる形の表示ができました。

簡単なリファクタリング

今度はBoxLayoutを使用してみます。ここで、プログラムのコード量が増えてきたので、コードを分割してみようと思います。
コードを分割する手段として使えるのは「メソッドを使う」というところです。
具体的には次のようにやります。

1.メソッドの処理を言葉で表現する。

筆者の表現したものなので、「これが正解!」ってことではないので注意してください。それぞれの人にそれぞれの言葉があるように表現の仕方は無限大にあります

  1. JFrameをインスタンス化
  2. BorderLayoutをセット
  3. 北(NORTH)にJLabel「Hello World」をセット
  4. 中央にJPanelをセット
  5. 東。。。
  6. 西。。。
  7. 南。。。

※性格が出るんですね。。。

このように、東西南北、中央の5つに各描画処理を行っているので、そのように処理を分割しようと思います。
メソッド名は、わかりやすいとおもうので「中央を描く」「北を描く」。。。のようにつけることにします。

2.表現したように分割

東西南北、中央の5つに分割することにしたので、次のようなメソッドを作成しました。まだからの状態です。引数および返り値もありません。
そのため、ビルドエラーが出ます。説明の段階を踏むためなので、ご了承ください。
<プログラムコード7>

public class SwingMain {
    ...
    /** 北を描く */
    private void darwNorth() {
    }
    /** 中央を描く */
    private void darwNCenter() {
    }
    /** 西を描く */
    private void darwWest() {
    }
    /** 東を描く */
    private void darwEast() {
    }
    /** 南を描く */
    private void drawSouth() {
    }
}

そして、単純に既存のコードを移植します。
<プログラムコード8>

public class SwingMain {
    public static void main(String[] args) {
        SwingMain main = new SwingMain();
        JFrame frame = new JFrame("Window Test");
        frame.setLayout(new BorderLayout());

        Container cont = frame.getContentPane();
        main.darwNorth();
        main.darwNCenter();
        main.darwWest();
        main.darwEast();
        main.drawSouth();

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(new Dimension(500, 200));
        frame.setVisible(true);
    }

    /** 北を描く */
    private void darwNorth() {
        cont.add(new JLabel("Hello World."), BorderLayout.NORTH);
    }
    /** 中央を描く */
    private void darwNCenter() {
        JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createLineBorder(Color.black));
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        panel.add(new JLabel("*************"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("*************"));
        cont.add(panel, BorderLayout.CENTER);
    }
    /** 西を描く */
    private void darwWest() {
        JLabel west = new JLabel("西");
        west.setBorder(BorderFactory.createLineBorder(Color.BLUE));
        cont.add(west, BorderLayout.WEST);
    }
    /** 東を描く */
    private void darwEast() {
        JLabel east = new JLabel("東");
        east.setBorder(BorderFactory.createLineBorder(Color.RED));
        cont.add(east, BorderLayout.EAST);

    }
    /** 南を描く */
    private void drawSouth() {
        JLabel south = new JLabel("南");
        south.setBorder(BorderFactory.createLineBorder(Color.YELLOW));
        cont.add(south, BorderLayout.SOUTH);

    }
}

この状態でもエラーが出ます。それは、Containerが宣言されているのは、メインメソッドだからです。

ならば、引数にContainerをわたしてやればよいのでわ?とおもうのでそのようにします。
<プログラムコード9>

package jp.zenryoku.swing;

import javax.swing.*;
import java.awt.*;

public class SwingMain {
    public static void main(String[] args) {
        SwingMain main = new SwingMain();
        JFrame frame = new JFrame("Window Test");
        frame.setLayout(new BorderLayout());

        Container cont = frame.getContentPane();
        main.darwNorth(cont);
        main.darwNCenter(cont);
        main.darwWest(cont);
        main.darwEast(cont);
        main.drawSouth(cont);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(new Dimension(500, 200));
        frame.setVisible(true);
    }

    /** 北を描く */
    private void darwNorth(Container cont) {
        cont.add(new JLabel("Hello World."), BorderLayout.NORTH);
    }
    /** 中央を描く */
    private void darwNCenter(Container cont) {
        JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createLineBorder(Color.black));
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        panel.add(new JLabel("*************"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("**        **"));
        panel.add(new JLabel("*************"));
        cont.add(panel, BorderLayout.CENTER);
    }
    /** 西を描く */
    private void darwWest(Container cont) {
        JLabel west = new JLabel("西");
        west.setBorder(BorderFactory.createLineBorder(Color.BLUE));
        cont.add(west, BorderLayout.WEST);
    }
    /** 東を描く */
    private void darwEast(Container cont) {
        JLabel east = new JLabel("東");
        east.setBorder(BorderFactory.createLineBorder(Color.RED));
        cont.add(east, BorderLayout.EAST);

    }
    /** 南を描く */
    private void drawSouth(Container cont) {
        JLabel south = new JLabel("南");
        south.setBorder(BorderFactory.createLineBorder(Color.YELLOW));
        cont.add(south, BorderLayout.SOUTH);

    }
}

実行結果は、下のようになります。

はじめに表示したものと同じです。そうならなくてはなりません。リファクタリングした後は、処理結果が全く同じになる事が大切です。
なので、単体テストクラスがないとリファクタリングするのがとても大変になリます。
逆に単体テストクラスがない場合はリファクタリングができません。

HTMLを読み込む

単純にHTMLファイルを作成して、それをSwingで表示するということができます。

<プログラムコード10>

    public static void main(String[] args) {
        SwingHtml main = new SwingHtml();
//        JEditorPane editorPane = new JEditorPane("text/html", HTML);
        JEditorPane editorPane = null;
        try  {
            URL url = new File("PracticeJava1/resources/012.html").toPath().toUri().toURL();
            editorPane = new JEditorPane(url);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
        editorPane.setEditable(false);
        editorPane.setPreferredSize(new Dimension(200, 150));
        JScrollPane scrollPane = new JScrollPane(editorPane);

        JPanel panel = new JPanel();

        panel.setLayout(new BorderLayout());
        panel.add(scrollPane, BorderLayout.CENTER);
        main.getContentPane().add(panel);
        main.setSize(new Dimension(400, 300));
        main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        main.setVisible(true);
    }

投稿者:

takunoji

音響、イベント会場設営業界からIT業界へ転身。現在はJava屋としてサラリーマンをやっている。自称ガテン系プログラマー(笑) Javaプログラミングを布教したい、ラスパイとJavaの相性が良いことに気が付く。 Spring framework, Struts, Seaser, Hibernate, Playframework, JavaEE6, JavaEE7などの現場経験あり。 SQL, VBA, PL/SQL, コマンドプロント, Shellなどもやります。

コメントを残す