SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

作って覚えるJavaプログラミングのススメ

Java入門 (8) - グラフィックの描画

作って覚えるJavaプログラミングのススメ 第8回

コンポーネントの描画の仕組み

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

先のサンプルでは、ウインドウが重なると図形が消えてしまったりする。
先のサンプルでは、ウインドウが重なると図形が消えてしまったりする。

 先のサンプルは、「とりあえず図形を描いてみただけ」というもので、実用に耐えるものではありません。コンポーネントは、それぞれ必要に応じてコンポーネントの表示を最新のものに更新する機能が備わっています。このため、必要に応じて表示を更新するなどしてしまうため、せっかく描いた図形が消えてしまったりしたのです。

 じゃあ、図形が勝手に消えたりせず、常にしっかりと表示されるようにするにはどうするのか。それには、「コンポーネントがどのようにして描画されるか」という仕組みを理解する必要があります。

 AWTとSwingは、描画に関する仕組みが若干違います。Swingは、AWTをベースに拡張していますので、まずはAWTの基本を頭に入れておく必要があります。――AWTでの、コンポーネントの描画は、次のようなメソッドが呼び出されることで行われます。

  • repaint
  • コンポーネントの再描画の必要が生ずると、まずrepaintが呼び出されます。これは、可能な限り早くupdateを呼び出します。
  • update
  • コンポーネント全体の再描画を行います。コンポーネントの背景を塗りつぶした後、各種の表示を行ってからpaintを呼び出します。
  • paint
  • 最後に呼び出されるメソッドです。コンポーネントに独自の描画を行うような場合、このメソッドを利用します。

 repaintupdatepaintという具合にメソッドが順次呼び出されていき、描画が完成するのです。プログラマが何らかの描画を付け加えたいときには、最終段階である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();
    }
}

 packageimportは省略しました。paintを使うと、非常にシンプルになることが分かるでしょう。paitntメソッドは、Graphicsインスタンスを引数に持ちます。これが、そのコンポーネントのグラフィック・コンテキストであるGraphicsインスタンスなのです。この引数で渡されたGraphicsインスタンスを使って描画をすれば、そのままコンポーネントに図形を描くことができます。また、コンポーネントのほうで用意されているものですから、Graphicsdisposeする必要もありません。

 paintの最初に、super.paintというものがありますが、これは、スーパークラスのpaintメソッドを呼び出す処理です。これは、「Swingコンポーネントでpaintを使うときのおまじない」と思ってください(これについては後述します)。

 実際に使ってみると、ウインドウが重なったりリサイズしたりしたときにも、描いた図形は消えないことが分かります。なぜ、paintメソッドを使うと消えないのか? それは、コンポーネントの表示を更新する必要が生じると、自動的に再描画のためのrepaintが呼び出され、repaintupdatapaintとメソッドが実行されるためです。常にこれらのメソッドによってコンポーネントが最新の状態に保たれ、常にpaintによって図形が描画されるため、先ほどのサンプルのように図形が消えることがないのす。

 もし、プログラム内部で必要に応じてコンポーネントを再描画させたくなったときには、★のようにrepaintを呼び出します。これだけでコンポーネントの表示は本来の形に戻ります。

Swingコンポーネントの描画の仕組み

 ここまでは、AWTを含めすべてのコンポーネントで共通する描画の仕組みでした。Swingの場合は、最後のpaintから、次のようにさらに細かくメソッドが呼び出されます。

  • paintComponent
  • コンポーネントの描画を行います。コンポーネントに独自の表示を行う場合、これを利用します。
  • paintBorder
  • コンポーネントのボーダーを描画します。
  • paintChildren
  • コンポーネント内に組み込まれているコンポーネントの描画を行います。

 つまり、Swingでは、コンポーネントの具体的な描画は、paintメソッドから先で行われている、と考えてよいでしょう。実際に、先ほどのサンプルから、☆マークの行を削除して実行してみてください。コンポーネントの描画が正常に行われなくなり、おかしな表示になってしまいます。スーパークラスにpaintが送られなくなったため、paintから呼び出される上記の3メソッドが実行されなくなったためです。

super.paintをしないと、背景にあるイメージなどが消されず、妙な表示になってしまう。
super.paintをしないと、背景にあるイメージなどが消されず、妙な表示になってしまう。

 では、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を組み込んで表示をさせています。

 ここでは、MyPanelpaintCompoentで描画を行っています。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を置くと、何も表示されなくなる。

 直接、JFramepaintComponentを配置しました。これだとウインドウには何も表示されなくなってしまいます。調べてみると分かることですが、このJFrame内のpaintComponentは、まったく呼び出されません。

 Swing用のpaintComponentpaintBorderpaintChildrenといったメソッドは、Swingコンポーネントの土台となるjavax.swing.JComponentというクラスに用意されています。Swing関係のコンポーネントは、すべてこのJComponentを継承しているため、自動的にこの仕組みが機能するようになっています。

 が、Swingのウインドウの土台となるJFrameは、java.awt.Frameクラスという、AWTのウインドウ・クラスを継承して作られています。つまり、JFrameJComponentではないのです。従って、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のみソースコードを掲載しておきます。こんな具合にアルファチャンネルの値を設定することで、色と色の重ね合わせなどが簡単に行えるのです。半透明な図形の描画などを行うときに知っておくと便利です。

次のページ
イメージファイルの描画

この記事は参考になりましたか?

作って覚えるJavaプログラミングのススメ連載記事一覧

もっと読む

この記事の著者

掌田 津耶乃(ショウダ ツヤノ)

三文ライター&三流プログラマ。主にビギナーに向けたプログラミング関連の執筆を中心に活動している。※現在、入門ドキュメントサイト「libro」、カード型学習サイト「CARD.tuyano.com」を公開...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/2543 2008/06/09 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー