広告

■□■□■□■□■□■□■□■□■□■□■□■□■□■□■
                      2012年02月17日

   Java総合講座 - 初心者から達人へのパスポート
                 2009年11月開講コース 058号

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■□■□■□■□■□■□■□■□■□■□■□■□■□■□■


-------------------------------------------------------
・現在、このメールマガジンは以下の2部構成になっています。
[1] 当初からのコース:vol.xxx(xxxは番号)が振られています。
   これは現在、中級レベルになっています。
[2] 2009年11月開講コース:xxx号(xxxは番号)が振られています。
   これは現在、初心者向けのレベルになっています。
・このメールマガジンは、画面を最大化して見てください。
小さな画面で見ていると、不適切な位置で行が切れてしまう
など、問題を起すことがあります。
・このメールマガジンに掲載されているソース・コード及び
文章は特に断らない限り、すべて筆者が著作権を所有してい
ます。また、これらのソース・コードは学習用のためだけに
提供しているものです。
-------------------------------------------------------


========================================================
◆ 01.グラフィックスのプログラミング
========================================================

今回は、描いた絵を印刷したりファイルに保管したりできるように、
お絵描きソフトに印刷や保管の機能を追加することにします。
ただし、アプレットは(サンドボックス・モデルによって)ダウン
ロード先のコンピューターを操作することができませんので、アプ
レットのままではこれらの機能を遂行することはできません。
したがって、お絵描きソフトをいったんアプリケーションに作り直
してから行います。



では、さっそくEclipseを起動してください。

まずはお絵描きソフトのアプリケーション版を作りましょう。

JStudy1プロジェクトのjp.co.flsi.lecture.cgパッケージの中に
DrawingToolFrameというクラス名で作成することにします。
そして、DrawingToolFrameにDrawingToolAppletとまったく同じ
お絵描き機能をつけます。
このときDrawingToolPanelがそのまま使えますね。


今までに学んだことをよく理解できている人は、演習として独力で
お絵描きソフトのアプリケーション版を作ってみてください。

独力で作れる自信のない人は以下をお読みください。


(1) パッケージ・エクスプローラーで、JStudy1の中のjp.co.flsi.lecture.cg
を右クリックし、「新規」→「その他」を選択します。

(2) 「新規」ウインドウでは「WindowBuilder」配下の「Swing Designer」配下の
「JFrame」を選択し、「次へ」ボタンをクリックします。

(3) 「名前」欄には
DrawingToolFrame
を入力し、「スーパークラス」欄がjavax.swing.JFrameになっていることを
確認し、「完了」ボタンをクリックします。


次にDrawingToolFrameのDesignビューを開きましょう。LayoutManagerをnullにして
おきましょう。

JFrameの中にはJPanelが張り付いているのでしたね。そのLayoutManagerが
予めBorderLayoutになっているはずなので、確認しておきましょう。
それをクリックし、Properties欄のLayoutを見て下さい。
BorderLayoutになっていますね。


次にDrawingToolAppletのときと同じように、下記のようにしてDrawingToolPanelを
貼り付けましょう。

(1) PaletteのSystem配下の「Choose component」をクリックします。

(2) 「型を開く」ウインドウにおいて、
DrawingToolPanel
と入力し(途中まで入力するだけで)、「一致する項目」欄に
DrawingToolPanel - jp.co.flsi.lecture.cg
がリストされるのでこれを選択(クリック)して、「OK」ボタンをクリック
します。

(3) これをDrawingToolFrameのお腹(JPanelが貼りついている)のCenterの
位置に貼り付け(クリック)します。


以上で、DrawingToolFrameにDrawingToolAppletと同じお絵描き機能が
組み込まれました。

DrawingToolFrameを保管し、実行してテストしてみてください。
ウインドウのサイズが若干小さめでしょうから、周囲をドラッグしてサイズ
を大きくしてから絵を描いてみて下さい。


なお、予めウインドウ(JFrame)のサイズを大きくしておきたい場合は、
DrawingToolFrameのコンストラクターの最後に

      setSize(600, 500);

のようなコードを追加してみて下さい。上のコードでは、横幅を600ピクセル、
高さを500ピクセルに指定していますが、このサイズの数値は自分のお好みに
変えてかまいません。

念のため、編集後のコンストラクター全体のソース・コードを下に提示
しておきます。

--------------------------------------------------------
   public DrawingToolFrame() {
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setBounds(100, 100, 450, 300);
      contentPane = new JPanel();
      contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
      contentPane.setLayout(new BorderLayout(0, 0));
      setContentPane(contentPane);

      DrawingToolPanel drawingToolPanel = new DrawingToolPanel();
      contentPane.add(drawingToolPanel, BorderLayout.CENTER);

      setSize(600, 500);
   }
--------------------------------------------------------


以上で、お絵描きソフトのアプリケーション版ができました。
簡単ですね。



========================================================

では続いて、描いた絵を印刷する機能を組み込むことにしましょう。


まず最初にこのアプリケーションにメニュー・バーをつけて、印刷の
メニュー項目を追加しておきましょう。
DrawingToolFrameのDesignビューを開いて下さい。

(1) Paletteの「Menu」の配下の「JMenuBar」を選択(クリック)し、
DrawingToolFrameのタイトル・バーをクリックして貼り付けます。

すると、タイトル・バーの下に(Add items here)という文字列が表示
されます。これがメニュー・バーです。

(2) Paletteの「Menu」の配下の「JMenu」を選択(クリック)し、
DrawingToolFrameのメニュー・バーの位置((Add items here)という
文字列が表示されている)をクリックして貼り付けます。

変数名(Properties欄のVariable)は
menuFile
にしておきましょう。

また、textプロパティーの値を
ファイル
にしましょう。すると、メニュー・バーに「ファイル」という文字列
が表示されますね。

(3) Paletteの「Menu」配下の「JMenuItem」を選択(クリック)し、
(Add items here)と表示されている所をクリックして貼り付けます。

変数名(Properties欄のVariable)は
menuItemPrint
にしておきましょう。

また、textプロパティーの値を
印刷
にしておきましょう。すると、メニュー項目に「印刷」という文字列
が表示されますね。



では次にDrawingToolFrameに、印刷を行うためのメソッドを追加しましょう。

その前に、DrawingToolFrameのお腹に張り付いているDrawingToolPanelは
DrawingToolFrameのコンストラクターの中の

      DrawingToolPanel drawingToolPanel = new DrawingToolPanel();

という行でインスタンス生成されていますが、この変数drawingToolPanelに
他のメソッドからもアクセスできるようにするために、このローカル変数を
フィールドに変換しておきましょう。

(1) DrawingToolFrameのソース・コードを開いて、そのコンストラクターの中の

      DrawingToolPanel drawingToolPanel = new DrawingToolPanel();

の行の中のdrawingToolPanelにカーソルを入れ、メニュー・バーから
「リファクタリング」→「ローカル変数をフィールドに変換」を選択
します。

(2) 「ローカル変数をフィールドに変換」ウインドウで「OK」ボタンを
クリックします。

これで、この変数drawingToolPanelに他のメソッドからもアクセスできるよう
になりましたが、実はアクセスしたいのは、DrawingToolPanelの中に張り付い
ているDrawingPanelです。印刷のときにこのDrawingPanelのpaint()メソッド
を呼び出す必要があるのです。

ところが、通常では貼り付けたGUI部品(ここではDrawingPanel)は
その所有者(ここではDrawingToolPanel)の中に隠蔽されてしまいます
ので、現状ではDrawingPanelにアクセスできません。

そこで、次の作業を行うことによって、張り付いているDrawingPanelを
外部からアクセスできるようにしておきましょう。

(1) DrawingToolPanelのエディターを開き、そのDesignビューを開きます。

(2) その中に張り付いているDrawingPanel(変数名はdrawingPanel)を
右クリックし、「Expose component」を選択します。

(3) 「Expose component」ウインドウの中でメソッド名が
getDrawingPanel
になっていることを確認し、Modifierは「public」が選択されていること
を確認し、「OK」ボタンをクリックします。

これで、getDrawingPanel()メソッドを通して外部からDrawingPanelにアクセス
できるようになりました。



では続いて、DrawingToolFrameのソース・コードに次のメソッドを追加して
ください。これが印刷を行うためのメソッドです。

--------------------------------------------------------
public void printDrawing() {
PrintJob p = getToolkit().getPrintJob(this, "絵の印刷", null);
if (p != null) {
Graphics g = p.getGraphics();
drawingToolPanel.getDrawingPanel().paint(g);
g.dispose();
p.end();
}
}
--------------------------------------------------------

なお、Graphicsは言うまでもなくjava.awtパッケージに入っていますが、
PrintJobもjava.awtパッケージに入っています。

ここで、getToolkit()は、Toolkitというクラスのオブジェクトを返し、
そのgetPrintJob()メソッドは印刷操作を開始(この時点で印刷のウイン
ドウが開きます)して、PrintJobオブジェクト(印刷操作(印刷ジョブ)を
表現するオブジェクト)を返します。
PrintJobオブジェクトのgetGraphics()メソッドで取り出したGraphicsオ
ブジェクトに対して描画すると、それを印刷することが可能になります。

そこで、
--------------------------------------------------------
drawingToolPanel.getDrawingPanel().paint(g);
--------------------------------------------------------
というコードによって、DrawingToolPanelに張り付いているDrawingPanelの
paint()メソッドを印刷に利用しています。

ここに描画した内容はGraphicsオブジェクトをdispose()することによっ
てプリンターに送られます。

あと、PrintJobオブジェクトのend()メソッドは印刷操作の後始末を行います。


では、DrawingToolFrameのメニュー項目からこのメソッドを呼び出せるよう
にプログラミングしましょう。

(1) DrawingToolFrameのDesignビューを開いてDrawingToolFrameのメニュー・
バーの「ファイル」配下の「印刷」と表示されたメニュー項目(menuItemPrint)
を右クリックし、「Add event handler」→「アクション」→「actionPerformed」
を選択します。

(2) 次のコードが自動生成されましたね。

--------------------------------------------------------
      menuItemPrint.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
         }
      });
--------------------------------------------------------

これを次のように編集しましょう。

--------------------------------------------------------
      menuItemPrint.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            printDrawing();
         }
      });
--------------------------------------------------------


では、これらを保管し、DrawingToolFrameを実行してテストしてみましょう。
絵を描いたあと、「ファイル」メニューの「印刷」を選択し、印刷を行っ
てみてください。


実は、印刷しようとすると、キャストのエラーになるはずです!!!
「コンソール」に表示される赤いメッセージの一番先頭を見てください。
「java.lang.ClassCastException」というエラー(Exception)が発生して
いますね。
これはキャストのエラーを意味します。
その下の行
--------------------------------------------------------
at jp.co.flsi.lecture.cg.DrawingPanel.paint(DrawingPanel.java:nn)
--------------------------------------------------------
(nnには数字がはいる)
は、このClassCastExceptionがDrawingPanelのpaint()メソッドのところ
(DrawingPanel.javaのソース・コードのnn行目のところ)で発生してい
ることを示しています。
「DrawingPanel.java:nn」のところにアンダーラインが引かれていますね。
そこをクリックしてみてください。
ソース・コードの中の該当する行を表示してくれます。
paint()メソッドの中の先頭の
--------------------------------------------------------
Graphics2D g2d = (Graphics2D) g;
--------------------------------------------------------
という行ですね。ここでは、(Graphics型の)gをGraphics2D型にキャスト
しています。

実は、PrintJobオブジェクトから取り出せるGraphicsオブジェクトは
Graphics2D型ではないのでキャストできないのです。それで、印刷しよう
としたときにClassCastExceptionが発生したのです。


そこで、DrawingPanelのpaint()メソッドの先頭
--------------------------------------------------------
Graphics2D g2d = (Graphics2D) g;
--------------------------------------------------------
の行を削除し、最後の
--------------------------------------------------------
g2d.drawImage(image, 0, 0, getSize().width, getSize().height, null);
--------------------------------------------------------
の行を
--------------------------------------------------------
g.drawImage(image, 0, 0, getSize().width, getSize().height, null);
--------------------------------------------------------
に書き換えてみてください。

保管して再度DrawingToolFrameをテストし直してみてください。今度は
うまくいきますね。

実はこの(Graphics2D型の)g2dという変数はdrawImage()メソッドを
呼び出すためだけに使っていたのですが、これは(Graphics型の)gの
drawImage()メソッドを呼び出しているだけだったのです。
つまりこのdrawImage()メソッドはGraphics2DのものではなくGraphicsから
継承したものだったのです。したがって、元々Graphics2D型にキャストする
必要はなかったのです。

これで、Exceptionが発生したときの調べ方もわかりましたね。


今回はここまで。

描いた絵をファイルに保管する方法は、次回お話しいたします。


(続く)



以上、今回は
┌───────────────────────────┐
・メニューの組み込み方
・グラフィックスの印刷の仕方
・Exceptionが発生したときの調べ方
└───────────────────────────┘
を学習しました。
では、また。



========================================================
◆ 02.文法解説 [JPanelのpaint()メソッド]
========================================================

[paint()メソッドとpaintComponent()メソッド]

これまで、JPanelをお絵描きのキャンバス代わりに使ってきました。
つまりJPanelを絵を描くときの用紙のように使い、paint()メソッドで
描画しました。

実はJPanel自体はpaint()メソッドを持っておらず、そのスーパークラ
スであるJComponentのpaint()メソッドを継承しているだけです。した
がって、DrawingPanelで実装した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リファレンスなどでメソッドを調べて判断してください。



ちなみに、今回作成したDrawingPanelなどでpaintComponent()を
上書きせずにpaint()を上書きし、しかもスーパークラスのメソッドの
呼び出しを行っていないのは、余計なメソッド呼び出しをしないこと
によってパフォーマンス(性能)を少しでもよくするためです。


(続く)



================================================
◆ 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) 2012 Future Lifestyle Inc. 不許無断複製