最終更新日 |
[関連]
Java入門1 Java入門2 Java入門3ーアプレット Java応用2ーアプレットとSwingその2
Javaのプログラミング2ーApplet,Swing(学生向けにまとめたページ)。
[このページの内容]
1 Swingの基本ーイベント処理 2 ボタンによる背景色の切り換え
3 Midiファイルの選択と演奏ーコンボボックス 4 御神籤ーイメージ処理
5 JARファイルの作成方法
これまで、Java入門1,2は、アリケーション・プログラムとして、基本データ変数や配列,forによる繰り返し、if文による条件分岐といった基礎的な例題を中心にして、IntegerやStringクラスのメソッドも一部紹介してきた。
入門3はアプレット 、ホームページ上でグラフィックスなどを動かすことのできる、Javaのプログラムやスレッドによる動きのある例を紹介した。
この応用1は、Swing によるGUI(Graphic User Interface)とアプレットで、ネット上で簡単ながら、対話できる形を紹介する。ボタンやメニューのある、一般のWindowsプログラムはすべてこのような形である。
初期のJavaのシステムでは、GUIはAWT によっていたが、機能の高いSwingが導入された。コーディグが複雑になるが、そちらを紹介する。ただSwingを使うと、AppletもJApplet にする必要があり、こちらもやや複雑になる。
「Java入門1」の1-4-1で紹介した JavaTM 2 Platform Standard Edition 5.0API 仕様 は、引き続き必要。
[2006/12/26、2007/1/10,1/22 追加・修正] 2-1 JPanelを拡張する、前文追加、コメントを詳細に。 2-1-2 thisキーワードの考察を追加(addActionListener(this)について) 2-1-3 内部クラスにする理由 追加 2-2 JPanelを拡張しない例、パネルも使わない例を追加 2-3 失敗事例だが、その理由を考察。 2-4 文字変換、コメント5を追記。 2-5 contentPaneについて を追加。
[ブラウザソフトのJava対応]
アプレットを見るには、ブラウザソフトがJavaに対応していないといけない(JavaのVM(仮想マシン)をサポート)。マイクロソフトのIEは、Java1.1という古いシステムをサポートしていたが、XPのSP1以降、サポートをうちきった。その後は、各メーカが独自に対応しているが、統一はされていない。もし、このページのアプレット画面が見られない場合は、ブラウザソフトのJava対応 を参照されたい。
サンマイクロのTutorial Example Three: CelsiusConverter にあるアプリケーション、main で動くプログラムをJApplet向けに、そしてずっと簡単に書き直したものである。 [プログラム] import java.applet.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; /* <applet code="Fahrenheit.class" width=250 height=80> </applet> */ public class Fahrenheit extends JApplet implements ActionListener { JButton henkan; JLabel label1; JTextField tx; public void init(){ Container contentPane = getContentPane(); contentPane.setLayout(new GridLayout(2,2)); tx = new JTextField(10); tx.setEditable(true); tx.addActionListener(this); contentPane.add(tx); henkan = new JButton("摂氏華氏変換"); henkan.addActionListener(this); contentPane.add(henkan); label1 = new JLabel("摂氏? "); contentPane.add(label1); } public void actionPerformed(ActionEvent event) { // String source = event.getActionCommand(); String s = tx.getText(); double n=Double.parseDouble(s); label1.setText("華氏: " + (n * 1.8 + 32)); } } [実行結果] 摂氏華氏変換 ソースプログラム Fahrenheit.java [コメント] 1 GUI、いわゆるイベントドリヴンの仕組みが必要になる。ユーザがたとえば「変換ボタン」を押すと、 イベントが発生するが、それを受け取り、処理するイベントハンドラを記述する。 2 ActionListener というインターフェイスを実装し、addActionListener(this)メソッドや actionPerformedメソッドが実装されているのがその現れである。 3 この例題では、摂氏の値を入力するテキストフィールド、変換ボタン、華氏に変換した結果を表示 するラベル と3種類のコンボーネントを使う。 4 テキストフィールドに文字を入力しエンターキーを押したときやボタンを押したときに、発生する イベントを受け取るActionListenerを現在のクラスに加える。 (addActionListenerは、JTextFieldやJButtonクラスのインスタンス・メソッド)。 5 変換ボタンも同様であり、実際には、このボタンが押されたとき、そのイベントがActionListenerを 通して、そのメソッドであるactionPerformed に受け渡される。 6 テキストフィールドの入力文字が、getText()でとり出され、double型に変換しか値nをもとに、華氏に 変換されて、labelに表示される。 7 その他、書くべきことは多々あるが、次の例題も類似のものなので、そちらに回す。
「Java入門2」5-2 コマンドライン引数の2進表現でやった例をSwingを使って、簡単ながら、対話で きる形にしてみる。プログラムの骨格は1-1に同じなので、変更点のみを記す。 [プログラム] // import文 1-1に同じ、省略 /* <applet code="HexaApplet.class" width=200 height=80> </applet> */ public class HexaApplet extends JApplet implements ActionListener { JLabel label2; //追加 String htmlText1; //追加、後は1-1に同じ、省略 public void init(){ //下記の追加、変更以外は全て1-1に同じ、省略 htmlText1="<html><body style=\"background:yellow;font-size:1.1em;\">"; //追加、 henkan = new JButton("2進・16進数変換"); //ボタン上の表記変更 label1 = new JLabel(htmlText1 + "2進数? "); //htmlタグを使って整形 contentPane.add(label1); label2 = new JLabel(htmlText1 + "16進数? "); contentPane.add(label2); } public void actionPerformed(ActionEvent event) { String s = tx.getText(); int n=Integer.valueOf(s).intValue(); label1.setText(htmlText1 + "2進数: " + Integer.toBinaryString(n)+ "</body></html>"); label2.setText(htmlText1 + "16進数: " +Integer.toHexString(n).toUpperCase() + "</body></html>"); } } [実行結果] 2進・16進数変換 ソースプログラム HexaApplet.java [コメント] 1 「Java入門2」5-2では、 System.out.println("Binary: " + Integer.toBinaryString(n)); System.out.println("Hexa: " + Integer.toHexString(n).toUpperCase()); が中心であったが、わずかに、label1.setTextなどにその片鱗がみえる程度で、非常に複雑に なっている。 2 Swingを使う場合はAppletを拡張したJAppletを使う。JAppletは、javax.swingパッケージにある。 その場合は、ContentPaneというコンテナ上 にButtonなどのコンポーネントをおく。 Container contentPane = getContentPane(); と、javax.swingバッケージのJAppletクラスのメソ ッドを使って、 Containerクラスのインスタンス、contentPaneを生成した後、contentPane.add(tx) などとする。なお、contentPaneの概念は分かりにくいので、2-5で考察する。 3 contentPane.setLayout(new GridLayout(2,2)) は、コンポーネントのレイアウトを決めるもので、 同じ大きさのコンポーネントを2行2列に配置する。GridLayoutは、java.awtバッケージにある。 4 JAppletの特徴のひとつは、htmlタグとstyle sheet により、テキストフィールド やラベルの表示を飾ることができることである。htmlText1はその例で、label1.setTextの中で、 文字列結合の形で使い、背景色を黄色、文字をやや大きくしている。
JPanelを拡張したクラスのインスタンスpanelに、ボタンを3つはりつけ、panelの背景色を変える。「コアJava2 1巻 基礎編」改定版(ASCII 2001/12) P375のアプリケーション版のプログラムをアプレット版に直すとともに、内部クラスを使わなくてもいい形にやや変更した。
といっても、基本を変えるほどの力はない。ただ、やっていることは単純でも、イベント処理そのもの、JPanelの使用、ボタンのレイアウトなど、難解なところが多いため、いささか、重箱の底をつくようなトライとなったが、さまざまに試行した。
2-1で使ったthisキーワードの検証を2-2で行った。「コアJava2 基礎編」と類似することになったが、テキストそのもののトライし、なぜ、内部クラスを使う必要があるのかも検証した。
さらに、内部クラスなど話をややこしくするJPanelの拡張を使わずにできないかと、トライしたのが、2-3である。パネルを使わない場合のボタンのレイアウトの難しさから来る失敗ををへて、ようやくできたもの。失敗例も2-4に記す。
以上のトライの相互関係をはっきりさせることにも力点をおいて、それぞれにコメントをつけた。
「コアJava2 1巻」P375のアプリケーション版のプログラムをアプレット版に直し、かつ、 内部クラスを使わない形に変更したもの(コメント9項参照)。[プログラム] import java.applet.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; /* <applet code="ColorPane.class" width=300 height=200> </applet> */ public class ColorPane extends JApplet { public void init(){ Container contentPane = getContentPane(); ColorPanel panel = new ColorPanel(); contentPane.add(panel); } } class ColorPanel extends JPanel implements ActionListener { JButton yellowB; JButton blueB; JButton redB; public ColorPanel() { yellowB = new JButton("Yellow"); blueB = new JButton("Blue"); redB = new JButton("Red"); add(yellowB); add(blueB); add(redB); yellowB.addActionListener(this); blueB.addActionListener(this); redB.addActionListener(this); } public void actionPerformed(ActionEvent event) { Color c; Object source = event.getSource(); if (source == yellowB) { c=Color.yellow; } else if (source == blueB) { c=Color.blue; } else { c=Color.red; } setBackground(c); } }[実行結果] 背景色の切り換え ソースプログラム ColorPane.java [コメント] 1 1章の2つの例題では、1-2 に書いたContentPaneというコンテナ上に、直接、テキストフィールド、 変換ボタンなどのコンボーネントをおいていた。 2 ここではContentPane上に、パネルをおいている(contentPane.add(panel))。 そのpanel とは、JPanelクラスを拡張したColorPanelのインスタンス。 ColorPanelクラスには、ボタンとそれを押したときに発生するイベントの送り先まで記述した 長文のコンストラクタを用意した。 3 2に述べたように、そこに色を変換するボタンを貼り付けるが、単に、add(yellowB)でいい。 addがJPanel上位のContainerクラスのメソッドだから。 4 ボタンが押されたときにイベントを発生させるとともに、それを受けとって背景色を変える操作も ColorPanelに記述(2項及び後述の7項参照)。 なお、ColorPanelと、別クラスにするのは、一つのクラスには、JAppletとJPanelと、2つのクラスの 拡張ができないため。 5 発生したイベントを受けとるjava.awt.eventパッケージのインターフェイス ActionListener も ColorPanelクラスに実装(implements)。 class ColorPanel extends JPanel implements ActionListener イベントをActionListenerに送信するために、JButtonクラスのインスタンス、blueBなどには、 2項で述べたコンストラクタの中で、blueB.addActionListener(this)としている。 (thisキーワードについては、2-2参照)。 6 インターフェイスは、そこに登録されているメソッドを全て実装する約束になっている。 ActionListenerの場合は、actionPerformed が唯一のメソッド。 7 ボタンを押すことで発生するイベントは、引数でactionPerformed に受け渡されて処理 される。この例題では、たとえば、青いボタンを押すとpanelの背景色を青に変える。 8 setBackground(c) は、JComponentクラスのメソッドで、JPanelクラスが継承しているので、限定子 なしで使える。(3項及びJava入門3 の2-3 コメント参照)。 後の2-2-2とあわせて考えると、このプログラムでは、パネルにボタンを配置し、パネルの背景色 を変えていることになろう。 9 なお、「core Java2 1巻」 P374には「ColorPanelオブジェクトからはpanelという変数にアクセス できないが、2つの方法で解決できる。 ひとつは、パネルをColorPanelオブジェクトに格納し、ColorPanelのコンストラクタで設定する方法。 より簡単なのが、ColorPanelをColorPaneの内部クラスにする方法。 これで、外側のパネルにアクセ スできるようになる」とある。 作成しているときは、特に意識せず、内部クラスの持つ意義もよく分からなかったが、結果的には、 前者を使ったことになる。 ColorPanel panel = new ColorPanel() で使っているコンストラクタに、もろもろ詰め込んでい るのが「パネルをColorPanelオブジェクトに格納」したことになろう。 次の「thisキーワード考察」では、内部クラスでトライしたことになる。それも結果的であるが。 [蛇足] 1 JAppletに直接、ボタンなどをおかず、パネルを使うのは、java.awtのCanvas(「独習Java」のCanvas への描画の例(P450))に近いように思うが、機能はずっと豊富か?。 2-5のように、複数のパネルを使い、レイアウトをコントロールすることもできる。 2 Object source = event.getSource(); の方が下記よりいいとある。 String source = event.getActionCommand(); (この場合は、if (source == "Yellow")とボタン上の表記を使う)
上記、プログラムの中に、yellowB.addActionListener(this) などと、addActionListenerの引数に 使われているthisキーワードについて考察する。 Java入門3 の3-3 の考察(Threadコンストラクタ の引数)で経験したことをいかした。 [プログラム] public class ColorPane2 extends JApplet { public void init(){ Container contentPane = getContentPane(); ColorPanel2 panel = new ColorPanel2(); //ColorPanel2()と変更 contentPane.add(panel); } } class ColorPanel2 extends JPanel { // JButton yellowB など3行同じ public ColorPanel() { //implements ActionListener 削除して、下記へ // yellowB = new JButton("Yellow") など3行同じ // add(yellowB) など3行同じ yellowB.addActionListener(new Response()); //変更、thisの代わり、 blueB.addActionListener(new Response()); redB.addActionListener(new Response()); } class Response implements ActionListener { //追加の内部クラス public void actionPerformed(ActionEvent event) { // すべて同じ } } } [実行結果] 背景色の切り換え2 ソースプログラム ColorPane2.java [コメント] 1 yellowB.addActionListener(this)などにあるaddActionListenerは、JButtonクラスが継承している AbstractButtonクラスのメソッド。 java.lang.Object | +--java.awt.Component | +--java.awt.Container | +--javax.swing.JComponent | +--javax.swing.AbstractButton | +--javax.swing.JButton 2 addActionListeneをAPIでみると、 「public void addActionListener(ActionListener l) ボタンに ActionListener を追加します。」 とある。 3 そこで、インターフェイスaddActionListenerをimplementsした内部クラスを新たにつくり、その インスタンスを引数にしてトライした次第。 4 2-1-1では、JPanelを拡張したColorPanelに、インターフェイス ActionListenerも含めたので、 ColorPanelのコンストラクタの中で、this キーワードが使えたということだろう。 5 同じクラス ColorPanelの中で、actionPerformedメソッドを記述している。ActionListenerが必要な のは、そちらなので、2-1-2の方が、プログラムの流れとしては自然なのかもしれない。 6 なお、Responseクラスを内部クラスにする理由は、そこでも使われるyellowBなどの変数が別 クラスで定義されているためでもあるが、それだけではないことが、次のトライで分かった。
ここまでやってきて、あらためて「core Java2 1巻」 P375のプログラムを眺めてみた。 クラス名は異なるが、 yellowB.addActionListener(new Response()) の行は、 yellowB.addActionListener(new Response4(Color.yellow))と、引数つきのコンストラクタを使っ ている。コンストラクタ側では、これらの引数を Color c で受ける。 すると、setBackground(c)は、同じクラス内にあるので、内部クラスにする必要がないはずとトライ。 [プログラム] public class ColorPane4 extends JApplet { //ほぼ同じ } } class ColorPanel4 extends JPanel { public ColorPanel4() { JButton yellowB; // 追加 JButton blueB; // 追加 JButton redB; // 追加 //以下 6行同じ yellowB.addActionListener(new Response4(Color.yellow)); //引数つきに変更 blueB.addActionListener(new Response4(Color.blue)); //引数つきに変更 redB.addActionListener(new Response4(Color.red)); //引数つきに変更 } class Response4 implements ActionListener { //ColorPanel4の内部クラス private Color c; // 追加 public Response4(Color c) { //引数つきのコンストラクタに変更 this.c=c; } public void actionPerformed(ActionEvent event) { setBackground(c); //簡潔になる } } } [ソースプログラム] ColorPane4.java [コメント] 1 Response4 を内部クラスとしない場合、actionPerformedメソッドに 1 Component p=new Component() を追加して、 p.setBackground(c); とするが、 「java.awt.Component は abstract、インスタンスを生成できない」とのエラー 2 単に、panel.setBackground(c); とすると、 予想通り、「シンボル: 変数 panelを見つけらない」とのエラー 2 setBackgroundメソッドは、1-1のコメント8に記したように、JPanel(またはJApplet)を継承した クラスなら、無条件に使えるものである。 3 そのため、Response4クラスもJPanelを継承したものでなければならず、ColorPanel4 の内部クラス とする必要がある。単に、引数だけの問題ではないと認識した。
JPanelの拡張といった本格的なものではなく、いままでやってきた単純な方法でやろうとして、 さんざん苦労する。いろいろ試行錯誤したあげく、2-3に記す3つの失敗例をへて、最終的には、 「core Java2 1巻」P449 を参考にしてようやくできたもの。 [プログラム] //import 2-1と同じ public class ColorTest4 extends JApplet implements ActionListener { JPanel panel; public void init(){ Container contentPane = getContentPane(); panel = new JPanel(); // 追加 JButton yellowB = new JButton("Yellow"); JButton blueB = new JButton("Blue"); JButton redB = new JButton("Red"); panel.add(yellowB); //変更 panel.add(blueB); //変更 panel.add(redB); //変更 contentPane.add(panel); yellowB.addActionListener(this); blueB.addActionListener(this); redB.addActionListener(this); } public void actionPerformed(ActionEvent event) { //JPanelを拡張せず Color c; String source = event.getActionCommand(); if (source == "Yellow") { c=Color.yellow; } else if (source == "Blue") { c=Color.blue; } else { c=Color.red; } panel.setBackground(c); //変更 } } [実行結果] 2-1 と同じ結果になる。 ソースプログラム ColorTest4.java [コメント] 1 panel.add(yellow)がきいて、OK(core Java P449を参考にした)。 2 色を変えるのは、panelではなく、contentPaneでもいいかと、 panel.setBackground(c) の代わりに、単にsetBackground(c)とすると、色が変わるのは一瞬で失敗。 3 contentPane.setBackground(c)もだめ。 4 結果的に、形の上でJPanelの拡張は使わないだけで、実質的に使ったのと同じか? だとすると、 panel.add(red)とか、panel.setBackground(c)など、限定子がいらないJPanelを拡張した方が すっきりしているか? 5 しかし、クラスは一つとなって、複雑なコンストラクタ、あるいは内部クラスも必要が なくなり、シンプル になっている。
2-2-1の結果から、パネルを使わなくともできるのではないかとトライしたものである。 パネルでは、デフォルトで、FlowLayoutというレイアウトマネージャが使われる。これが適当な 大きさでボタンを配置してくれる。BorderLayoutを使うと、NORTH,CENTER,SOUTH,EAST,WESTと5区分で、 JApplet(contentPane)の全体を使うので、異常に大きいボタンを作ることになる(2-3-2のコメント1参照)。 バネルを使わずとも、FlowLayoutを意識的に使って、ボタンをそこに配置し、色を変えるのは contentPaneとして成功した。 [ソースプログラム] // import文は同じ。 public class ColorTest3 extends JApplet implements ActionListener { JPanel panel; Container contentPane; public void init(){ contentPane = getContentPane(); contentPane.setLayout(new FlowLayout()); JButton yellowB = new JButton("Yellow"); JButton blueB = new JButton("Blue"); JButton redB = new JButton("Red"); contentPane.add(yellowB); contentPane.add(blueB); contentPane.add(redB); yellowB.addActionListener(this); blueB.addActionListener(this); redB.addActionListener(this); } public void actionPerformed(ActionEvent event) { Color c; String source = event.getActionCommand(); if (source == "Yellow") { c=Color.yellow; } else if (source == "Blue") { c=Color.blue; } else { c=Color.red; } contentPane.setBackground(c); //panel.setBackground(c) に変えて } } [実行結果] 2-1 と同じ結果になる。 ソースプログラム ColorTest3.java [コメント] ボタンの大きさを調整するのに、contentPane.setLayout(new FlowLayout())として、色を変えるの はパネルではなく、contentPaneとすれば、パネルは特に必要なしということになった。すっきりしたが、 contentPane.setLayout(new FlowLayout())は、contentPane全体をFlowLayoutするのではないのかと いう疑問は残る。
2-2の2つの成功例以前のトライである。そこに至るまでの経過とソースのありか、 実行例、 コメントを記す。一応できたものの意図したこととは大きく違うことになった。 [実行結果] カラーテスト ソースプログラム ColorTest.java [コメント] 1 panel.setBackground(c);は、panelの大きさが意図とは 異なり、全然小さいが、一応OK。 2 contentPane.setLayout(new FlowLayout()) はボタンの 大きさはいいぐあい。 単に、setBackground(c)だと、JPanelもJLabelにしたと きのように、色が変わらない。 3 しかし、このFlowLayout()がきいて、panelの大きさも小さくなったのだろう? [参考] contentPane.setBackground(c) とすると、背景色の変化が意図した範囲に出る。ただ し、小さ い領域の変化も残る。下記はそのプログラム。この経験が2-2の2つの成功例につながった。 [ソースプログラム] ColorTest10.java
[実行結果] 右の図。 ソースプログラム ColorTest2.java[コメント] 1 BorderLayout.WESTなどとすると、ボタンが異常に 大きくなる。 contentPane.add(yellow,BorderLayout.WEST); contentPane.setLayout(new GridLayout())も同じ。 2 1の問題は、Layoutで、panelのみ大きくできればよい。 GridBagがもっとも洗練されているという。 APIにも例があるが、検討課題とする。 なお、ボタンのみをはりつけた別バネルを作った例が、 2-1の他にも Java応用2 6 スライドショーにある。
JPanelの拡張ではなく、JLabelの拡張の拡張でやろうとして、ボタンの表示さえできずに失敗。 参考までにソースのありか、実行例を記載するが、ラベルは表示機能のみであることを思い知った。。 [実行結果] カラーラベル ソースプログラム ColorLabel.java
「Java入門2」6-2 文字コードの16進表現(ASCIIコード)のSwing版。 1-2の10進、16進数変換も加えることにし、1章の2つの事例をあわせたような形で、内容的には、 1章に属するが、2つのJTextFieldや変換ボタン,JLabelを直接、ContentPaneにバネルに置くこと ができない。そこで、2-1を参考にして、ContentPaneにJPanelを貼り付け、その上に、変換ボタ ンなど置くことにした。 2-1との違いは、そのパネルも二つ必要になることである。 プログラム的には2-1の拡張版といえる。 [プログラム] import java.applet.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; /* <applet code="CharCode.class" width=600 height=100> </applet> */ public class CharCode extends JApplet { public void init(){ String htm1=""; String htm2=""; Container contentPane = getContentPane(); BPanel bpanel = new BPanel(htm1,htm2); CPanel cpanel = new CPanel(htm1,htm2); contentPane.add(cpanel,BorderLayout.CENTER); contentPane.add(bpanel,BorderLayout.SOUTH); } } class BPanel extends JPanel implements ActionListener { JButton henkan; JLabel label; JTextField tx; String ht1; String ht2; public BPanel(String ht1,String ht2){ this.ht1=ht1; this.ht2=ht2; tx = new JTextField(10); tx.setEditable(true); tx.addActionListener(this); add(tx); henkan = new JButton("16進数変換"); henkan.addActionListener(this); add(henkan); label = new JLabel(ht1 + "16進数?" + ht2); add(label); } public void actionPerformed(ActionEvent event) { String source = event.getActionCommand(); String s = tx.getText(); int n=Integer.parseInt(s); label.setText(ht1 + "16進数: " +Integer.toHexString(n) + ht2); } } class CPanel extends JPanel implements ActionListener { JButton henkan; JLabel label; JTextField tx; String ht1; String ht2; public CPanel(String ht1,String ht2){ this.ht1=ht1; this.ht2=ht2; tx = new JTextField(10); tx.setEditable(true); tx.addActionListener(this); add(tx); henkan = new JButton("文字コード変換"); henkan.addActionListener(this); add(henkan); label = new JLabel(ht1 + "文字コード? " + ht2); add(label); } public void actionPerformed(ActionEvent event) { String source = event.getActionCommand(); String s = tx.getText(); String ns=""; for (int i=0; i < s.length(); i++) ns += Integer.toHexString(s.charAt(i)) + " "; label.setText(ht1 + "文字コード: " + ns + ht2); } } [実行結果] 文字コード変換 ソースプログラム CharCode.java [コメント] 1 前文に書いたように、ASCIIコード変換のためののcpanelと16進数変換のためのbpanel と、 二つのクラスとそのインスタンスを作成。 CPanel cpanel = new CPanel(htm1,htm2); など。 2 右辺のCPanel(htm1,htm2)は、CPanelクラスのコンストラクタ、htm1,htm2は、JLabelの背景色 などのためのもの(1章でも使用)。 その処理は、CPanelクラスの中で、引数をthisキーワードを使って行っている。 3 cpanelとbpanelをcontentPaneに加える際、BorderLayout.CENTERとSOUTHに、それぞれ配置する。 contentPane.add(cpanel,BorderLayout.CENTER); 個人的にも、このあとの、3 Midiファイルを選択 4 御神籤 6 スライドショーで、BorderLayout の使い勝手の悪さをいやというほど体験していたが、「コアJava2 Vol1.1基礎編」P448の記述が 大いに参考になった。 パネルには、横1列に配置されるデフォルトのFlowLayoutで配置し、そのパネルをBorderLayoutの NORTH,CENTER,SOUTHのいずれかに配置するというものである。 4 「Java入門2」6-2では、先頭文字のみのASCIIコードを表示するものであったが、入力文字列の それぞれの文字のASCIIコードを表示するようにした。日本語、漢字も変換できる。 以前、JavaScriptで組んだ 16進数変換・文字コード と当然ながら、同じ結果になる。 5 文字コード変換で、 ns += Integer.toHexString(s.charAt(i)) + " "; としたが、 toHexStringの引数はint型のはずだが、s.charAt(i)はchar型、それで問題なしなので、 自動的に変換してくれるのだろうか?
ApplicationのJFrameの場合であるが、「コアJava2 1巻」P333に、JFrameの内部構造の図がある。 「コンテントペインはJFrameを構成する多数のペインやフレームの中でも、Swingのプログラムに とって最も重要」とあったので、JAppletの場合も同様と、なんの疑いもなく、1章と2章のすべての プログラムは、Container contentPane = getContentPane()からはじめていた。 ふと、少なくとも、2章のJPanelを拡張して、パネルの上に、ボタンをおいたり描画したりする場合 は、直接、JAppletにしてもいいのではないかと疑問に思い、すべてのプログラムで検証してみた。 結果は、パネルを使用しなかった2-2-2以外では、問題がなかった。 2-2-2は、Applet画面なら、setBackground(c)で背景色が変わるはずなのに、JAppletではだめで あった。 getContentPane()は、1-2のコメント2に書いたことだが、javax.swingバッケージのJAppletクラス のメソッドなので、APIのJAppletの説明を読んでみた。以下、該当部分の引用。 「JFC/Swing コンポーネントアーキテクチャのサポートを追加する java.applet.Applet の 拡張バージョンです。 JApplet クラスは java.applet.Applet と一部互換性がありません。JApplet は JRootPane をその唯一の子として保持します。JApplet の子はどれでも contentPane を親に持たなけ ればなりません。これは java.applet.Applet とは異なります。」 難解、 contentPaneの概念が分かったとはいいかねるが、実体験もして、Appletと違うことは分かった。
Midiファイル1ケの場合は、htmlのembedタグを使えば簡単にできてしまう。そこで、コンボボックス を使って、複数のMidiファイルから選択して鳴らすことにした。 なお、3-2に述べるJARファイルを使っている。 [プログラム] import java.applet.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; /* <applet code="Sound11.class" archive="Sound11s.jar" width=200 height=50> </applet> */ public class Sound11 extends JApplet implements ActionListener { JComboBox cb; JButton play1; JButton stop1; AudioClip onceClip; public void init() { Container contentPane=getContentPane(); String[] midifile={"coroptica.mid","meim.mid","mazurka4.mid", "calkwalk.mid", "gagaku.mid","ieraisha.mid", "ny_bass.mid","sc.mid"}; cb = new JComboBox(midifile); cb.addActionListener(this); contentPane.add(cb,BorderLayout.WEST); play1 = new JButton("Play"); play1.addActionListener(this); contentPane.add(play1,BorderLayout.CENTER); stop1 = new JButton("Stop"); stop1.setEnabled(false); // stop1.setMnemonic(KeyEvent.VK_M); stop1.addActionListener(this); contentPane.add(stop1,BorderLayout.EAST); } public void actionPerformed(ActionEvent event) { //Midiファイルの選択 String cbmidi = (String)cb.getSelectedItem(); onceClip = getAudioClip(getCodeBase(),cbmidi); //Buttonの表示を見る String source = event.getActionCommand(); if ( source == "Play") { onceClip.play(); //Play it once. showStatus(cbmidi + " play" + "."); stop1.setEnabled(true); return; } if (source =="Stop") { onceClip.stop(); //Stop the sound. showStatus(cbmidi + " stop" + "."); stop1.setEnabled(false); return; } } } [実行結果] 複数のmidiファイルの選択と演奏 フォークダンス曲、ショパン、ジャズなどお楽しみください。 ソースプログラム Sound11.java [コメント] 1 midiの音を鳴らすAudioClipインターフェイスとgetAudioClipメソッド、play,stopメソッドに ついては、3-2 のコメントでふれる。 2 コンボボックスを選択するとイベントが発生し、直接選択を受けることができる。 public void itemStateChanged(ItemEvent ie) { String cbmidi = (String)ie.getItem(); } でよい。 itemStateChanged(ItemEvent ie) は ItemListener の、getItem()は JComboBoxのメソッド。 3 そうすると、JButtonのplayボタンが不要になる。それはいいのだが、stop のactionPerformed まできかなくなるので、下記のように選択したアイテムだけを返すようにした。 String cbmidi = (String)cb.getSelectedItem();。 4 最初、 String cbmidi = (String)event.getSelectedItem(); としていて、「シンボルを解決できない」とのエラーに悩まされた。 5 actionPerformedの中で使っている「Javaチュートリアル」にあったComboBoxDemo.javaの下記 2行を理解しないまま、誤引用したため。 JComboBox cb = (JComboBox)e.getSource(); String petName = (String)cb.getSelectedItem(); getSource()は、EventObject のメソッドで、イベントが発生したオブジェクトを返すので、 左辺はcbであって、cbmidiではない。 5 getSelectedItem()によって選択されたmidiファイルをgetAudioClipメソッドを使って、JButtonの playボタンで鳴らす。 6 public void stop() { onceClip.stop(); //Cut short the one-time sound. } をいれると、実行はするが、ブラウザーツールーサンのJavaコンソール でエラー。 7 後述するが、多くのmidiファイルをひとまとめにする意味で、JARファイルを作成、使用した。 アプレットタグの中のarchive="Sound11s.jar" にあらわれる。 8 最初、あまりの難航に、JAppletではAudioClipはだめなのかと、APIでSequencerインターフェイスを 見つけて下記のようにやってみたが、 implements ActionListener,Sequencer { Sequencer onceClip; onceClip = MidiSystem.getSequencer(); 「パッケージjavax.soundは存在しない」「クラス Sequencerのシンボルを解決できない」とかさんざん。
サンマイクロのTutorial Playing Sounds の中のSoundApplet.javaを大いに参考とし、それを簡潔にしたものである。3-1の前に取り組んだもの、Midiファイル1つの場合、もとがAppletとAWT によるものだったので、あえて、JAppletとSwingに変えることはしなかった。
AudioClipによるMidiの扱いとplay、stopボタンによる処理のシンプルな例としてやくだつかもしれない。プログラムのありかとコメントのみを残す。
[実行結果] midiファイルの演奏 。 ソースプログラム Sound.java [コメント] 1 AppletとAWTによるもので、 Container pane = getContentPane(); pane.setLayout(new GridLayout(0,2)); がなく、pane.add(label)はなく、単にadd(label)となっている。 (addは、Appletクラスの上位にあるContainerクラスのメソッド)。 2 APIでみると、getAudioClip()は、AppletクラスのメソッドでAudioClipを出力する。 AudioClip はjava.appletに属するpublic Interfaceとなっているが、implementsとはしていない。 AudioClipインターフェイスがAppletクラスに実装されているからと推測している。 3 また、AudioClipのメソッドには、play(),stop()の他にloop()があるが、それは使っていない。 インターフェイスを利用する場合はすべてのメソッドを実装するという約束ごとも守っていない ことになるが、問題なく実行できる。それも、Appletクラスでは実装されているとすれば、納得できる。
"吉"とか""凶"とか、おみくじを配列とし、乱数にその添字(index)を指定させて画面に表示する。"あなたの運勢は?"と表示したボタンを押すと、その処理が始まるようにする。ここまでは、JavaScriptの基本-3章 とほほ同じ(なお、JavaScriptの応用2-5章 には、DOM(Document Object Model)の応用例としても使っている)。
image処理のトライとして、写真を追加することにした。それが難物であった。
写真をLabelに張り付けるのは、JLabelのコンストラクタに、写真ファイルから生成するImageIcon を引数にするつだけでよく簡単なのだが、その引数でやや苦労した。
おみくじ2[プログラム] import java.applet.*; import java.awt.*; import java.awt.event.*; import java.net.*; import javax.swing.*; /* <applet code="Omikuji2.class" width=600 height=550> </applet> */ public class Omikuji2 extends JApplet implements ActionListener { JLabel label1; public void init(){ Container pane = getContentPane(); String html1="<html><body style=\"background:yellow;font-size:1.3em;\">"; JButton uranai = new JButton(html1 + "あなたの運勢は?"); uranai.addActionListener(this); pane.add(uranai,BorderLayout.WEST); label1 = new JLabel(""); pane.add(label1,BorderLayout.EAST); URL baseURL = getCodeBase(); ImageIcon icon = new ImageIcon(getURL(baseURL,"shinobazu47.jpg")); JLabel label2 = new JLabel(icon); pane.add(label2,BorderLayout.SOUTH); } public void actionPerformed(ActionEvent event) { String[] omikuji = {"大吉","吉","小吉","小凶","凶","大凶"}; int n = (int)Math.floor(Math.random()*6); String message = " すべてうまくいくが、調子に乗らぬこと"; String message1 = " 小さな喜び事あり。争いを避けること"; String message2 = " これからが肝心。今の調子でがんばれ"; String message3 = " 思わぬところに落とし穴が。用心すべし"; String message4 = " 何事も辛抱"; String message5 = " 今が最悪期。これから徐々に運が上向く"; String html2="<html><body style=\"background:blue;color:white;font-size:1.5em;\">"; String html3="<html><p style=\"color:yellow;font-size:1.1em;\">"; switch (n) { case 0: label1.setText(html2 + omikuji[0] + html3 + message); break; case 1: label1.setText(html2 + omikuji[1] + html3 + message1); break; case 2: label1.setText(html2 + omikuji[2] + html3 + message2); break; case 3: label1.setText(html2 + omikuji[3] + html3 + message3); break; case 4: label1.setText(html2 + omikuji[4] + html3 + message4); break; case 5: label1.setText(html2 + omikuji[5] + html3 + message5); break; } } protected URL getURL(URL codeBase, String filename) { URL url = null; try { url = new URL(codeBase, filename); } catch (java.net.MalformedURLException e) { System.out.println("Couldn't create image: badly specified URL"); return null; } return url; } } [実行結果] 運試し、お御神籤 でどうぞ。 ソースプログラム Omikuji2.java [コメント] 1 画像処理にImageクラスを使う例はこの後の4-2で述べるが、「Javaチュートリアル」P484に 「Appletクラスの提供するgetImageメソッドが、ロードされた画像に対応するImageオプジェクトを 生成するが、Swingアプレットでは、ImageよりもIconを使うため、ImageIcon(画像ファイルからロ ードされるアイコン)を使う傾向がある」とあった。 2 そして、同書P484に、 URL baseURL = getCodeBase(); ImageIcon icon = new ImageIcon(getURL(baseURL,"shinobazu47.jpg"); 同等の例があった。 (これも、最初は"../image/shinobazu47.jpg"として、画像をimageフォルダにおいてトライ、 ネット上ではいいが、ローカルではだめなので、classと同一ディレクトリにおいた)。 3 getURLはP484にあったメソッドそのまま、ややこしそうだが、単に画像ファイルのurlを返す だけとみて、ImageIconコンストラクタの引数だけに変更してみた。 ImageIcon icon = new ImageIcon(new URL(baseURL, "shinobazu47.jpg")); 4 すると「例外 java.net.MalformedURLException は報告されない。スローするにはキャッチまたは、 スロー宣言をしなければならない」とのエラーメッセージをもらって、getURLメソッドを追加して 解決した。 APIでURLクラスをみると、このコンストラクタには、throws MalformedURLException が必要と あった!! 5 例外処理まで含めてやや複雑なことになったが(5章では簡潔にした)、ImageIconオブジェクト が生成できてしまえば、それをJLabelにはりつければよく、きわめて簡単である(JLabelのコン ストラクタの引数にするだけ)。JLabel label2 = new JLabel(icon); 6 画像ファイルがひとつあるだけなので、あえてJARファイルを作成するまでもないが、トライしたが、 画像が出ない。ImageIconのコンストラクタとJARファイルは相性が悪いようだ(7章参照)。 [それ以前の経緯] 7 画像ファイルとJavaのコード(この場合はOmikuji2.class)が同じディレクトリにある場合は、 単に、ImageIcon icon = new ImageIcon("shinobazu47.jpg"); だけでよい。 ただし、appletviewerの場合だけで、ブラウザで見る Omikuji2.htmlおよびアップした場合は、 下記エラー。 同じディレクトリにあろうとも、プログラム(Omikuji2.java)のようにしないといけない。 classjava.security.AccessControlException: access denied (java.io.FilePermission shinobazu47.jpg read) at Omikuji20.init(Omikuji20.java:33) アプレットの場合は、セキュリティのためファイルアクセスに関する制限が厳しい。 (4、6は、Omikuji20.java にソースと経緯がある)。 8 Omikuji5.java は、 Image image = Toolkit.getDefaultToolkit().getImage("../image/shinobazu47.jpg");とする方法を 「Core Java」P365でみつけてトライして失敗した。「Javaチュートリアル」P444には、アプレットで は使えないとある。 9 「Javaチュートリアル」P385にあった例にもとづき、 URL iconURL=ClassLoader.getSystemResource("shinobazu47.jpg"); ImageIcon icon = new ImageIcon(iconURL); としたが、失敗。Omikuji30.java これもアプレットでは使えないものかもしれない。
JPanelを拡張した別クラス MyPanelに、paintComponent(Appletクラスではpaint)メソッドをおき、 appletタグのパラメータ(下記、プログラム内のコメント行にある)による画像ファイル名から imageを生成する getImage(getCodeBase(),getParameter("file"));メソッドをおいている。
おみくじ別解 Omikuji.java[プログラム] import 4-1 に同じ /* <applet code="Omikuji.class" archive="Omikujis.jar" width=600 height=480> <param name="file" value="rakan168.jpg"> </applet> */ public class Omikuji extends JApplet implements ActionListener { JLabel label1; public void init(){ // pane.add(label1,BorderLayout.EAST)の行まで、JButton とlabel1の設定までは、 4-1 に同じ。 // 以下、4-1では、label2に、ImageIcon iconをはりつけているだけ。こちらは、以下の4行。 MyPanel panel=new MyPanel(); Dimension d =new Dimension(600,412); panel.setPreferredSize(d); pane.add(panel,BorderLayout.SOUTH); } public void actionPerformed(ActionEvent event) { 4-1 に同じ } class MyPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); java.awt.Image image = getImage(getCodeBase(),getParameter("file")); // java.awt.Image image =getImage(getDocumentBase(),getParameter("file")); g.drawImage (image,0,0,this); } } } [実行結果] お御神籤 でどうぞ。(写真のみ、4-1と異なる)。 ソースプログラム Omikuji.java [コメント] 1 冒頭に述べたように、JPanelを拡張した別クラス MyPanelにおいたpaintComponentメソッドで、 imageを生成、描画する。別クラス MyPanelを内部クラス とした。 2 最初は内部クラスにするということを知らず、JAppletとの関連がないためか、Appletタグの パラメータを取得するgetParameterメソッドがエラーとなる。 3 やむなく、JAppletを拡張しているメインのクラスに移し、出力のimageを引数にして、引数つきの コンストラクタまで作って、MyPanelに渡していた。 4 応用2 「8 スライドショーの失敗例」でさんざん苦労したあげく、 内部クラスにする手法を知った。メインクラスの変数はそのまま使えるだけでもありがたいが、問題の Image image = getImage(getCodeBase(),getParameter("file"));を直接、MyPanelに書いてもよ くなった。内部クラスゆえ、JAppletとの関連もとれるのだろう。 4 ただし、問題の行で、「互換性のない型 検出値: java.awt.Image、期待値: Image」ということだ ったので、強引に、java.awt.Image image として解決。 5 画像部分の大きさ、 panel.setSize(600,412);ではだめで、JComponentから継承したpanel.setPreferredSize(d); としたが, 引数として、Dimensionを要求された。d =new Dimension(600,412);] 6 4-1と違い、JARファイルはうまくいく。ImageIconではなく、Imageオブジェクトを使えばいいようだ。 [それ以前の経緯] 7 Omikuji10.javaが、Omikuji.javaの前段のトライ、canvasもpanelも使わなかった。画像は出るが、 御神籤のJButtonが遅くなり、位置制御もうまくいかない。 8「独習」P450のCanvasにImageを描画して、それをpaneにaddした。 それがOmikuji1.java、Omikuji.javaの原型。 9 しかし、「コアJava2 Vol1.1基礎編」P335によると、Canvasやpaintは古いAWTのもの、swing では、PanelとpaintComponentがよいとあり変更した。別クラスでは、super.paintComponent(g);と 上位クラスが役割を果たすように必要ともあった。
3章と4章で、すでにJARファイルにふれてきたが、ここでまとめておく。
[JARファイルのメリット] 多くのmidiファイルがあれば、ブラウザはその都度接続をしなくてはならず、時間もかかる。 また、midiファイルをたとえば、midiという別ディレクトリにまとめておくと、アップしないこと にはデバッグもできないことになる。 同一ディレクトリにおいて、JAR(Java ARchive)ファイルとしてひとまとめにする方法がある。 接続も一度ですむ。 [JARファイルの作成] コマンドプロンプトで、 jar cvf Sound11s.jar *.class *.mid | | | JARファイル名 格納するファイル とすればよい。 cvfは、jarツールのオプションの一部で、 cは新しいアーカイブファイルを作成 vはコマンドプロンプト上に格納過程などを表示 fはアーカイブファイルの名前をコマンドライン引数の2番目に指定(この場合はSound11s.jar) [JARファイルの解凍] コマンドプロンプトで、 jar xvf Sound11s.jar [JARファイルの内容一覧を表示] jar tf Tanigawa.jar (解凍せずに一覧を見たい場合) [アプレットタグへの追加] 、<applet code="Sound11.class" archive="Sound11s.jar" width=300 height=50> と、アーカイブファイル名を加える。(以上「Core Java1巻のP640を参考にした)。 なお、パッケージ化してディレクトリが異なるファイルを含む場合のJarファイルの作成が Java応用2の7章 にある。