Java入門|マウス操作で描画する

描画アプリの第一歩。マウスで押した場所に、気のしるしを描いて画面を動かそう。

これまでのGUIアプリケーションでは、ウィンドウを表示したり、ボタンやラベルを置いたり、画像を読み込んで表示したりしてきました。
ここまででも画面はかなり豊かになってきましたが、さらに一歩進むと、ユーザーの操作に合わせて画面の内容そのものを変えることができるようになります。

ドラゴンボールの世界でたとえるなら、修行部屋のパネルに最初から表示された内容を見るだけではなく、悟空が指で押した場所に気のマークが現れたり、修行ポイントがその場で記録されたりするような状態です。
つまり、画面がただ見せるだけのものではなく、操作に応じてその場で描き変わるものになっていくわけです。

ここでは、マウスで押した位置を取得し、その場所に図形を描画する基本の仕組みを見ていきます。
イベント処理と描画処理がどう結びつくのか、そして repaint() がどんな役割を持っているのかを、ドラゴンボールの世界観に置きかえながらやさしく整理していきます。

マウス操作で描画するとはどういうことか

マウス操作で描画するというのは、ユーザーが画面をクリックした位置を調べ、その座標を使って図形や画像を描くことです。

流れを簡単にまとめると、こうなります。

手順内容
1マウスを押したことをイベントとして受け取る
2押した位置の座標を調べる
3その座標を変数に保存する
4repaint() で再描画を指示する
5paint() が呼ばれ、その位置に図形が描かれる

ドラゴンボール風にいうと、修行場の床を指で押した場所に、特訓ポイントのしるしを出す流れです。
押した場所の情報を覚えておいて、その位置にマークを描き直すことで、画面が変化します。

サンプルプログラム:マウスで描画する例

今回は、マウスで押した場所にオレンジ色の気の玉を描画するプログラムです。

ファイル名:Sample6.java

import java.awt.*;
import java.awt.event.*;

public class Sample6 extends Frame
{
    int x = 30;
    int y = 30;

    public static void main(String[] args)
    {
        Sample6 sm = new Sample6();
    }

    public Sample6()
    {
        super("気のしるしを描く");

        addWindowListener(new SampleWindowListener());
        addMouseListener(new SampleMouseAdapter());

        setSize(420, 320);
        setVisible(true);
    }

    public void paint(Graphics g)
    {
        // 見出しを描画する
        g.setColor(Color.black);
        g.drawString("画面を押すと、その場所に気のしるしが現れます", 60, 60);

        // 気のしるしを描画する
        g.setColor(Color.orange);
        g.fillOval(x, y, 20, 20);
    }

    class SampleWindowListener extends WindowAdapter
    {
        public void windowClosing(WindowEvent e)
        {
            System.exit(0);
        }
    }

    class SampleMouseAdapter extends MouseAdapter
    {
        public void mousePressed(MouseEvent e)
        {
            // 押した位置を保存する
            x = e.getX();
            y = e.getY();

            // 画面を再描画する
            repaint();
        }
    }
}

このプログラムでできること

このプログラムを実行すると、「気のしるしを描く」というタイトルのウィンドウが表示されます。
ウィンドウの中には説明文が表示されていて、マウスで画面を押すと、その場所にオレンジ色の丸が現れます。

つまり、このプログラムでは次のことを行っています。

できること内容
画面を表示するFrame を使ってウィンドウを開く
マウス操作を受け取るフレームに MouseAdapter を登録する
押した位置を取得するMouseEvent から座標を取り出す
図形を描くpaint() で fillOval() を使う
描き直すrepaint() で再描画する

今回のソースはボタンではなくフレーム

前回までのイベント処理では、ボタンをソースにすることが多くありました。
今回は少し違って、ソースはフレームそのものです。

つまり、画面全体を対象にしてマウスイベントを受け取っています。

addMouseListener(new SampleMouseAdapter());

この addMouseListener() は Frame に対して呼び出しているので、ウィンドウ全体でマウス操作を受け取る形になります。

ドラゴンボール風にいうと、修行開始ボタンだけに反応するのではなく、修行場の床全体に「押された位置を記録する仕組み」を付けているようなものです。

paint() で図形を描く

画面に図形を描いているのは paint() メソッドです。

public void paint(Graphics g)
{
    g.setColor(Color.black);
    g.drawString("画面を押すと、その場所に気のしるしが現れます", 60, 60);

    g.setColor(Color.orange);
    g.fillOval(x, y, 20, 20);
}

ここでは2つのことをしています。

処理内容
drawString()説明文を表示する
fillOval()丸い図形を塗りつぶして描く

fillOval(x, y, 20, 20) は、x と y の位置に、幅20、高さ20の楕円を描く命令です。
今回は幅と高さが同じなので、見た目としては丸に近い形になります。

ドラゴンボール風にいえば、これはクリックした地点に小さな気の玉を出現させているような表現です。

x と y の変数は何を表しているか

このプログラムでは、x と y という2つの変数を用意しています。

int x = 30;
int y = 30;

これらは、図形を描く位置を表しています。

変数意味
x横方向の位置
y縦方向の位置

最初は 30, 30 の位置に描くようにしてありますが、マウスを押すと、その位置の座標で上書きされます。
つまり、どこに図形を描くかを記録しておくための変数です。

mousePressed() で押した位置を取得する

マウスで押した位置を受け取るのは mousePressed() です。

public void mousePressed(MouseEvent e)
{
    x = e.getX();
    y = e.getY();

    repaint();
}

ここでやっていることは、とても大切です。

座標を取得する

x = e.getX();
y = e.getY();

MouseEvent から getX() と getY() を呼び出して、押した位置の座標を取り出しています。

再描画を指示する

repaint();

座標を変えただけでは、まだ画面の見た目は変わりません。
そこで repaint() を呼び出して、画面を描き直すように指示しています。

MouseEvent から何が分かるのか

MouseEvent には、マウス操作に関する情報が入っています。
今回使っているのは座標ですが、それが特に重要です。

メソッド分かること
getX()押した場所の横座標
getY()押した場所の縦座標

これによって、どこを押したのかが分かります。
GUIでは、こうした座標情報を使って図形を描いたり、反応する位置を決めたりします。

ドラゴンボール風にいうと、修行場のどの地点に印を付けるべきかを、装置が正確に読み取っている状態です。

repaint() の役割

このテーマで特に大切なのが repaint() です。

repaint();

repaint() は、画面をもう一度描き直してほしいときに呼び出すメソッドです。
これを呼ぶことで、Javaは paint() を再び実行し、最新の状態で画面を描き直します。

つまり今回の流れはこうです。

順番起こること
1マウスを押す
2x と y が新しい座標に変わる
3repaint() を呼ぶ
4paint() が呼ばれる
5新しい位置に丸が描かれる

ここがとても重要です。
座標を変えるだけでは足りず、描き直しの合図として repaint() が必要です。

ドラゴンボール風にいうと、修行場のしるしの位置情報を更新したあと、表示パネルに「最新の位置で描き直して」と命令しているようなものです。

paint() と repaint() の関係

paint() と repaint() は混同しやすいので、役割を分けて整理すると分かりやすいです。

メソッド役割
paint()実際に画面へ描画する
repaint()画面を描き直してほしいと依頼する

repaint() を呼ぶと、その結果として paint() が実行されます。
つまり repaint() 自体が絵を描くのではなく、paint() を呼ぶきっかけを作るのです。

fillOval() で丸を描く

今回の図形描画の中心は fillOval() です。

g.fillOval(x, y, 20, 20);

このメソッドは、塗りつぶされた楕円を描きます。
引数の意味は次の通りです。

引数意味
x描画開始位置の横座標
y描画開始位置の縦座標
20
20高さ

幅と高さを同じにすると丸に近い見た目になります。
今回は気のしるしとして使っているので、小さな丸を表示する形にしています。

setColor() で色を変える

図形の色は setColor() で決めています。

g.setColor(Color.orange);

このあとに描く図形は、オレンジ色で描かれます。
今回はドラゴンボールの気の玉らしい雰囲気を出すために Color.orange を使っています。

説明文は黒色にしたいので、その前に

g.setColor(Color.black);

も使っています。

つまり、paint() の中では

  • まず黒で文字を描く
  • 次にオレンジで丸を描く

という順番になっています。

このプログラムの流れを整理する

全体の流れを順番に見ると、かなり分かりやすくなります。

1. 画面を用意する

Frame を継承してウィンドウを作ります。

2. マウスリスナを登録する

addMouseListener(new SampleMouseAdapter());

フレーム全体でマウスイベントを受け取れるようにします。

3. マウスを押した位置を取得する

x = e.getX();
y = e.getY();

押した位置を変数に保存します。

4. repaint() を呼ぶ

repaint();

最新の位置で画面を描き直すように指示します。

5. paint() で図形を描く

g.fillOval(x, y, 20, 20);

保存された座標を使って丸を描きます。

この図が示していること

この図では、フレームがソースとなってマウスイベントを受け取り、mousePressed() でクリック位置の座標を取得し、そのあと repaint() によって paint() が呼ばれ、最後にその位置へ図形が描画される流れを示します。
つまり、ユーザーの操作が直接描画処理へつながること、そして repaint() がイベント処理と画面更新をつなぐ橋渡しになっていることが分かります。
また、ボタンではなくフレーム全体がソースになっている点も視覚的に整理できます。

イベント処理と描画処理がつながる面白さ

今回の内容は、GUI学習の中でもかなり面白いポイントです。
なぜなら、イベント処理だけでもなく、描画だけでもなく、その2つがつながっているからです。

  • イベント処理は、ユーザーの操作を受け取る
  • 描画処理は、画面の見た目を作る
  • repaint() は、その間をつなぐ

この3つが組み合わさることで、操作に応じて画面が変わるアプリケーションになります。

ドラゴンボール風にいうと、悟空が修行場を押すという行動が、気のしるしとしてその場に現れる流れです。
操作と結果がつながるので、画面が急に生き生きして見えてきます。

この考え方が広がると何ができるか

今回の例では丸を1つ描くだけですが、考え方を広げると、いろいろな応用ができます。

応用例できること
画像を表示するクリックした場所にキャラクター画像を出す
線を引くドラッグした軌跡に線を描く
色を変える位置によって違う色の図形を描く
複数描画する押すたびに複数のマークを残す

つまり、今回の基本を理解すると、簡単なお絵描きアプリや位置指定型のGUI表現にもつながっていきます。

はじめて学ぶときに押さえたい見方

最初は少し流れが多く感じるかもしれませんが、次の見方で整理すると理解しやすいです。

どこでイベントを受け取るのか

フレームに addMouseListener() を登録しています。

どこで位置を調べるのか

mousePressed() の中で getX() と getY() を使っています。

どこで描画するのか

paint() の中で fillOval() を使っています。

どこで画面更新を起こすのか

repaint() を呼んで paint() を再実行させています。

この4つの場所を意識すると、コード全体の役割がかなりはっきり見えてきます。

この内容でつかんでおきたいこと

今回のテーマでいちばん大切なのは、マウスの位置情報を使って、その場所に描画できるということです。
そして、そのためにはイベント処理だけでなく、再描画の仕組みも必要になります。

押さえておきたいポイントを整理すると、次の通りです。

ポイント内容
フレームがソースになる画面全体でマウスイベントを受け取れる
MouseEvent押した位置の座標を取得できる
getX(), getY()クリック位置を取り出す
paint()実際に図形を描く
fillOval()丸い図形を描く
repaint()画面を描き直すきっかけを作る
MouseAdapter必要なマウスメソッドだけを書ける

この仕組みが分かると、JavaのGUIは単に部品を並べるだけでなく、ユーザーの動きに合わせて画面そのものを作り変えていけることが見えてきます。
ここまで来ると、アプリケーションはさらに表現力のあるものになっていきます。