このページは、学部2年生向け授業である、「マルチメディアプログラミング実習」 のために用意しました。
(Wikiの仕様で大文字小文字が混在した英単語に疑問符?が追加されるところがありますが、無視してください。)
SwingはJavaのパッケージで、GUIを構築するための数多くのclassが含まれています。例えば、ウィンドウを作成するためのクラス、ボタンを作成するためのクラス、メニューを作成するためのクラスなどがあります。
初期のJavaでは、AWT (Abstract Window Toolkit) というパッケージが使われていました(今でも使えます)。AWTを使っても、ウィンドウ、ボタン、メニューなどを全部作ることができます。でも、Swingに移行しています。AWTとSwingでは作れるGUIの見た目が違います。AWTは、Java独特のGUIになります。それに対して、Swingでは、稼働しているOSのGUIに近い形のアプリケーションが作れます。例えば、AWTでメニューバーを作ると、Windows風に、ウィンドウ上部にメニューバーが現れます。他のmacOSのアプリと一緒に使うと、違和感があります。それに対して、Swingでは、画面上部にメニューバーが現れます。
まずはJFrameというSwingのクラスを使います。Frameは枠のことで、ウィンドウです。 JはJavaのJです。
以下のリンクからJFrameクラスを選択して、何ができるかざっと見ておきましょう。
https://docs.oracle.com/javase/jp/8/docs/api/index.html
授業の最初で作ったウィンドウを出すプログラムです。
import javax.swing.JFrame; public class SimpleWindow { public static void main(String argv[]) { JFrame f = new JFrame("私が作った最初の窓"); f.setSize(200,100); f.setVisible(true); } }
このままだと、ウィンドウを閉じてもプログラムは終了しません。 ウィンドウを閉じた時に、プログラムを終了するかしないかは、アプリの設計でどちらでも可能です。がシンプルなプログラムでは、ウィンドウを閉じた時に終了する方式が多いようです。 ウィンドウを閉じるとプログラムが終了するようにしてみましょう。以下のように1行追加します。
import javax.swing.JFrame; public class SimpleWindow { public static void main(String argv[]) { JFrame f = new JFrame("私が作った最初の窓"); f.setSize(200,100); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); } }
このプログラムをよくみていきましょう。 mainの最初の行で、JFrameのコンストラクタを呼び出して、インスタンスを作っています。 引数付きのコンストラクタを使っています。Stringの引数を使うと、その内容がウィンドウのタイトル名になります。 作ったインスタンスはJFrameを参照する変数 f に代入されています。
mainの2行目では、JFrameのインスタンスメソッドであるsetSize()を呼んでいます。文字通り、ウィンドウのサイズを設定するメソッドです。
mainの最後の行では、setVisible()メソッドを呼んでいます。これは、ウィンドウを目に見えるように表示する機能です。作っただけでは表示されないです。
アプリケーションで使うウィンドウを作る時の流儀の一つに、素のウィンドウクラスを継承して、使いたいウィンドウをサブクラスで作る方法があります。Javaではそのような方法が、一般的です。ということで、JFrameを継承して、ウィンドウを作ってみます。上のプログラムと同じことを行いますが、やり方が違っています。以下です。
import javax.swing.JFrame; public class SimpleWindow extends JFrame { public void initialize () { this.setTitle("私が作った最初の窓"); this.setSize(200,100); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public static void main (String argv[]) { SimpleWindow sw = new SimpleWindow(); sw.initialize(); } }
このプログラムでは、JFrameを継承して、サブクラスとしてSimpleWindowを定義しています。 なので、SimpleWindowのインスタンスは、JFrameの機能を全て使えます。 別のプログラムからSimpleWindowのインスタンスを作って表示しても良いのですが、面倒なので、自信を動かすプログラムをmainで書きました。今まで、クラスを作ってそれを動かすプログラムをmainに書いていたのと同じです。
mainでは、自分自身のインスタンスを作って、それへの参照をswという変数に代入しています。 そしてinitialize()というインスタンスメソッドを呼んでいます。
initialize()は今の所SimpleWindowで新たに定義した唯一のメンバーです。 その名の通り、自分自身の初期化を行うつもりで命名しました。 コンストラクタとして実装しても良いのですが、結構複雑な仕事をするので、 別のメソッドにしました。ちなみに、他のオブジェクト指向言語では、 newした後に、init()とかinitialize()とかのメソッドを呼ぶ方式も多いです。(例えばSmallTalk言語)。 ここではそれを真似しました。
initialize()の中で、ウィンドウのタイトルを決めて、サイズを決めて、終了オプションを指定して、setVisibleしています。
このままではウィンドウが空っぽなので、文字を表示してみましょう。 文字を表示するクラスにJLabelがあります。その名の通りラベルです。
以下のリンクからJLabelクラスを選択して、何ができるかざっと見ておきましょう。
https://docs.oracle.com/javase/jp/8/docs/api/index.html
基本的な使い方は以下です。まずは、インスタンスを作ります。コンストラクタの引数で、表示させる文字を指定することもできます。
Label label = new Label("Hello!");
ところで、JFrameで作ったインスタンスは、Containerというクラスのインスタンスを持っています。その名前の通り、「何かを格納するもの」です。つまりウィンドウに情報を格納する機能を持ったインスタンスです。JFrameのインスタンスに、getContentPane()というメソッドを送ると、ウィンドウに表示したい情報を格納するインスタンスを返してくれます。
Container content = this.getContentPane();
で、自分自身のコンテントペーンを返してくれます。 これに対して、上記で作ったラベルインスタンスをaddすることができます。
content.add(label);
上記の、継承を使ったプログラムによるウィンドウの中に、 Hello!という文字を出してみよう。 上で紹介した3行を、initialize()メソッドに加えれば文字を出せます。
解答例:
import javax.swing.*; import java.awt.*; public class SimpleWindow extends JFrame { public void initialize () { this.setTitle("私が作った最初の窓"); JLabel label = new JLabel("Hello!"); Container content = this.getContentPane(); content.add(label); this.setSize(200,100); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String argv[]) { SimpleWindow sw = new SimpleWindow(); sw.initialize(); } }
コンテンツの大きさに合わせてウィンドウサイズを変更する機能があります。 上記のプログラムではsetSize()で変更していますが、これの代わりに、
this.pack();
というメソッドを呼び出してみましょう。以下のようになるはずです。
解答例:
import javax.swing.*; import java.awt.*; public class SimpleWindow extends JFrame { public void initialize () { this.setTitle("私が作った最初の窓"); JLabel label = new JLabel("Hello!"); Container content = this.getContentPane(); content.add(label); this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String argv[]) { SimpleWindow sw = new SimpleWindow(); sw.initialize(); } }
今度はウィンドウにボタンを表示してみましょう。 ボタンなので、多分JButtonというクラスがありそうです。 以下から探して、何ができるかざっと見ておきましょう。
https://docs.oracle.com/javase/jp/8/docs/api/index.html
上記のプログラムのJLabelのところをJButtonにすればだいたい良いようです。試してみましょう。
解答例:
import javax.swing.*; import java.awt.*; public class SimpleWindow extends JFrame { public void initialize () { this.setTitle("私が作った最初の窓"); JButton button = new JButton("Hello!"); Container content = this.getContentPane(); content.add(button); this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String argv[]) { SimpleWindow sw = new SimpleWindow(); sw.initialize(); } }
http://www.siio.jp/index.php?plugin=attach&pcmd=open&file=button.png&refer=How2JavaProgramming
上のプログラムのように、 getContentPane()で得られたContainerに直接ボタンを貼付けることができます。 しかし、貼付けられるのは一つのボタンだけのようです。
2個以上のボタンを貼り付けるためには、JPanel(パネル、板)というクラスのインスタンスを作って、 これに複数のボタンを貼り付け(addする)、 そのパネルとJFrameのcontentに貼り付けます。 こんな感じのイメージです。
2個のボタンを出してみましょう。 手順としては、
です。(順番は多少前後しても構いません)
解答例:
import javax.swing.*; import java.awt.*; public class SimpleButton extends JFrame { public void initialize () { this.setTitle("私が作った最初の窓"); JPanel panel = new JPanel(); JButton button1 = new JButton("button1"); JButton button2 = new JButton("button2"); panel.add(button1); panel.add(button2); Container container = this.getContentPane(); container.add(panel); this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String argv[]) { SimpleButton sw = new SimpleButton(); sw.initialize(); } }
これからがGUIプログラミングの重要なところです。 GUIプログラミングでは、ユーザがマウスをクリックしたり、ボタンを押したり、メニューを選んだり、などのイベントが発生すると、あらかじめ用意された、それぞれのイベントに対応するメソッド(イベントハンドラー)が呼ばれます。
現在のプログラムでは,ボタンを押しても何もおこりません. ボタンが押されたイベントに対応するイベントハンドラーが無いからです。
ボタンが押されたイベントを受け取るためには,
必要があります.このインスタンスが、イベントを受け取るインスタンスになります。
上の、ボタンが2つあるプログラムで、button1が押された時にHelloと表示するプログラムを作ってみましょう。
ボタンのイベントを受け取るインスタンスは、新しく作成しても良いですが、 ここではSimpleWindowから作ったインスタンスでイベントを受け取ることにします。
現在、SimpleWindowはJFrameを継承していますが、これに加えて、Action Listenerをインプレメントすることにします。 Action Listenerをインプレメントすることは、 Action Listenerが持っているメソッドを全て用意していますという宣言になります。 この場合、そのメソッドは、actionPerformed()というメソッド一つだけです。 なので、これを用意します。そこではHelloと表示することにします。
さらに、button1のAction Listenerとして、Simple Windowのインスタンスを登録しておきます。 これは、button1にアクションが発生したら、こちらのインスタンスを使ってくださいという意味です。
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class SimpleButton extends JFrame implements ActionListener { public void initialize () { this.setTitle("私が作った最初の窓"); JPanel panel = new JPanel(); JButton button1 = new JButton("button1"); JButton button2 = new JButton("button2"); button1.addActionListener(this); panel.add(button1); panel.add(button2); Container container = this.getContentPane(); container.add(panel); this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void actionPerformed (ActionEvent e) { System.out.println("Hello"); } public static void main(String argv[]) { SimpleButton sw = new SimpleButton(); sw.initialize(); } }
これで、button1を押した時にHelloと表示されるようになりました。 button2を押しても、何もおきません。Action Listenerが登録されていないからです。
演習:button2のAction Listenerとしても、自分自身を登録してみましょう。
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class SimpleButton extends JFrame implements ActionListener { public void initialize () { this.setTitle("私が作った最初の窓"); JPanel panel = new JPanel(); JButton button1 = new JButton("button1"); JButton button2 = new JButton("button2"); button1.addActionListener(this); button2.addActionListener(this); panel.add(button1); panel.add(button2); Container container = this.getContentPane(); container.add(panel); this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void actionPerformed (ActionEvent e) { System.out.println("Hello"); } public static void main(String argv[]) { SimpleButton sw = new SimpleButton(); sw.initialize(); } }
この結果、button1でもbutton2でも、どちらも押されればHelloと表示されるようになりました。
button1を押した時はHelloと表示され、button2を押した時はGoodbyeと表示されるように変更しましょう。
action Performedメソッドの中でボタンを区別すれば可能になります。 このメソッドで受け取る引数はActio Eventのインスタンスです。 Action Eventにはどういうインスタンス変数があるかみておきましょう。
https://docs.oracle.com/javase/jp/8/docs/api/index.html
今回は、この中のget Source()メソッドを使いましょう。 このメソッドで、イベントを発生したインスタンスがわかります。 正確には、イベントを発生したインスタンスへの参照がわかります。 なので、それがbutton1だったのか、button2だったのかをif文で判定して、 その結果、HelloかGoodbyeを表示すれば良いです。
ただし、今のプログラムでは、変数button1, button2はinitialize()メソッドの中で定義されているので、これが終了したら消えてしまいます。使い捨てになっています。action Performedメソッドの中からもbutton1, button2が見えるようにするためには、インスタンス変数として定義しておく必要があります。以下はヒントです。
ヒント(最初の6行です)
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class SimpleButton extends JFrame implements ActionListener { JButton button1, button2; public void initialize () {
演習:このヒントを元に、button1を押した時はHelloと表示され、button2を押した時はGoodbyeと表示されるように変更しましょう。
解答例:
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class SimpleButton extends JFrame implements ActionListener { JButton button1, button2; public void initialize () { this.setTitle("私が作った最初の窓"); JPanel panel = new JPanel(); button1 = new JButton("button1"); button2 = new JButton("button2"); button1.addActionListener(this); button2.addActionListener(this); panel.add(button1); panel.add(button2); Container container = this.getContentPane(); container.add(panel); this.pack(); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void actionPerformed (ActionEvent e) { if(e.getSource()==button1) System.out.println("Hello"); else System.out.println("Goodbye"); } public static void main(String argv[]) { SimpleButton sw = new SimpleButton(); sw.initialize(); } }
ここではボタンを区別するために、インスタンス(への参照)を比較しました。それ以外の方法もあります。 一つは、ボタンのテキスト(ボタンの上に表示されている文字)を入手して、それを比較しても良いです。 ボタンのテキストは、get Text()メソッドで入手できます。 以下のように変更すると、ボタンのテキストが表示されるようになります。 ボタンに、HelloとGoodbyeを表示しておけば、それらが表示されます。 ボタンのテキストを文字列比較して、処理を変えることも可能です。
public void actionPerformed(ActionEvent e){ System.out.println(((JButton)e.getSource()).getText()); }
もう一つは、ボタンにコマンドを書く方法です。 ボタンにsetActionCommand(String)を定義しておくと、getActionCommand()で知ることができます。 例えば、
button1.setActionCommand("hello");
としておけば、actionPerformedの中で
e.getActionCommand();
で文字列を得られます。なので、例えば、
public void actionPerformed(ActionEvent e){ System.out.println(e.getActionCommand()); }
でコマンド部分を印刷できます。コマンドを文字列比較して、処理を変えることも可能です。
以下のプログラムで5個のボタンをレイアウトできます。 パネルに、Border Layourのインスタンスを設定しています。 また、パネルにaddする時に、Border Layoutクラスのクラス変数を、引数に追加しています。 これで東西南北中央に配置されます。
import java.awt.*; import javax.swing.*; import java.awt.event.*; public class BorderLayoutSample extends JFrame { public void initialize() { this.setTitle("Simple Window"); JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add(new JButton("WEST"),BorderLayout.WEST); panel.add(new JButton("CENTER"),BorderLayout.CENTER); panel.add(new JButton("EAST"),BorderLayout.EAST); panel.add(new JButton("NORTH"),BorderLayout.NORTH); panel.add(new JButton("SOUTH"),BorderLayout.SOUTH); Container container = this.getContentPane(); container.add(panel); this.pack(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } public void actionPerformed(ActionEvent e) { System.out.println(e.getActionCommand()); } public static void main (String args[]) { BorderLayoutSample f = new BorderLayoutSample(); f.initialize(); } }
演習:このプログラムがボタンのイベントを処理するように変更して、ボタンがクリックされるとそのボタン名を表示するよう改造してください。
解答例:
イベントハンドラーでボタンの名前を表示しています。
import java.awt.*; import javax.swing.*; import java.awt.event.*; public class BorderLayoutSample extends JFrame implements ActionListener { public void initialize() { JButton[] button; this.setTitle("Simple Window"); JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); button = new JButton[5]; panel.add(button[0]=new JButton("WEST"),BorderLayout.WEST); panel.add(button[1]=new JButton("CENTER"),BorderLayout.CENTER); panel.add(button[2]=new JButton("EAST"),BorderLayout.EAST); panel.add(button[3]=new JButton("NORTH"),BorderLayout.NORTH); panel.add(button[4]=new JButton("SOUTH"),BorderLayout.SOUTH); Container container = this.getContentPane(); container.add(panel); this.pack(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } public void actionPerformed(ActionEvent e) { System.out.println(e.getText()); } public static void main (String args[]) { BorderLayoutSample f = new BorderLayoutSample(); f.initialize(); } }
解答例2:
イベントハンドラーでボタンに割り当てられたコマンドを表示しています。 上記の例よりは複雑ですが、コマンドを使うメリットはあります。 この例ではメリットを活かせていませんが、 コマンドを割り当てると、ボタンの名前を変更しても影響が出ません。また、ボタンにはわかりやすい名前をつけて、コマンドにはプログラムて扱いやすい名前をつけることも可能です。
import java.awt.*; import javax.swing.*; import java.awt.event.*; public class BorderLayoutSample extends JFrame implements ActionListener { public void initialize() { JButton[] button; this.setTitle("Simple Window"); JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); button = new JButton[5]; panel.add(button[0]=new JButton("WEST"),BorderLayout.WEST); panel.add(button[1]=new JButton("CENTER"),BorderLayout.CENTER); panel.add(button[2]=new JButton("EAST"),BorderLayout.EAST); panel.add(button[3]=new JButton("NORTH"),BorderLayout.NORTH); panel.add(button[4]=new JButton("SOUTH"),BorderLayout.SOUTH); for(int i=0; i < button.length; i++) { button[i].addActionListener(this); button[i].setActionCommand(button[i].getText()); } Container container = this.getContentPane(); container.add(panel); this.pack(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } public void actionPerformed(ActionEvent e) { System.out.println(e.getActionCommand()); } public static void main (String args[]) { BorderLayoutSample f = new BorderLayoutSample(); f.initialize(); } }
演習:このプログラムを参考にして、 上で作った二つのボタンのプログラム、Simple Buttonに対して、レイアウトマネージャのFlowLayoutを使って、左寄せ、センタリング、右寄せを試してください。そして、ウィンドウのサイズを変更した時の、ボタンの移動を観察してください。
演習のヒント
bt1 = new JButton("button1"); bt2 = new JButton("button2"); panel.setLayout(new FlowLayout(FlowLayout.RIGHT));
FlowLayoutにはCENTER, LEFT, RIGHTなどの揃え方の指定がありますが、これをコンストラクタの引数で指定できるようです。addのところではレイアウト指定しないようです。以下を試してください。
panel.setLayout(new FlowLayout(FlowLayout.CENTER));
panel.setLayout(new FlowLayout(FlowLayout.LEFT));