「グラフィックを描く」
今回は、Javaの命令(メソッド)を使って、画面にグラフィックを描かせる方法について説明していきましょう。JavaのAWTに用意されている部品類(ボタンとかそういうもの。一般にコンポーネントと呼んだりします)は、自分自身の中に様々な仕組みをもっています。例えば、何かを描画するようなシステムを用意した部品もちゃんと用意されているのですね。その代表的なものが「Canvas」というものです。
Canvasは、それ自体はただの白い四角い部品でしかありません。けれど、その中にある描画用のメソッドを利用することで、自由にグラフィックを表示させることができるようになっています。ですから、Canvasを継承して描画用のメソッドを持ったクラスを用意し、それを組み込んで表示させれば、グラフィックをJavaで描かせることができるはずです。
import java.awt.*; import java.awt.event.*; public class Test9 extends Frame { MyCanvas mc; public Test9() { super(); setTitle("Hello"); setSize(300,250); setLayout(null); mc = new MyCanvas(); mc.setBounds(25,25,250,200); this.add(mc); } public static void main (String args []) { new Test9().show(); } class MyCanvas extends Canvas { public void paint(Graphics g) { g.drawOval(50,50,100,75); g.drawRect(100,100,100,75); } } }
これが実行したところです。見ればわかるように、円と四角を描いてみました。まあ、単純ですが、基本を覚えるためのサンプルってことで。
では、ソースを見てみましょう。――まず、Test9クラスの中で行なっているのは、コンストラクタで「MyCanvas」というクラスのインスタンスを作り、これをaddするという作業だけです。まあ、この部分は今まで何度もやってきましたからわかりますね。
このMyCanvasが、描画用のクラスです。このクラスは、Test9クラスの中に内部クラスとして定義されていますね。ちょと見てみましょう。
class MyCanvas extends Canvas { public void paint(Graphics g) { g.drawOval(50,50,100,75); g.drawRect(100,100,100,75); } }
こんな感じのものです。MyCanvasは、CanvasというAWTのクラスを継承して作ります。ですから、extends Canvasとなっているわけですね。
その中には、1つだけメソッドが用意されていますね。これが「描画用メソッド」です。このpaintというメソッドは、このコンポーネントが画面に表示したり再描画したりする必要が生じると、コンポーネント自体の様々な表示の処理を行なったとに、最後に呼び出されるようになっています。従って、このpaintメソッドに描画の処理を書いておけば、それはどんな場合でも常にコンポーネントに表示されるようになるのです。
では、このメソッドの中でどうやって描画を行なわせるのか。それが「g.drawOval〜」「g.drawRect〜」という2行のメソッドです。これらは、いずれも「g」ってもののメソッドのようですね? このgとは何でしょう?
メソッドのパラメータ部分を見ると、「paint(Graphics g)」とあります。つまりこのgは、Graphicsというクラスのインスタンスだったわけです。そしてこのGraphicsこそが、AWTの描画一切を引き受けるものだったのです。
Graphicsは、「描画用の様々な機能を全てまとめ、それらを呼び出すことで実際に画面に表示が行なわれるようにしたもの」です。まあ、わかりやすくいえば「描画担当クラス」ってことですね。AWTのコンポーネントのクラスには、全てこのGraphicsが組み込まれていて、描画を担当しています。そう、Canvasだけでなくて、あらゆる部品の「描画の素」なのです。
コンポーネントに何かを表示させるには、そのコンポーネントに組み込まれているGraphicsを取り出し、その中の描画用メソッドを呼び出してやります。そうすると、そのコンポーネントに実行した図形などが表示される、という具合になっています。
ということは、このpaintメソッドで渡されるgっていうのは…? そう、このMyCanvasに設定されているGraphicsのインスタンスがパラメータとして渡されていた、というわけです。今回使った「drawOval」「drawRect」は、それぞれ円と四角を描くメソッドです。こうやって渡されたgインスタンスのメソッドを呼び出すことで、描画を行えるようになっていたのですね。
描画用のメソッドというのは、他にもたくさんあります。とりあえず主なものを整理しておきましょう。これらを一通り覚えれば、簡単な図形なら描けるようになりますよ。
・枠線だけの四角形を描く
drawRect(横位置, 縦位置, 横幅, 縦幅)
・枠線だけの円を描く
drawOval(横位置, 縦位置, 横幅, 縦幅)
・塗りつぶした四角形を描く
fillRect(横位置, 縦位置, 横幅, 縦幅)
・塗りつぶした円を描く
fillOval(横位置, 縦位置, 横幅, 縦幅)
・2点を結ぶ直線を描く
drawLine(横位置1, 縦位置1, 横位置2, 縦位置2)
・テキストをグラフィックとして描く
drawString(テキスト, 横位置, 縦位置)
さて、図形は描けるようになっても、モノクロだけじゃちょっとつまらないですね。ということで次は色を変えてみましょう。またテキストを描く時に必要なフォントもあわせて変更してみることにします。――描画関係だけですから、 先ほど作ったMyCanvasクラスだけ修正すればよいですね。
class MyCanvas extends Canvas { public void paint(Graphics g) { Color c = new Color(255,0,0); g.setColor(c); Font f = new Font("Dialog",Font.BOLD,24); g.setFont(f); g.drawString("Hello World.", 50,50); } }
これがその実行例です。大きな文字で赤く「Hello World.」と表示されているでしょう? まあ、ここでは文字だけですが、もちろん同じやり方で図形の色も変更できます。――では、コードを見てみましょう。paintメソッドでは、まずカラーを設定する処理がされています。
Color c = new Color(255,0,0);
g.setColor(c);
これがそうですね。色というのは、「Color」クラスのインスタンスとしてAWTでは扱われています。このインスタンスを作って、Graphicsの描画色を変更すれば、描く図形の色が変わるのです。new Colorでは、3つのパラメータがありますね? これは、それぞれ赤・緑・青の3色の輝度を指定します。これは0〜255の整数で指定するのが一般的です。Colorはこの他にもいろんな作り方があるのですが、まずこの「RGBを指定して作る」という方法を覚えておけばよいでしょう。
そしてColorインスタンスができたら、これを「setColor」というメソッドでgに設定します。こうすると、以後、描画用のメソッドを実行すると、それらは全て設定した色で描かれるようになるのです。
Colorインスタンスは、実はnew Colorする他にも設定方法があります。このColorクラスの中には、よく使われる色のインスタンスがあらかじめ用意されているんです。ですから、わざわざColorインスタンスを作らなくても、それらを利用することで色を変更することができます。
g.setColor(Color.red);
例えばこんな具合にすれば、gの色を赤に変更できるんです。「red」「blue」「green」「black」「white」など、けっこういろんな色がColorには用意されています。どんな色が使えるか、各自で実験してみると面白いですよ。
さて、次にフォントに進みましょう。フォントも基本的な考え方はColorと同じです。フォントを示すクラスのインスタンスを作成し、それをgインスタンスに設定すれば、以後そのフォントでテキストのフォントが描かれるようになるというわけです。
Font f = new Font("Dialog",Font.BOLD,24);
g.setFont(f);
これがその部分です。フォントを示すクラスは「Font」というものです。このインスタンスは、やっぱり3つのパラメータからなります。1つ目がフォント名、2つ目がスタイルの設定値、3つ目がサイズを示す整数です。
ただしFontはColorよりもちょっとだけ注意が必要です。まずフォント名。これは「MS明朝」とか「Osaka」とか「ヒラギノ角ゴシックPro」なんて設定してもダメです。Javaでは「論理フォント名」というものを設定しないといけません。
Javaは、プラットフォーム(要するにOS)に依存しないプログラム、どんなOSでも動くプログラムの作成を目指して設計されています。ですから、特定のOSにしか用意されていないフォントは使えないのです。そこで「論理フォント名」というものを用意することにしました。論理フォント名というのは、Javaの中だけで通用する架空(?)のフォント名と考えて下さい。これを設定すると、実行時にJavaの仮想マシンが「今はWindowsで実行しているからこのフォントだな」というように判断して自動的にフォントを選んでくれる、というわけです。
この論理フォント名としては「Dialog」「Serif」「SansSerif」「Monospaced」といったものがあります。それぞれどんなフォントになるか試してみると面白いでしょう。
もう1つ注意しておきたいのはスタイルの設定です。これは整数の値で指定するんですが、通常はFontクラスに用意されている定数を使います。「Plain」「Bold」「Italic」というのがそれで、「Font.Italic」なんて具合に使って指定してやればいいわけです。
そしてフォントのインスタンスができたら、それを「setFont」で設定します。これでフォントの変更が完了です。あとは、drawStringでテキストを描画すれば、指定した色とフォントで文字が描かれるというわけです。
このpaintメソッドというのは、あらかじめ用意したメソッドを実行して表示させるというものです。ということは、ユーザーが何か操作してそれに応じてその場で何か描く、というようなことには向かないことになります。こういう「ユーザーの操作と連係した描画」はどうすればいいのでしょう?
要するに、この部品のGraphicsインスタンスが取りだせれば描画は行えるわけです。そしてユーザーが操作した時のイベント処理は、前回に勉強しました。ということは、これらを組み合わせれば、「クリックして描かせる」ということができそうじゃないですか。
import java.awt.*; import java.awt.event.*; public class Test9 extends Frame { Canvas mc; public Test9() { super(); setTitle("Hello"); setSize(300,250); setLayout(null); mc = new Canvas(); mc.setBounds(25,25,250,200); this.add(mc); mc.addMouseListener(new Clicked()); } public static void main (String args []) { new Test9().show(); } class Clicked extends MouseAdapter { public void mouseClicked(MouseEvent ev){ Graphics gr = mc.getGraphics(); int x = ev.getX(); int y = ev.getY(); int r = (int)(Math.random() * 255); int g = (int)(Math.random() * 255); int b = (int)(Math.random() * 255); gr.setColor(new Color(r,g,b)); gr.fillOval(x - 10,y - 10,20,20); gr.dispose(); } } }
これがそのサンプルです。マウスでクリックすると、ランダムな色で円を描いていきます。今回はCanavs自身に描画機能を持たせるのではなく、イベントリスナーの中から描画を行なうことになります。ということで、Test9全体を書き換える感じになりました。
今回使うのは「マウスリスナー」というリスナークラスです。これは、マウスをクリックしたりした時のイベント処理を行なうためのものです。このマウスリスナー、「MouseListener」というものをimplementsして作るんですが、実は他にも作り方があります。「MouseAdapter」というものをextendsして作ることもできるのです。今回はこちらを使っています。
なんで2つもあるのか?ということを説明すると長くなってしまいます。実をいえばMouseListenerにはたくさんのイベントに対応したメソッドが用意されているんです。「マウスを押し下げた」「離した」「クリックした」というような具合に、それぞれの動作に応じて細かくメソッドが分かれているんですね。で、implementsでインターフェイスクラスを組み込んだ場合は、用意されているメソッド全部を書かないといけないって決まりになってます。
今回は「クリックした時」の1つだけしか使いませんから、全部書くのは面倒です。そこでこういうときのために、あらかじめ全部のメソッドを書いてある「MouseAdapter」というクラスを最初から用意しておいた、というわけなのです。――まあ、このへんはちょっとわかりにくいかも知れませんから、「2つあって、MouseListenerのときは全部のメソッドを書かないといけないのでMouseAdapterのほうが便利」ということだけ覚えておきましょう。
さて、このMouseAdapterをextendsして作ったのが「Clicked」というリスナークラスです。これのインスタンスを作り、addMouseListenerでCanvasインスタンスに組み込んでおけば、Canvasをクリックするとイベント処理が行なわれるようになりますね。
で、肝心のClickedクラスを見てみましょう。ここでは「mouseClicked」ってメソッドが用意されてます。これがマウスをクリックした時に呼び出されるもの何ですね。では、メソッド内を見てみましょう。
public void mouseClicked(MouseEvent ev){ Graphics gr = mc.getGraphics(); int x = ev.getX(); int y = ev.getY(); int r = (int)(Math.random() * 255); int g = (int)(Math.random() * 255); int b = (int)(Math.random() * 255); gr.setColor(new Color(r,g,b)); gr.fillOval(x - 10,y - 10,20,20); gr.dispose(); }
うわ、ちょっと難しそうですねえ。順番に見ていくことにしましょう。まず、最初の「getGraphics」です。これは、そのコンポーネントに組み込まれているGraphicsインスタンスを返すメソッドです。これで、mc(Canvasインスタンス)のGraphicsを取り出しているんですね。
そしてその次にある「getX」「getY」というのが、イベントが発生した時のマウスの縦横の位置を取り出しているところです。このmouseClickedメソッドではMouseEventというイベントのインスタンスがパラメータとして渡されていますが、これはgetXとgetYでマウスの位置を調べることができるようになってるんですね。
その次にある3行は、3つの乱数の値を取り出している部分です。「Math.random」っていうメソッドがあるのがわかりますか? これが乱数を返すメソッドなんですね。Mathっていうのは、数値関数を集めた、ちょっと特別なクラスなんです。この中にある関数類は、こんな具合に「Math.○○」として呼び出すことができます。
問題は、「このrandomで返すのは0〜1の間の乱数である」ということなんです。つまり、実数(double)の値なんですね。でもColorで設定するRGBの値は整数(int)です。Javaでは、タイプが違う値はそのままじゃ使えません。
そこで、「実数の値を整数として取り出す」ということが必要になります。それが、その前についている (int) ってやつです。これは「キャスト(型変換)」ってもので、ある値を別のタイプに変換する時に使います。例えば、「(double)x」とかすると、xの値をdouble(つまり実数)として取り出したりできるんですね。今回は(int)ですが、これは整数として取り出します。少数以下は切り捨てられちゃうんです。
randomは0〜1の乱数ですから、これを255倍すれば、0〜255の(実数の)乱数が得られるわけです。で、これを(int)で整数にしておさめてやれば、0〜255の整数の乱数が得られる、というわけです。わかりました?
後は、得られた乱数を使ってnew Colorし、それから得られたマウスの位置の値を使ってfillOvalしているだけです。――おっと、最後に1つ重要なのを忘れてた。「dispose」です。
これは、「Graphicsを消去する」メソッドです。――実をいえば、getGrahicsで得られたのは、このコンポーネントのGraphicsインスタンスじゃありません。コンポーネントのGraphicsインスタンス「のコピー」なんです。つまり、よけいに1個、Graphicsを作っちゃってたんですね。
Javaは、基本的にインスタンスは作りっぱなしでいいんですが、AWTのGraphicsだけは、使い終わったら「dispose」というので破棄するのがマナーになってます。まあ、そのまま放っておいても、プログラム終了すればちゃんと消えるんであんまり心配はないんですが…。なんでGraphicsだけそうしないといけないのかというのはちょっと難しくなるので、「こうするのがマナーだ」ということだけ覚えておいて下さい。
これは、getGraphicsで取り出したものだけです。paintのパラメータで渡されたgインスタンスなんかはそのままにしておいて大丈夫です。
…というわけで、なんかだいぶ「オブジェクト指向プログラミング」って感じになってきましたね(笑)。よりオブジェクティブになるにつれ、なんだかわけわかんないという状態になってきます。とりあえず、ここまでのソースコードを実際に打ち込んで動かしてみて、それから自分なりにいろいろとカスタマイズしていじってみてください。何度か実際に動かしてみれば、Graphicsという抽象的で目に見えないオブジェクトがどういう働きをしているか、なんとなくイメージがつかめてくると思いますから。