| ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 2007年07月01日 楽しいJava講座 - 初心者から達人へのパスポート vol.060 セルゲイ・ランダウ バックナンバー: http://www.flsi.co.jp/Java_text/ ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ [このメールマガジンは、画面を最大化して見てください。] ======================================================== ◆ 01.グラフィックスのプログラミング ======================================================== 今回は、描いた絵を印刷できるように、お絵描きソフトに印刷 の機能を追加することにします。 ただし、アプレットは(サンドボックス・モデルによって)ダウン ロード先のコンピューターを操作することができませんので、お絵 描きソフトをいったんアプリケーションに作り直してから行います。 では、さっそくEclipseを起動してください。 まずはお絵描きソフトのアプリケーション版を作りましょう。 JStudy1プロジェクトのjp.co.flsi.lecture.cgパッケージの中に GraffitiJFrameというクラス名で作成することにします。 そして、GraffitiJFrameにGraffitiAppletとまったく同じお絵描き 機能をつけます。このときGraffitiPanelがそのまま使えますね。 今までに学んだことをよく理解できている人は、演習として独力で お絵描きソフトのアプリケーション版を作ってみてください。 独力で作れる自信のない人は以下をお読みください。 (1) パッケージ・エクスプローラーで、JStudy1の中のjp.co.flsi.lecture.cg を右クリックし、「新規」→「その他」を選択します。 (2) 「新規」ウインドウでは「Java」の「Visual Class」を選択し、 「次へ」ボタンをクリックします。 (3) 「名前」にはGraffitiJFrameを入力し、「Style」はSwingの中の Frameを選択(「スーパークラス」がjavax.swing.JFrameになることを 確認)し、「public static void main(String[] args)」にチェック・ マークを入れて「終了」ボタンをクリックします。 GraffitiJFrameのエディターが開いたら、GraffitiJFrameのサイズを 少し広げておきましょう。 initialize()メソッドのソース・コードの先頭にある行 -------------------------------------------------------- this.setSize(300, 200); -------------------------------------------------------- を -------------------------------------------------------- this.setSize(300, 280); -------------------------------------------------------- に変更します。(数値は自分の好みに合わせてください。 VEエディターの画面上でハンドルをドラッグして広げるという方法も あります。) 次にGraffitiJFrameのVEエディターで、LayoutManagerをnullにして おきましょう。 JFrameの中にはJPanelが張り付いているのでしたね、それを右クリッ クし、「Set Layout」→「null」を選択します。 次にGraffitiAppletのときと同じように下記のGUI部品を貼り付けて いきましょう。 (1) JToggleButtonを貼り付け、Bean NameはredToggleButton、 textプロパティーは「赤」、backgroundプロパティーも赤色にして おきます。 (2) JToggleButtonを貼り付け、Bean NameはgreenToggleButton、 textプロパティーは「緑」、backgroundプロパティーも緑色にして おきます。 (3) JToggleButtonを貼り付け、Bean NameはblueToggleButton、 textプロパティーは「青」、backgroundプロパティーも青色にして おきます。selectedプロパティーをtrueにしておきます。 (4) JListを貼り付け、Bean NameはstrokeList。サイズを少し広げ ておきます。 (5) JToggleButtonを貼り付け、Bean NameはfreeCurveToggleButton、 textプロパティーは「自由曲線」、selectedプロパティーをtrueにし ておきます。 (6) JToggleButtonを貼り付け、Bean NameはlineToggleButton、 textプロパティーは「直線」 (7) JToggleButtonを貼り付け、Bean NameはrectangleToggleButton、 textプロパティーは「矩形」 (8) JToggleButtonを貼り付け、Bean NameはellipseToggleButton、 textプロパティーは「楕円」 (9) GraffitiPanelを貼り付け、Bean NameはgraffitiPanelにしまし ょう。サイズを調整しておきましょう。 貼り付けたあと、GraffitiAppletと同じように各GUI部品をきれいに 揃えておきましょう(vol.056参照)。 では、次にソース・コードの編集を行いましょう。 (1) initialize()メソッドの最後に以下のコードを追加します。 -------------------------------------------------------- ButtonGroup group = new ButtonGroup(); group.add(getRedToggleButton()); group.add(getGreenToggleButton()); group.add(getBlueToggleButton()); ButtonGroup shapeGroup = new ButtonGroup(); shapeGroup.add(getFreeCurveToggleButton()); shapeGroup.add(getLineToggleButton()); shapeGroup.add(getRectangleToggleButton()); shapeGroup.add(getEllipseToggleButton()); -------------------------------------------------------- (2) 各色のボタンにイベントのコードを追加しましょう。 まず、「赤」ボタン(redToggleButton)を右クリックし、 「Events」→「actionPerformed」を選択し、getRedToggleButton() メソッドの中に生成された次のコード -------------------------------------------------------- redToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed() } }); -------------------------------------------------------- を次のように編集します。 -------------------------------------------------------- redToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { getGraffitiPanel().setDrawColor(new Color(255,0,0)); } }); -------------------------------------------------------- 同様にして、「緑」ボタン(greenToggleButton)に対しては、 (getGreenToggleButton()メソッドの中で) -------------------------------------------------------- greenToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { getGraffitiPanel().setDrawColor(new Color(0,255,0)); } }); -------------------------------------------------------- 同様にして、「青」ボタン(blueToggleButton)に対しては、 (getBlueToggleButton()メソッドの中で) -------------------------------------------------------- blueToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { getGraffitiPanel().setDrawColor(new Color(0,0,255)); } }); -------------------------------------------------------- というふうに編集しましょう。 ┌補足─────────────────────────┐ 編集しているうちにVEエディターの画像(デザイン・ビューと呼ぶ) が変になるときがありますね。例えば色が消えたとか、GUI部品が 消えたとか、何らかの形で画像がおかしくなるときがあります。 そういうときは、一度Eclipseを終了し、再度Eclipseを起動して みてください。 └───────────────────────────┘ (3) JList(strokeList)に項目を追加し、描画の線の太さを変えら れるようにソース・コードを編集します。 getStrokeList()メソッドを次のように編集しましょう。 -------------------------------------------------------- private JList getStrokeList() { if (strokeList == null) { Vector<String> stroke = new Vector<String>(); strokeList = new JList(stroke); stroke.add("太さ1"); stroke.add("太さ2"); stroke.add("太さ5"); strokeList.setBounds(new Rectangle(14, 106, 42, 53)); strokeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); strokeList.addListSelectionListener(new javax.swing.event.ListSelectionListener() { public void valueChanged(javax.swing.event.ListSelectionEvent e) { switch (strokeList.getSelectedIndex()){ case 0: getGraffitiPanel().setDrawStroke(new BasicStroke(1f)); break; case 1: getGraffitiPanel().setDrawStroke(new BasicStroke(2f)); break; case 2: getGraffitiPanel().setDrawStroke(new BasicStroke(5f)); break; } } }); } return strokeList; } -------------------------------------------------------- なお、Rectangle(14, 106, 42, 53)の数値はこの通りにする必要は ありません。 (4) 各図形のボタンにイベントのコードを追加しましょう。 まず、「自由曲線」ボタン(freeCurveToggleButton)を右クリックし、 「Events」→「actionPerformed」を選択し、getFreeCurveToggleButton() メソッドの中に生成された次のコード -------------------------------------------------------- freeCurveToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed() } }); -------------------------------------------------------- を次のように編集します。 -------------------------------------------------------- freeCurveToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { getGraffitiPanel().setFreeCurveFlag(true); getGraffitiPanel().setLineFlag(false); getGraffitiPanel().setRectFlag(false); getGraffitiPanel().setEllipseFlag(false); } }); -------------------------------------------------------- 同様にして、「直線」ボタン(lineToggleButton)に対しては、 (getLineToggleButton()メソッドの中で) -------------------------------------------------------- lineToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { getGraffitiPanel().setFreeCurveFlag(false); getGraffitiPanel().setLineFlag(true); getGraffitiPanel().setRectFlag(false); getGraffitiPanel().setEllipseFlag(false); } }); -------------------------------------------------------- 同様にして、「矩形」ボタン(rectangleToggleButton)に対しては、 (getRectangleToggleButton()メソッドの中で) -------------------------------------------------------- rectangleToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { getGraffitiPanel().setFreeCurveFlag(false); getGraffitiPanel().setLineFlag(false); getGraffitiPanel().setRectFlag(true); getGraffitiPanel().setEllipseFlag(false); } }); -------------------------------------------------------- 同様にして、「楕円」ボタン(ellipseToggleButton)に対しては、 (getEllipseToggleButton()メソッドの中で) -------------------------------------------------------- ellipseToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { getGraffitiPanel().setFreeCurveFlag(false); getGraffitiPanel().setLineFlag(false); getGraffitiPanel().setRectFlag(false); getGraffitiPanel().setEllipseFlag(true); } }); -------------------------------------------------------- というふうに編集しましょう。 以上で、GraffitiJFameにGraffitiAppletと同じお絵描き機能が 組み込まれました。 GraffitiJFameを保管し、実行してテストしてみてください。 では続いて、描いた絵を印刷する機能を組み込むことにしましょう。 まず最初にこのアプリケーションにメニュー・バーをつけて、印刷の メニュー項目を追加しておきましょう。 (1) パレットから「Swing Menus」の中の「JMenuBar」を取り出し、 JFrameのタイトル・バーをクリックして貼り付けます。Bean Nameは 「jJMenuBar」にしておきましょう。 (2) パレットから「Swing Menus」の中の「JMenu」を取り出し、 「Java Beans」ビューのjJMenuBarをクリックして貼り付けます (「Java Beans」タブをダブルクリックして「Java Beans」ビュー を最大化してから貼り付けると操作しやすい)。 Bean Nameは「jMenuFile」にしておきましょう。 (3) 「Java Beans」ビューで「jMenuFile」が選択されている状態で、 「プロパティー」ビューを開き、textプロパティーの値を「ファイル」 にしましょう。 (4) 再度「Java Beans」ビューを開き、パレットから「Swing Menus」 の中の「JMenuItem」を取り出し(パレットの代わりにツール・バーから GUI部品を取り出すこともできます。余裕のある人はツール・バーの中を 探してみてください)、「Java Beans」ビューのjMenuFileをクリックし て貼り付けます(「Java Beans」タブをダブルクリックして「Java Beans」 ビューを最大化してから貼り付けると操作しやすい)。 Bean Nameは「jMenuItemPrint」にしておきましょう。 (5) jMenuItemPrintのtextプロパティーの値は「印刷」にしておきま しょう。 こうしてメニュー・バーを追加すると、既に張り付いていたGUI部品が 下にずれるでしょうから、各GUI部品の位置を調整しなおしてください。 では次にGraffitiJFrameに、印刷を行うためのメソッドを追加しましょう。 次のメソッドを追加してください。 -------------------------------------------------------- public void printGraffiti() { PrintJob p = getToolkit().getPrintJob(this, "絵の印刷", null); if (p != null) { Graphics g = p.getGraphics(); getGraffitiPanel().paint(g); g.dispose(); p.end(); } } -------------------------------------------------------- ここで、getToolkit()は、Toolkitというクラスのオブジェクトを返し、 そのgetPrintJob()メソッドは印刷操作を開始(この時点で印刷のウイン ドウが開きます)して、PrintJobオブジェクト(印刷操作(印刷ジョブ)を 表現するオブジェクト)を返します。 PrintJobオブジェクトのgetGraphics()メソッドで取り出したGraphicsオ ブジェクトに対して描画すると、それを印刷することが可能になります。 そこで、 -------------------------------------------------------- getGraffitiPanel().paint(g); -------------------------------------------------------- というコードによって、GraffitiPanelのpaint()メソッドを印刷に利用 しています。 ここに描画した内容はGraphicsオブジェクトをdispose()することによっ てプリンターに送られます。 PrintJobオブジェクトのend()メソッドは印刷操作の後始末を行います。 では、GraffitiJFrameのメニュー項目からこのメソッドを呼び出せるよう にプログラミングしましょう。 (1) GraffitiJFrameのVEエディターにおいて「Java Beans」ビューの中の jMenuItemPrintを右クリックし、「Events」→「actionPerformed」を選択 します。 (2) getJMenuItemPrint()メソッドの中に次のコードが自動生成されましたね。 -------------------------------------------------------- jMenuItemPrint.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed() } }); -------------------------------------------------------- これを次のように編集しましょう。 -------------------------------------------------------- jMenuItemPrint.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { printGraffiti(); } }); -------------------------------------------------------- では、これらを保管し、GraffitiJFrameを実行してテストしてみましょう。 絵を描いたあと、「ファイル」メニューの「印刷」を選択し、印刷を行っ てみてください。 実は、印刷しようとすると、キャストのエラーになるはずです!!! 「コンソール」に表示される赤いメッセージの一番先頭を見てください。 「java.lang.ClassCastException」というエラー(Exception)が発生して いますね。 これはキャストのエラーを意味します。 その下の行 -------------------------------------------------------- at jp.co.flsi.lecture.cg.GraffitiPanel.paint(GraffitiPanel.java:nn) -------------------------------------------------------- (nnには数字がはいる) は、このClassCastExceptionがGraffitiPanelのpaint()メソッドのところ (GraffitiPanel.javaのソース・コードのnn行目のところ)で発生してい ることを示しています。 「GraffitiPanel.java:nn」のところにアンダーラインが引かれていますね。 そこをクリックしてみてください。 ソース・コードの中の該当する行を表示してくれます。 paint()メソッドの中の先頭の -------------------------------------------------------- Graphics2D g2d = (Graphics2D) g; -------------------------------------------------------- という行ですね。ここでは、(Graphics型の)gをGraphics2D型にキャスト しています。 実は、PrintJobオブジェクトから取り出せるGraphicsオブジェクトは Graphics2D型ではないのでキャストできないのです。それで、印刷しよう としたときにClassCastExceptionが発生したのです。 そこで、GraffitiPanelのpaint()メソッドの先頭 -------------------------------------------------------- Graphics2D g2d = (Graphics2D) g; -------------------------------------------------------- の行を削除し、最後の -------------------------------------------------------- g2d.drawImage(image, 0, 0, 300, 200, null); -------------------------------------------------------- の行を -------------------------------------------------------- g.drawImage(image, 0, 0, 300, 200, null); -------------------------------------------------------- に書き換えてみてください。 保管して再度GraffitiJFrameをテストし直してみてください。今度は うまくいきますね。 実はこの(Graphics2D型の)g2dという変数はdrawImage(image, 0, 0, 300, 200, null) を呼び出すためだけに使っていたのですが、これは(Graphics型の)gの drawImage(image, 0, 0, 300, 200, null)を呼び出しているだけだったのです。 つまりこのdrawImage()メソッドはGraphics2DのものではなくGraphicsから継承 したものだったのです。したがって、元々Graphics2D型にキャストする必要は なかったのです。 これで、Exceptionが発生したときの調べ方もわかりましたね。 今回はここまで。 (続く) 以上、今回は ┌───────────────────────────┐ ・メニューの組み込み方 ・グラフィックスの印刷の仕方 ・Exceptionが発生したときの調べ方 └───────────────────────────┘ を学習しました。 では、また来週。 ======================================================== ◆ 02.文法解説 [JPanelのpaint()メソッド] ======================================================== [paint()メソッドとpaintComponent()メソッド] これまで、JPanelをお絵描きのキャンバス代わりに使ってきました。 つまりJPanelを絵を描くときの用紙のように使い、paint()メソッドで 描画しました。 実はJPanel自体はpaint()メソッドを持っておらず、そのスーパークラ スであるJComponentのpaint()メソッドを継承しているだけです。した がって、GraffitiPanelで実装したpaint()メソッドは、JComponentの paint()メソッドをオーバーライド(override=上書き)していたことに なります。 実はJPanel(正確にはそのスーパークラスのJComponent)のpaint()メ ソッドは、paintComponent()メソッド、paintBorder()メソッド、 paintChildren()メソッドをこの順に呼び出します。これらのメソッド はそれぞれ、自分自身の描画、ボーダー(border=境界:JPanelの外周の こと)の描画、張り付いているGUI部品の描画を行います。 ボーダーの描画や張り付いているGUI部品の描画をあとにすることによっ て、ボーダーやGUI部品がJPanel自身の描画によって隠れてしまわない ようにしようとしているわけです。 もし、このpaint()メソッドをオーバーライドしてしまうと、張り付い ているGUI部品がかき消されてしまいます。 したがって、JPanelにGUI部品を貼り付けているとき(これがJPanelの 普通の使い方ですが)は、paint()メソッドをオーバーライドしては いけません。またボーダーを描いている場合もpaint()メソッドをオー バーライドしてはいけません。 代わりにpaintComponent()メソッド(自分(JPanel)自身を描画するメ ソッド)を上書きします。 なお、メソッドのオーバーライドを行うときには、原則として最初に スーパークラスのメソッドの呼び出しを行っておきます(以下の演習 を参照)。これは、元々のメソッドの機能を実行した上で、さらに機能 を追加することを意味します。 スーパークラスのメソッドの呼び出しが不要かどうかを判断するため には、そのメソッドが何をするものかよく知っている必要があります。 APIリファレンスなどでメソッドを調べて判断してください。 (続く) ================================================ ◆ 03.演習問題 ================================================ 1. 上記のpaint()メソッドのオーバーライドとpaintComponent()メソッド のオーバーライドの違いについて確認するために以下のようなクラスを 作成し、TestJFrameを実行してみてください。(左側にPaintJPanel、 右側にPaintComponentJPanelが張り付いていますので、見比べてくだ さい。) PaintJPanelとPaintComponentJPanelはどちらも放射状の線を描き、中央 にボタンを一つ貼り付け、外周にBevelBorderという立体的なボーダーを 使用した、同じようなパネルにしていますが、paint()メソッドをオーバー ライドしたか、paintComponent()メソッドをオーバーライドしたか、だけ が異なっています。 paint()メソッドをオーバーライドすると、張り付いているGUI部品(ここ ではボタン)やボーダーがかき消されることを確認してください。 [PaintJPanel -- paint()をオーバーライドしたパネル] -------------------------------------------------------- import java.awt.Color; import java.awt.Graphics; import javax.swing.JButton; import javax.swing.JPanel; import javax.swing.border.BevelBorder; public class PaintJPanel extends JPanel { private JButton aButton = new JButton(); public PaintJPanel() { super(); setSize(100, 100); setLayout(null); setBorder(new BevelBorder(BevelBorder.RAISED)); aButton.setBounds(10, 40, 80, 20); aButton.setText("Test"); add(aButton); } public void paint(Graphics g) { super.paint(g); // スーパークラスのメソッドの呼び出し g.setColor(new Color(0, 255,255)); for (float theta = 0; theta < Math.PI * 20; theta++) { g.drawLine(50, 50, (int)(50 * Math.cos(theta/10)) + 50, (int)(50 * Math.sin(theta/10)) + 50); } } } -------------------------------------------------------- [PaintComponentJPanel -- paintComponent()をオーバーライドしたパネル] -------------------------------------------------------- import java.awt.Color; import java.awt.Graphics; import javax.swing.JButton; import javax.swing.JPanel; import javax.swing.border.BevelBorder; public class PaintComponentJPanel extends JPanel { private JButton aButton = new JButton(); public PaintComponentJPanel() { super(); setSize(100, 100); setLayout(null); setBorder(new BevelBorder(BevelBorder.RAISED)); aButton.setBounds(10, 40, 80, 20); aButton.setText("Test"); add(aButton); } public void paintComponent(Graphics g) { super.paintComponent(g); // スーパークラスのメソッドの呼び出し g.setColor(new Color(0, 255,255)); for (float theta = 0; theta < Math.PI * 20; theta++) { g.drawLine(50, 50, (int)(50 * Math.cos(theta/10)) + 50, (int)(50 * Math.sin(theta/10)) + 50); } } } -------------------------------------------------------- [TestJFrame -- テスト用のフレームウインドウ] -------------------------------------------------------- import javax.swing.JFrame; import javax.swing.JPanel; public class TestJFrame extends JFrame { private PaintJPanel panel1 = new PaintJPanel(); private PaintComponentJPanel panel2 = new PaintComponentJPanel(); private JPanel contentPane = new JPanel(); public TestJFrame() { super(); setSize(250, 150); setContentPane(contentPane); contentPane.setLayout(null); contentPane.add(panel1, null); contentPane.add(panel2, null); panel1.setBounds(10, 10, 100, 100); panel2.setBounds(120, 10, 100, 100); setTitle("TestJFrame"); } public static void main(String[] args) { TestJFrame frame = new TestJFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } -------------------------------------------------------- 新出のクラスやメソッドがありましたら、自分でAPIリファレンスなどで 調べて理解しておいてください。 2. 上記のPaintJPanelクラスのpaint()メソッドの先頭にある -------------------------------------------------------- super.paint(g); // スーパークラスのメソッドの呼び出し -------------------------------------------------------- の行を削除してからTestJFrameを再度実行し、振る舞いがどう変わるか 確認してください。 3. 今度は、 -------------------------------------------------------- super.paint(g); // スーパークラスのメソッドの呼び出し -------------------------------------------------------- をPaintJPanelクラスのpaint()メソッドの一番最後に移動してから TestJFrameを再度実行し、振る舞いがどう変わるか確認してください。 自力ではどうしても理解できないところがありましたら、下記のWebページ に質問を送ってください。 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ★ホームページ: http://www.flsi.co.jp/Java_text/ ★このメールマガジンは 「まぐまぐ(http://www.mag2.com)」 を利用して発行しています。 ★バックナンバーは http://www.flsi.co.jp/Java_text/ にあります。 ★このメールマガジンの登録/解除は下記Webページでできます。 http://www.mag2.com/m/0000193915.html ★このメールマガジンへの質問は下記Webページにて受け付けて います。わからない所がありましたら、どしどしと質問をお寄 せください。 http://www.flsi.co.jp/Java_text/ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Copyright (C) 2007 Future Lifestyle Inc. 不許無断複製 |