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

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

Abstract Window Toolkitは、Swingコンポーネント・セットの基礎です。Java ™ 2プラットフォーム5.0リリースでのAWTの変更点について、John Zukowskiが解説します。これらの変更点には、ポインター位置を取得するための PointerInfoMouseInfo 、そして、オーバーレイされたコンポーネントを持つ画面に対するzオーダー管理などがあります。

John Zukowski, President, JZ Ventures, Inc.

John ZukowskiJohn Zukowski は、JZ Ventures, Inc. の戦略的 Java コンサルティングを推進し、数多くの jGuru のコミュニティー主導の Java FAQs で常任指導者として活躍しています。最新の著書には、Apress から出版された「 Java Collections」および「 Definitive Guide to Swing for Java 2」 (第 2 版) があります。



2005年 5月 24日

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. マウス位置を取得する

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オーダー

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. ウィンドウをレイヤー化する

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

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


時が経つのは早いもの

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

参考文献

  • Tigerを使いこなすシリーズ の他の記事も読んでください。
  • Sun Developer Networkから J2SE 5.0 をダウンロードしてください。
  • Barry A. FeigenbaumとTom Brunetによる記事、「 イメージベース・パスを使用した2Dアニメーション 」(developerWorks, 2004年1月)は、固定オブジェクトのアニメーションから大量のコーディングをなくす上で役に立ちます。
  • PointerInfo クラスのJavadocを読んでください。
  • MouseInfo クラスのJavadocを読んでください。
  • Container クラスのJavadocを読んでください。
  • SunのJ2SE 5.0ドキュメンテーションでは、 AWTの機能強化 を解説しています。
  • Javaプログラミングについてさらに学ぶために、 developerWorksのJavaゾーン を見てください。技術資料やハウツー記事、教育資料、ダウンロード、製品情報その他の情報が豊富に用意されています。
  • New to Java technology には、Java言語を使い始めるための助けとなる最新情報が用意されていますので、ご覧ください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


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