Tigerを使いこなす: 成長するAWT

マウスの位置決めとzオーダー

Comments

Javaのユーザーインターフェースとなると、Swingコンポーネントばかりが注目を浴びます。しかしGUIには、Swing以上のものがあるのです。これまでのJavaプラットフォームのリリースと同様、SwingもAWT(Abstract Window Toolkit)の上に構築されています。

「の上に構築され」というのは、何を意味するのでしょう。基礎となるAWTでサポートされていない限り、Swingでは何にも起きないのです。例えば、オリジナルのAWTでは、ピア・コンポーネント(peered components)と呼ばれるものがありました。AWTのButtonコンポーネントを作ると、ネイティブ・コンポーネントが作られるのです。Microsoft WindowsのマシンであればWindowsボタン・コンポーネントが作られ、SolarisシステムであればMotifボタンが作られます。10年前に初めてJava言語が紹介された時には、ネイティブ・コンポーネントこそが進むべき道だったのです。

Javaプラットフォームのバージョン1.1で、これが変わりました。バージョン1.1では、ピアレス(peerless)あるいは軽量コンポーネントを作る機能が追加されました。こうした変更以前は、Swingは存在できませんでした。カスタム・コンポーネントを作るということはCanvasをサブクラス化することであり、それによって、プラットフォームにネイティブなコンポーネントが作られたのです。

ちょっと脇道にそれました。今回のヒントは、SwingやJava言語の起源についてではなく、AWTが5.0リリースでどのように進化したかを解説します。確かにSwingは最も目立つ変更ですが、Swingだけが変更されたわけではありません。AWTでの機能強化にも目を向けてみましょう。

PointerInfoとMouseInfo

Java 5.0プラットフォームで追加された2つの新しいクラスが、 PointerInfoMouseInfo です。これらのクラスによって、デスクトップ上のマウス位置を探しやすくなります。まず MouseInfo クラスから始めますが、次の2つの静的メソッドが使えるようになりました。

  • int getNumberOfButtons() を使うと、マウスのボタンが幾つあるかが分かります。ボタンの数によって、利用できる操作の設定を変えたい場合があります。一例をリスト1に示します。
    リスト1. マウスのボタンを数える
    public class Mouse {
      public static void main(String args[]) {
        int count = MouseInfo.getNumberOfButtons();
        System.out.println("Mouse buttons = " + count);
      }
    }
  • PointerInfo getPointerInfo() は、 PointerInfo オブジェクトを返します。

返された PointerInfo オブジェクトには、このオブジェクト独自の、次の2つのメソッドがあります。

  • GraphicsDevice getDevice() は、マウスが位置しているデバイス、つまり実際の GraphicsDevice オブジェクトをレポートします。このメソッドを、複数画面を持ったシステムで呼ぶと、そのマウス位置に固有の GraphicsDevice オブジェクトを取得することができます。
  • Point getLocation() は、スクリーン上のマウスの、物理的な位置を取得します。これをリスト2に示します。
    リスト2. マウス位置を取得する
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    public class Pos {
      public static void main(String args[]) {
        Runnable runnable = new Runnable() {
          public void run() {
            JFrame frame = new JFrame("Mouse Position");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          JLabel label = new JLabel();
            label.setHorizontalAlignment(JLabel.CENTER);
            MouseMotionListener mouseMotionListener = new MouseMotionListener() {
              public void mouseDragged(MouseEvent e) {
                showMousePos(e);
              }
              public void mouseMoved(MouseEvent e) {
                showMousePos(e);
              }
              private void showMousePos(MouseEvent e) {
                JLabel src = (JLabel)e.getComponent();
                PointerInfo pointerInfo = MouseInfo.getPointerInfo();
                Point point = pointerInfo.getLocation();
                src.setText(point.toString());
              }
            };
            label.addMouseMotionListener(mouseMotionListener);
            frame.add(label, BorderLayout.CENTER);
            frame.setSize(300, 300);
            frame.setVisible(true);
          }
        };
        EventQueue.invokeLater(runnable);
      }
    }

図1は、このコードが実際に動作しているところを示しています。ウィンドウ上でマウスを動かすと、座標位置が変わります。

図1. マウス位置を取得する
図1. マウス位置を取得する
図1. マウス位置を取得する

Component クラスも、 Point を返す getMousePosition() メソッドを持っています。 getMousePosition() が返す Point は、コンポーネントの座標空間に基づくマウス位置です。 PointerInfogetLocation() メソッドを呼ぶと、画面が1つだけのシステムでは、グラフィックス・デバイス全体(つまり画面)に対する座標空間で動作します。

java.awt.Robot クラスに慣れていない人のために言うと、 Robot クラスを使うとマウス位置を移動させることができるのです。このクラスは1.3のころからあり、XY座標に対する引数を持った、正に名前通りの mouseMove() メソッドを含んでいます。ですから今や、マウスを動かせるだけではなく、マウス位置を見つけることもできるのです。

zオーダーとAlwaysOnTop

zオーダーの概念によって、コンポーネントとそのウィンドウを置くべき3次元位置を表現することができます。xy位置は、それぞれ水平と垂直の位置を表現し、(0, 0) が画面の左最上部のコーナーです。xyの組み合わせにzオーダーを加えることによって、どのような上下位置関係でコンポーネントを描画するかを制御できるようになります。

コンテナー・レベルでは、 setComponentZOrder(Component c, int layer) と、 getComponentZOrder(Component c) という2つの新しいメソッドがあります。 setComponentZOrder() メソッドに渡されるレイヤーは、0から始まるのが最上位レイヤーであり、これは他のものの上に、最後に描画されます。下のレイヤーになるほど数が増えて行きます。

リスト3のプログラムでは、これを説明するために3つのコンポーネントを作り、それらを重ねて置いています。zオーダーを扱うには、幾つかのステップがあります。まず、コンテナーのレイアウト・マネージャーを使用不可にする必要があります(つまり setLayout(null) にします)。これをせずにzオーダー・セットを持つコンポーネントを追加しても、重複するコンポーネントは無いので、意味がないことになります。もちろん、コンポーネントを重複させる、カスタムのレイアウト・マネージャーを使っている場合には、それでも動作します。

次に、レイアウトを何ら制約せずに、コンテナーにコンポーネントを追加します。次に、コンテナーの中にある各コンポーネントに対して setComponentZOrder() を呼ぶことによって、zオーダーを設定します。追加のステップとして、重複しているコンポーネントが正しく描画されるためには、重複コンポーネントを含むコンテナーは、最適化描画を使用不可にしておく必要があります(コンポーネントの isOptimizedDrawingEnabled() メソッドが false を返すようにします)。この最後のステップは、( setOptimizedDrawingEnabled() メソッドは無いので) JPanel サブクラスを作ることによって行います。

リスト3. zオーダーとAlwaysOnTop
import java.awt.*;
import javax.swing.*;
public class Zs {
  public static void main(String args[]) {
    Runnable runnable = new Runnable() {
      public void run() {
        JFrame frame = new JFrame("Z-Ordering");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JButton top = new JButton("Top");
        JButton middle = new JButton("Middle");
        JButton bottom = new JButton("Bottom");
        int x=25;
        int y=75;
        int width="100";
        int height="50";
        int overlap = 25;
        int widthDelta = width - overlap;
        int heightDelta = height - overlap;
        top.setBounds(x, y, width, height);
        middle.setBounds(x + widthDelta, y - heightDelta,
          width, height);
        bottom.setBounds(x + (widthDelta) * 2, y - (heightDelta) * 2,
          width, height);
        JPanel contentPane = new JPanel() {
          public boolean isOptimizedDrawingEnabled() {
            return false;
          }
        };
        contentPane.setLayout(null);
        contentPane.add(top);
        contentPane.add(middle);
        contentPane.add(bottom);
        contentPane.setComponentZOrder(top, 0);
        contentPane.setComponentZOrder(middle, 1);
        contentPane.setComponentZOrder(bottom, 2);
        frame.setContentPane(contentPane);
        frame.setSize(300, 200);
        frame.setVisible(true);
      }
    };
    EventQueue.invokeLater(runnable);
  }
}

図2は、リスト3のコードの結果を示します。

図2. zオーダー
図2. zオーダー
図2. zオーダー

zオーダーの制御は、コンテナー内部の個々のコンポーネントに対して行えるだけではありません。ウィンドウのzオーダーを管理することもでき、少なくとも、どのウィンドウが一番上にあるべきかを言うことができるのです。もちろん、 toFront() メソッドを呼んで、ある特定なウィンドウが前面になるようにすることもできるのですが、Java 5.0プラットフォームでは、 Window クラスに新しい setAlwaysOnTop() メソッドがあるのです。あるウィンドウが、その alwaysOnTop プロパティーを true に設定してあると、そのウィンドウは常に他のウィンドウの上になります。他のウィンドウも、そのウィンドウのコンポーネントを選択された状態にしておくことはできるのですが、 alwaysOnTopfalse (デフォルト)に設定されたウィンドウは、背景としてとどまります。

リスト4は、この振る舞いを示しています。

リスト4. zオーダーとAlwaysOnTop
import java.awt.*;
import javax.swing.*;
public class Top {
  public static void main(String args[]) {
    Runnable runnable = new Runnable() {
      public void run() {
        JFrame frame = new JFrame("Topper");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JDialog one = new JDialog(frame, "One");
        one.add(new JButton("One"), BorderLayout.CENTER);
        one.setSize(100, 100);
        one.setLocation(25, 25);
        one.setVisible(true);
        JDialog two = new JDialog(frame, "Two");
        two.add(new JButton("Two"), BorderLayout.CENTER);
        two.setSize(100, 100);
        two.setLocation(50, 50);
        two.setVisible(true);
        JDialog three = new JDialog(frame, "Three");
        three.add(new JButton("Three"), BorderLayout.CENTER);
        three.setSize(100, 100);
        three.setLocation(75, 75);
        three.setVisible(true);
        two.setAlwaysOnTop(true);
        frame.setSize(300, 200);
        frame.setVisible(true);
      }
    };
    EventQueue.invokeLater(runnable);
  }
}

図3は、リスト4の結果を示しています。

図3. ウィンドウをレイヤー化する
図3. ウィンドウをレイヤー化する
図3. ウィンドウをレイヤー化する

信頼できないアプリケーションでは、そのポリシー・ファイルに、 setWindowAlwaysOnTop というターゲット名で AWTPermission が必要です。これによって、悪意のユーザーがデスクトップ全体をオーバーライドして本物のように見せかけ、その下でアプリケーションが情報を吸い上げてしまうというトラブルを防止することができます。

注意すべきこととして、アクティブな1つのウィンドウのみが alwaysOnTop プロパティーを true に設定されている、というようにすべきです。もし複数のウィンドウが true に設定されていると、振る舞いは不定となり、プラットフォーム固有となってしまう可能性があります。

時が経つのは早いもの

Javaユーザーの皆さん、10周年おめでとうございます。AWTが長らえ、そして改善され続けますように!


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=226632
ArticleTitle=Tigerを使いこなす: 成長するAWT
publish-date=05242005