簡単なお絵描きプログラムを作ってイベントの扱いを練習します.
import java.awt.*; public class DrawFrame extends Frame { public DrawFrame(String _title) { super(_title); setSize(400,300); setVisible(true); } public static void main(String argv[]) { new DrawFrame("お絵描きプログラム"); } }
import java.awt.*; import java.awt.event.*; public class DrawFrame extends Frame implements WindowListener{ public DrawFrame(String _title) { super(_title); addWindowListener(this);//自分自身を登録する setSize(400,300); setVisible(true); } //////window listenerに必要とされるメソッドを実装//// public void windowActivated(WindowEvent e) { } public void windowClosed(WindowEvent e) { } public void windowClosing(WindowEvent e) {System.exit(0);/*プログラム終了*/} public void windowDeactivated(WindowEvent e) { } public void windowDeiconified(WindowEvent e) { } public void windowIconified(WindowEvent e) { } public void windowOpened(WindowEvent e) { } public static void main(String argv[]) { new DrawFrame("お絵描きプログラム"); } }
Mouse Motion Listenerとして自分を登録すれば,マウスの移動に伴うイベントに対応できます. また自分自身のgetGraphics()を呼び出すと,自分のウィンドウに線を描くなどの機能を提供する Grahicsインスタンスを得ることが出来ます.
import java.awt.*; import java.awt.event.*; public class DrawFrame extends Frame implements MouseMotionListener{ int lastX=0, lastY=0; public DrawFrame(String _title) { super(_title); addMouseMotionListener(this);//自分自身を登録する setSize(400,300); setVisible(true); } //////Mouse Motion Listenerに必要とされるメソッドを実装//// public void mouseMoved(MouseEvent e) {} public void mouseDragged(MouseEvent e) { Graphics g = getGraphics(); g.drawLine(lastX, lastY,e.getX(), e.getY()); lastX=e.getX(); lastY=e.getY(); } public static void main(String argv[]) { new DrawFrame("お絵描きプログラム"); } }
上の例では,ドラッグの最後の位置が次回の開始点になっているので,一筆書きになってしまています.これを回避するには,マウスボタンが押された地点を次回の開始点,lastX, lastYにすれば良いです.そのためには,自分自身を,Mouse Listenerにすれば良いです.Mouse Listenerがサポートしなければならないメソッドは以下の通りです.ここのmousePressedでlastX, lastYを設定します.
public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) { lastX=e.getX(); lastY=e.getY(); } public void mouseReleased(MouseEvent e) {}
Graphicsクラスを拡張した(継承した)Graphics2Dクラスを使うと太い線が簡単に描けるようです. Graphics2DクラスはGraphicsクラスのサブクラスなので, Graphicsクラスのインスタンスをキャストしてしまえば、Graphics2Dクラスにできます.
import java.awt.*; import java.awt.event.*; public class DrawFrame extends Frame implements MouseMotionListener{ int lastX=0, lastY=0; public DrawFrame(String _title) { super(_title); addMouseMotionListener(this);//自分自身を登録する setSize(400,300); setVisible(true); } //////Mouse Motion Listenerに必要とされるメソッドを実装//// public void mouseMoved(MouseEvent e) {} public void mouseDragged(MouseEvent e) { Graphics2D g2 = (Graphics2D)getGraphics(); //太さ20の線を描く.線の両端は丸くする. g2.setStroke(new BasicStroke(20.0f ,BasicStroke.CAP_ROUND,BasicStroke.JOIN_MITER)); g2.drawLine(lastX, lastY,e.getX(), e.getY()); lastX=e.getX(); lastY=e.getY(); } public static void main(String argv[]) { new DrawFrame("お絵描きプログラム"); } }
上の例では,ウィンドウの面に直接描画していました.いわば描きっぱなしの状況でした.そこで,ウィンドウのサイズを変更したり,ウィンドウをドッグにしまったりすると描いた内容が消えてしまいます.そのほか,描いた内容をセーブするために保持しておきたい,描いている途中の作業を見せないようにしたいなどの理由で,バッファを用意してそちらに描画して,あとで表示するという手法がとられます.バッファを使うには以下のように,バッファに描いたあとで,paintというメソッドでこれを表示します.paintは,ウィンドウのサイズ変更などでも呼び出されるので,描いた内容が消えなくなります.
import java.awt.*; import java.awt.event.*; public class DrawFrame extends Frame implements MouseMotionListener{ int lastX=0, lastY=0; Image bufferImage; Graphics bufferGraphics; public DrawFrame(String _title) { super(_title); addMouseMotionListener(this);//自分自身を登録する setSize(400,300); setVisible(true); //バッファ用のImageとGraphicsを用意しておく bufferImage = createImage(400,300); bufferGraphics=bufferImage.getGraphics(); } //////Mouse Motion Listenerに必要とされるメソッドを実装//// public void mouseMoved(MouseEvent e) {} public void mouseDragged(MouseEvent e) { //バッファに描く bufferGraphics.drawLine(lastX, lastY,e.getX(), e.getY()); lastX=e.getX(); lastY=e.getY(); repaint();//再描画するためpaint()を呼び出す } public void paint(Graphics g) { if(null!=bufferImage) g.drawImage(bufferImage,0,0,400,300,null); super.paint(g); //他に描画するものがあるかもしれないので親を呼んでおく } public static void main(String argv[]) { new DrawFrame("お絵描きプログラム"); } }
教科書ではメニューアイテムのインスタンスを保持して実現していますが、 以下のように簡単に作ることもできます。
import java.awt.*; import java.awt.event.*; public class DrawFrame extends Frame implements ActionListener{ MenuBar mb; Menu mColor; Color currentColor = Color.black; //color of the pen public DrawFrame(String _title) { super(_title); mb = new MenuBar(); mb.add(mColor = new Menu("Color")); mColor.add("Black"); mColor.add("Red"); mColor.add("Green"); mColor.add("Blue"); mColor.add("Yellow"); mColor.add("White"); mColor.addActionListener(this);//自分自身を登録する setMenuBar(mb);//menubarを登録する setSize(400,300); setVisible(true); } //listener methods public void actionPerformed(ActionEvent e) { if(e.getActionCommand().equals("Black")) currentColor=Color.black; else if(e.getActionCommand().equals("Red")) currentColor=Color.red; else if(e.getActionCommand().equals("Green")) currentColor=Color.green; else if(e.getActionCommand().equals("Blue")) currentColor=Color.blue; else if(e.getActionCommand().equals("Yellow")) currentColor=Color.yellow; else if(e.getActionCommand().equals("White")) currentColor=Color.white; } public static void main(String argv[]) { new DrawFrame("お絵描きプログラム"); } }
Toolkitというクラスのインスタンスを使うと,JPEGやGIFファイルからImageインスタンスを作ることができます.これをpaintで描画すれば画像ファイルを表示できます.
import java.awt.*; public class DrawFrame extends Frame { Image pictureImage; public DrawFrame(String _title) { super(_title); setSize(400,300); setVisible(true); //画像ファイルからImageインスタンスを作る Toolkit toolkit = Toolkit.getDefaultToolkit(); pictureImage = toolkit.getImage("himawari.jpg"); //Imageインスタンスを用意して,用意できたら自分(this)のpaintを呼ぶ toolkit.prepareImage(pictureImage,-1,-1,this); } public void paint(Graphics g) { if(null!= pictureImage) g.drawImage(pictureImage,0,0,400,300,null); super.paint(g); //他に描画するものがあるかもしれないので親を呼んでおく } public static void main(String argv[]) { new DrawFrame("画像ファイル表示"); } }
このプログラムの中の,
toolkit.prepareImage(pictureImage,-1,-1,this);
の行で,ファイルからイメージを読み込みイメージインスタンスを用意しています.用意ができると,自分自身の(thisの)paintメソッドを呼ぶように指定しました.実際には,イメージインスタンスの一部が用意されると順次その部分だけ再描画するようpaintメソッドが呼ばれます.paintメソッドの中にSystem.out.printlnなどでメッセージを出すようにするとその様子が分かるかと思います.
public void paint(Graphics g) { System.out.println("painting..."); if(null!=pictureImage) g.drawImage(pictureImage,0,0,400,300,null); super.paint(g); //他に描画するものがあるかもしれないので親を呼んでおく }
とすると,以下のようになります.
siio$ java DrawFrame painting... painting... painting... painting... painting... painting... painting... painting... painting... painting... painting... painting... painting... painting...
プログラムによっては,イメージインスタンスが完全に用意されたタイミングを知りたい場合があります.実はprepareImageはpaintを直接呼んでいるのではなく,imageUpdateというメソッドを呼んでいて,それがpaintを呼んでいます.ということでimageUpdateをオーバライドして処理を横取りすればタイミングを知ることができます.たとえば以下のようなメソッドを追加すると,全部の画像が用意されたときだけ,paintを呼ぶことができます.
public boolean imageUpdate(Image img , int flg , int x , int y , int w , int h) { if ((flg & ALLBITS) == 0) return true; else { repaint(); return false; } }
上のようにして読み込んだイメージインスタンスを,先の描画バッファとして使っているイメージに読み込むことで,写真にお絵描きしたり,写真をコラージュのようにはめ込んだりすることもできます.例えば,写真にお絵描きするには,以下のようにします.
public boolean imageUpdate(Image img , int flg , int x , int y , int w , int h) { if ((flg & ALLBITS) == 0) return true; else { bufferGraphics.drawImage(pictureImage,0,0,400,300,null); repaint(); return false; } }
ここでは,イメージが用意できたところで,バッファイメージ用のグラフィックスインスタンスbufferGraphicsに書き込んでいます.上のサンプルのように,マウスがドラッグされたときにbufferGraphicsの線を引く(drawLine)などのメソッドを呼べば,写真に絵を描くことができます.