コンポーネントの描画の仕組み
さて、先ほどのサンプルに戻ります。実際にサンプルを何度か動かしてみると、図形がうまく表示されたりされなかったりした人もいるのではないでしょうか。また、せっかく表示できても、ウインドウが重なったり、ウインドウサイズを変更したりすると、描いた図形が消えてしまったりしたはずです。

先のサンプルは、「とりあえず図形を描いてみただけ」というもので、実用に耐えるものではありません。コンポーネントは、それぞれ必要に応じてコンポーネントの表示を最新のものに更新する機能が備わっています。このため、必要に応じて表示を更新するなどしてしまうため、せっかく描いた図形が消えてしまったりしたのです。
じゃあ、図形が勝手に消えたりせず、常にしっかりと表示されるようにするにはどうするのか。それには、「コンポーネントがどのようにして描画されるか」という仕組みを理解する必要があります。
AWTとSwingは、描画に関する仕組みが若干違います。Swingは、AWTをベースに拡張していますので、まずはAWTの基本を頭に入れておく必要があります。――AWTでの、コンポーネントの描画は、次のようなメソッドが呼び出されることで行われます。
- repaint
- update
- paint
repaintが呼び出されます。これは、可能な限り早くupdateを呼び出します。paintを呼び出します。 repaint→update→paintという具合にメソッドが順次呼び出されていき、描画が完成するのです。プログラマが何らかの描画を付け加えたいときには、最終段階であるpaintメソッドをオーバーライドして、ここに描画の処理を追加します。では、先ほどのサンプルをちょっと書き換えてみましょう。
public class Sample extends JFrame { public Sample(){ this.setSize(300, 200); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); repaint(); // ★ } public void paint(Graphics g){ super.paint(g); // ☆ g.setColor(Color.RED); g.fillOval(50, 50, 100, 100); } public static void main(String[] args) { new Sample(); } }
package、importは省略しました。paintを使うと、非常にシンプルになることが分かるでしょう。paitntメソッドは、Graphicsインスタンスを引数に持ちます。これが、そのコンポーネントのグラフィック・コンテキストであるGraphicsインスタンスなのです。この引数で渡されたGraphicsインスタンスを使って描画をすれば、そのままコンポーネントに図形を描くことができます。また、コンポーネントのほうで用意されているものですから、Graphicsをdisposeする必要もありません。
paintの最初に、super.paintというものがありますが、これは、スーパークラスのpaintメソッドを呼び出す処理です。これは、「Swingコンポーネントでpaintを使うときのおまじない」と思ってください(これについては後述します)。
実際に使ってみると、ウインドウが重なったりリサイズしたりしたときにも、描いた図形は消えないことが分かります。なぜ、paintメソッドを使うと消えないのか? それは、コンポーネントの表示を更新する必要が生じると、自動的に再描画のためのrepaintが呼び出され、repaint→updata→paintとメソッドが実行されるためです。常にこれらのメソッドによってコンポーネントが最新の状態に保たれ、常にpaintによって図形が描画されるため、先ほどのサンプルのように図形が消えることがないのす。
もし、プログラム内部で必要に応じてコンポーネントを再描画させたくなったときには、★のようにrepaintを呼び出します。これだけでコンポーネントの表示は本来の形に戻ります。
Swingコンポーネントの描画の仕組み
ここまでは、AWTを含めすべてのコンポーネントで共通する描画の仕組みでした。Swingの場合は、最後のpaintから、次のようにさらに細かくメソッドが呼び出されます。
- paintComponent
- paintBorder
- paintChildren
つまり、Swingでは、コンポーネントの具体的な描画は、paintメソッドから先で行われている、と考えてよいでしょう。実際に、先ほどのサンプルから、☆マークの行を削除して実行してみてください。コンポーネントの描画が正常に行われなくなり、おかしな表示になってしまいます。スーパークラスにpaintが送られなくなったため、paintから呼び出される上記の3メソッドが実行されなくなったためです。

では、Swingコンポーネントの正しい書き方にサンプルを修正してみましょう。
package codezine.java; import java.awt.*; import javax.swing.*; public class Sample extends JFrame { public Sample(){ this.setSize(300, 200); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); MyPanel panel = new MyPanel(); this.add(panel,BorderLayout.CENTER); this.setVisible(true); } public static void main(String[] args) { new Sample(); } } class MyPanel extends JPanel { public void paintComponent(Graphics g){ g.setColor(Color.RED); g.fillOval(50, 50, 100, 100); } }
ここでは、JPanelを継承してMyPanelというクラスを作り、ここにpaintComponentを用意しました。そして、Sampleクラス(JFrame)では、このMyPanelを組み込んで表示をさせています。
ここでは、MyPanelのpaintCompoentで描画を行っています。Swingでは、ウインドウに何かの描画を行うときには、このように「JPanel」という無色透明なコンポーネントを継承して描画のためのクラスを作り、そこにpaintComponentを用意するのが一般的なやり方です。
JFrameとpaintComponent
ここで、「なんでJFrameに直接用意しないんだ?」と疑問に思った人。実をいえば、このpaintComponentなどのSwing専用描画メソッドは、ウインドウ関係すなわちJFrameでは呼び出されないのです。実際に試してみましょう。
public class Sample extends JFrame { public Sample(){ this.setSize(300, 200); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public void paintComponent(Graphics g){ g.setColor(Color.RED); g.fillOval(50, 50, 100, 100); } public static void main(String[] args) { new Sample(); } }

直接、JFrameにpaintComponentを配置しました。これだとウインドウには何も表示されなくなってしまいます。調べてみると分かることですが、このJFrame内のpaintComponentは、まったく呼び出されません。
Swing用のpaintComponent、paintBorder、paintChildrenといったメソッドは、Swingコンポーネントの土台となるjavax.swing.JComponentというクラスに用意されています。Swing関係のコンポーネントは、すべてこのJComponentを継承しているため、自動的にこの仕組みが機能するようになっています。
が、Swingのウインドウの土台となるJFrameは、java.awt.Frameクラスという、AWTのウインドウ・クラスを継承して作られています。つまり、JFrameはJComponentではないのです。従って、JFrameに直接描画処理を埋め込むときには、先のようにpaintメソッドをオーバーライドする必要があります。
このように、Swingでは、ウインドウであるJFrameとコンポーネント類で描画の仕組みが微妙に異なっている、という点は知っておきましょう。
Colorの利用
先ほど、色を利用するのにColorクラスを使いました。このColorは、クラスフィールドとして次のようなものが用意されています。
WHITE, LIGHT_GRAY, GRAY, DARK_GRAY, BLACK, RED, PINK, ORANGE, YELLOW, GREEN, MAGENTA, CYAN, BLUE
それぞれがどんな色かは、名前を見れば大体分かりますね。これらの色は、そのままColorクラスからフィールドを指定して利用できます。また、大文字と小文字の両方の名前が用意されており、例えば赤ならば「RED」でも「red」でもOKです。
それ以外の色は、次のようにnewでRGBを細かく引数指定することで、新たにインスタンスを作成できます。
new Color(R, G, B) new Color(R, G, B, A)
R,G,Bは、それぞれの原色の輝度を0~255の範囲内のint値で指定します。またAは、αチャンネルの輝度で、これも0~255のint値となります。アルファチャンネルは、通常、その色の透過度として用いられます。これを利用すると、面白い効果が得られます。
class MyPanel extends JPanel { public void paintComponent(Graphics g){ for(int i = 0;i < 255;i += 20){ g.setColor(new Color(0,0,255,i)); g.fillOval(i, i, 100, 100); g.setColor(new Color(255,0,0,i)); g.fillOval(255 - i, i, 100, 100); } } }

先ほど作成したMyPanelクラスを使ったサンプルを利用しているため、修正したMyPanelのみソースコードを掲載しておきます。こんな具合にアルファチャンネルの値を設定することで、色と色の重ね合わせなどが簡単に行えるのです。半透明な図形の描画などを行うときに知っておくと便利です。
