Go to Contents Go to Java Page
J2SE 1.5 虎の穴
 
 

マウスの位置がたちどころに分かる MouseInfo

 
 
Tiger ゲームには最悪だった
 
 

マウスポインタの位置に関しては苦い思いでがいっぱいあります。

ブロック崩しでもシューティングでもいいのですが、マウスポインタの位置に応じてパッドや自機の位置を変えようとします。通常は MouseMotionEvent クラスを使用して、マウスポインタの移動を検出します。

ところが、ところがです。

マウスのポインタがゲームのフレームの外側に出てしまうと、MouseMotionEvent イベントが発生しなくなってしまうのです。つまり、パッドや自機が全然動かなくなってしまうのです。遊んでいるほうはマウスポインタの位置じゃなくてマウスを右や左に動かすということだけ集中しているので、急にパッドや自機が動かなくなってしまいパニック状態に陥ってしまいます。

フレームの外側に出ても唯一イベントがあがってくるのが、マウスをドラッグしたときです。逆にいえば、これ以外の時は一切イベントが発生しませんでした。

マウスドラッグが分かるということは、ドラッグしていないときでもちゃんとマウスポインタの位置を把握できるはずなのに、できない。理不尽です。

でも、そんな杞憂は過去のこと。Tiger ではいつでもどこでもマウスポインタの位置を知ることができるようになったのです。

 

 
 
Tiger MouseInfo クラスと PointerInfo クラス
 
 

マウスポインタの位置を知るためのキーになるのは java.awt.MouseInfo クラスと java.awt.PointerInfo クラスです。両方とも Tiger で取りいれられたクラスです。

MouseInfo クラスには 2 つの static なメソッドだけが定義されています。1 つがマウスのボタン数を調べる MouseInfo#getNumberOfButtons メソッドと PointerInfo オブジェクトを取得する MouseInfo#getPointerInfo メソッドです。

PointerInfo クラスも 2 つのメソッドが定義されていますが、普通は PointerInfo#getLocation メソッドしか使わないと思います。getLocation メソッドの戻り値は Pointer オブジェクトで位置を知ることができます。

覚えるのはこれだけです。たった 3 つのメソッドを理解すれば、後は何も必要ありません。

サンプルのソース MouseLocationTest1.java

このサンプルはマウスのボタンの数を出力した後に、1 秒おきに 10 回マウスポインタの位置を出力します。

import java.awt.MouseInfo;
import java.awt.PointerInfo;
 
public class MouseLocationTest1 {
    public MouseLocationTest1() {
        System.out.println("Number of Mouse Button : "
                           + MouseInfo.getNumberOfButtons());
 
        for (int i = 0; i < 10; i++) {
            PointerInfo pointerInfo = MouseInfo.getPointerInfo();
            System.out.println("Location of Mouse : " + pointerInfo.getLocation());
 
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException ex) {
                break;
            }
        }
    }
 
    public static void main(String[] args) {
        new MouseLocationTest1();
    }
}

マウスのボタンの数は単に MouseInfo#getNumberOfButtons メソッドを呼び出しているだけです。

マウスポインタの位置は MouseInfo#getPointerInfo メソッドで PointerInfo オブジェクトを取得し、PointerInfo#getLocation メソッドで位置を検出しています。

C:\examples>java MouseLocationTest1
Number of Mouse Button : 2
Location of Mouse : java.awt.Point[x=996,y=343]
Location of Mouse : java.awt.Point[x=787,y=305]
Location of Mouse : java.awt.Point[x=357,y=395]
Location of Mouse : java.awt.Point[x=394,y=227]
Location of Mouse : java.awt.Point[x=789,y=464]
Location of Mouse : java.awt.Point[x=964,y=70]
Location of Mouse : java.awt.Point[x=351,y=493]
Location of Mouse : java.awt.Point[x=689,y=287]
Location of Mouse : java.awt.Point[x=820,y=139]
Location of Mouse : java.awt.Point[x=509,y=543]
 
C:\examples>

ここで使っているのは 2 ボタンマウスなので、はじめのボタン数は正しいです。

不思議かもしれませんが、MouseInfo クラスの getLocationInfo メソッドは GUI を一切表示しない状態でも使用できるということです。もちろん、AWT や Swing を使用していても、MouseInfo クラス/PointerInfo クラスを使用することは可能です。

 

 
 
Tiger コンポーネントでも使えるか?
 
 

独立したクラス MouseInfo を使用すればマウスポインタの位置は取得することができることは分かりました。

でも、せっかくだから AWT や Swing のコンポーネントで使ってみたいですよね。そんなときのために用意されたのが Componennt#getMousePosition メソッドです。なぜ、PointerInfo#getLocation メソッドとメソッド名を合わせないのでしょう。何か裏があるかもしれません ^^;;

まぁ、何はともあれやってみましょう。

サンプルのソース MouseLocationTest2.java

PointerInfo クラスから取得したものと、Component クラスから取得したものを並べて書いてみました。

import java.awt.MouseInfo;
import java.awt.PointerInfo;
import javax.swing.JFrame;
 
public class MouseLocationTest2 {
    public MouseLocationTest2() {
        JFrame frame = new JFrame("Mouse Location");
        frame.setBounds(100, 100, 500, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         
        frame.setVisible(true);
 
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException ex) {}
 
        PointerInfo pointerInfo = MouseInfo.getPointerInfo();
        System.out.println("Location of Mouse : " + pointerInfo.getLocation());

        System.out.println("Position of Mouse : " + frame.getMousePosition());
    }
 
    public static void main(String[] args) {
        new MouseLocationTest2();
    }
}

実行すると意外な結果が得られました。

C:\examples>java MouseLocationTest2
Location of Mouse : java.awt.Point[x=342,y=553]
Position of Mouse : java.awt.Point[x=242,y=453]
 
C:\examples>java MouseLocationTest2
Location of Mouse : java.awt.Point[x=816,y=157]
Position of Mouse : null
 
C:\examples>

1 回目がフレームの内部にマウスポインタがある場合、2 回目の実行がマウスポインタがフレームの外にある場合です。

まず、1 回目のほうですが、マウスポインタの位置が 2 つの方法で違っています。どういうことかというと、座標の原点が違うということです。

PointerInfo#getLocation メソッドで得られる値は画面の絶対的な座標になります。 一方の Component#getMousePosition メソッドで得られる値はそのコンポーネント左上が原点になっているのです。ようするに絶対座標ではなくて、相対座標になっているわけです。

それから考えると 2 回目の実行の結果も分かるような気がします。コンポーネントの相対座標なので、コンポーネント上にマウスポインタがなければ、相対座標がマイナスの値になってしまったりします。それはそれで私はいいと思うのですが、Tiger ではそれよりも null を返すことを選んだようです。

このように取得できる値が異なるので、getLocation と getMousePosition というように名前を変えてきたのだと思います。でも、帰るのだったらもっと分かりやすい名前にしてもらえないかなぁ。

さて、せっかくなので Component#getMousePosition メソッドがどのように処理されているのか見てみましょう。Component#getMousePosition メソッドを次に示します。

    public Point getMousePosition() throws HeadlessException {
        if (GraphicsEnvironment.isHeadless()) {
            throw new HeadlessException();
        }
 
        PointerInfo pi = (PointerInfo)java.security.AccessController.doPrivileged(
                             new java.security.PrivilegedAction() {
                                 public Object run() {
                                     return MouseInfo.getPointerInfo();
                                 }
                             }
                         );
 
        synchronized (getTreeLock()) {
            Component inTheSameWindow = findUnderMouseInWindow(pi);
            if (!isSameOrAncestorOf(inTheSameWindow, true)) {
                return null;
            }
            return pointRelativeToComponent(pi.getLocation());
        }
    }

はじめの if 文はディスプレイを使用しないヘッドレスモードの場合なので本質とは関係ありません。次のブロックで PointerInfo オブジェクトを取得しています。AccessController クラスが使用されているのはセキュリティポリシーが設定できるようになっているからです。

次の if 文でマウスポインタがコンポーネント上にあるかどうかを調べており、なければ null が帰るようになっています。

最後に MouseInfo#getPointerInfo メソッドを使用して取得した PointerInfo オブジェクトをpointerRelativeToComponent メソッドを使用して相対値に変換します。pointRelativeToComponent メソッドもいっしょに見てみましょう。

    Point pointRelativeToComponent(Point absolute) {
        Point compCoords = getLocationOnScreen();
        return new Point(absolute.x - compCoords.x,
                         absolute.y - compCoords.y);
    }

getLocationOnScreen メソッドはコンポーネントが絶対座標でどこに位置しているかを調べるためのメソッドです。ですから、引き算をすれば相対値になりますね。

 

 
 
Tiger おまけ
 
 

いつでもマウスポインタの位置が取れるとしたら、すぐ思いつくのはあれでしょう。Windows 世代の人にはなじみがないかもしれませんが、UNIX の X11 で育った私にはやっぱり xeye です。

役に立つアプリではありませんが、Java でも同じようなものを作ってみましょう。ただし、xeye のように目のふちが丸いと計算が面倒になるので、四角にします。丸と四角の違いは本質的ではないので大目に見てください。

サンプルのソース JavaEye.java
EyeComponent.java

マウスポインタの位置に目を書くだけなので、たいしたことありません。面倒なのは、マウスがコンポーネントの外がわにあるときです。そのときにはコンポーネントの一番外側に書くようにします。

実際に目を描いているのは、EyeComponent クラスの paintComponent メソッドです。

    public void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D)g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
 
        PointerInfo pointerInfo = MouseInfo.getPointerInfo();
        Point absolute = pointerInfo.getLocation();
 
        Point relative = traslatePointRelativeToComponent(absolute);
 
        int x;
        if (relative.x < 0) {
            x = 0;
        } else if (relative.x > width - eyeWidth) {
            x = width - eyeWidth;
        } else {
            x = relative.x;
        }
 
        int y;
        if (relative.y < 0) {
            y = 0;
        } else if (relative.y > height - eyeHeight) {
            y = height - eyeHeight;
        } else {
            y = relative.y;
        }
 
        g.setColor(Color.BLACK);
        g.fillOval(x, y, eyeWidth, eyeHeight);
    }
	
    private Point traslatePointRelativeToComponent(Point absolute) {
        Point compCoords = getLocationOnScreen();
        return new Point(absolute.x - compCoords.x,
                         absolute.y - compCoords.y);
    }

赤の部分がマウスポインタを調べる部分です。再描画されるごとにマウスポインタの位置を調べて、描画するようにしています。translatePointRelativeToComponent メソッドは先ほどの Component#pointRelativeToComponent とまったく同じなのですが、pointRelativeToComponent メソッドがパッケージプライベートなので、同じことを書き直しただけです。

次の青い字のところが、マウスポインタがコンポーネントの外がわにある場合の処理です。ここで、width と height がコンポーネントの幅と高さになります。また、eyeWidth と eyeHeight が目の黒目の幅と高さです。

本来ならば、目の中心とマウスポインタを結んだ直線がコンポーネントの淵と交わる点を求めてそこに黒目を描くのでしょうか、今回は簡易的な方法ということで。

そして、最後に楕円を描画します。

定期的に再描画するのは JavaEye クラスの start メソッドでタイマーを設定しているからです。タイマーには java.util.Timer と javax.swing.Timer の 2 種類を使えますが、今回は GUI に特化した処理しかしないので javax.swing.Timer を使用しました。

    public void start() {
        ActionListener listener = new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    frame.repaint();
                }
            };
			
        Timer timer = new Timer(50, listener);
        timer.start();
    }

これを実行すると、下のようなウィンドウが表示されるはずです。ぐりぐりやって遊んでみてください。

SupplementaryCharTest1_1 の結果
図 1 JavaEye の実行結果

 

 
 
Tiger おわりに
 
 

こんな簡単なことがなぜいままでできなかったのでしょう。不思議ですね。

逆にいえば、Tiger でデスクトップで Java を使うことをかなり意識してきているということが分かります。せっかくの機能なので、積極的に使っていきたいものです。

ただし、Applet や JavaWebStart で使うには注意が必要です。SandBox 内でマウスの位置を取得すると SecurityException が発生してしまうので、セキュリティポリシーの設定が必要です。

 

今回使用したサンプルはここからダウンロードできます。

 

(Jul. 2004)

 
 
Go to Contents Go to Java Page