GUI部品にフォーカスを奪われた場合の対処JavaTips 〜Javaプログラミング編

» 2005年01月19日 10時00分 公開
[小田原大@IT]

 パネル上にいくつかのボタンを持ったGUIアプリケーションにおいて、キー入力を受け付けさせたくても、入力できなくなることがあります。これはなぜなのでしょうか。また対策はどうすればよいのでしょうか。

イベントモデル

 Javaは、GUIにおけるイベントの処理に「委譲イベントモデル」を採用しています。このモデルではイベントを発生させる「イベントソース」と、そのイベントを処理するための「リスナ」という2つの役割が存在します。イベントを受け取ってリスナに渡すためには、GUI上の適当なコンポーネントにリスナを登録する必要があります。

 例えば「マウスをパネル上でクリックするとその場所に点が表示される」場合では、マウスがMouseEventを発生させる「イベントソース」となり、そのイベントが「リスナ」としてのパネルに渡されることになります。リスナはパネルに登録され、パネルはマウスがその上で発生させるイベントを受け取ります。

キーイベント

 キー入力を扱う場合にも同じモデルが用いられています。キーボードがイベントソースで、そこから発生するイベントはjava.awt.event.KeyEventというクラスで表現されます。リスナはKeyListenerというインターフェイスを実装します。

 入力したキーの文字を画面に表示させ、ボタンをクリックしたらそれがクリアされるという簡単なアプリケーションを考えてみます。

KeyTest.java
import java.awt.*;
import java.awt.event.*;
public class KeyTest extends Frame implements KeyListener{
    Button b = new Button("clear");
    String message = "Hello,";
    public KeyTest(String title){
        super(title);
        setLayout(new FlowLayout());
        addKeyListener(this);
        add(b);
        b.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    message = "";
                    repaint();
                }
            });
        setSize(300, 200);
    }
    public void paint(Graphics g){
        g.drawString(message, 50, 100);
    }
    public void keyPressed(KeyEvent e){
        message += e.getKeyChar()+"";
        repaint();
    }
    public void keyReleased(KeyEvent e){}
    public void keyTyped(KeyEvent e){}
    public static void main(String[] args){
        KeyTest frame = new KeyTest("KeyTest");
        frame.setVisible(true);
    }
}

 このプログラムでは2つのイベントが扱われています。ButtonがソースとなるActionEvent、キーボードの入力がイベントとなるKeyEventです。ActionEventに対するリスナはButtonのオブジェクトに登録します。他の場所で使うことはないので、登録と同時に無名クラスのリスナを定義しています。 KeyEventに対するリスナは、Frameに登録します。こうすれば、ウィンドウがアクティブなときにはキー入力を受け付けてくれるように思われます。

 プログラムの処理は、キー入力をする度にその文字がウインドウ上の文字列の後ろに付け加わり表示されるというものです。clearと書かれたボタンが画面上部に配置されていて、これをクリックするとmessageが空文字列に変わります。つまり画面上の文字列が消去されます。

画面1 KeyTest のウインドウを起動したときの状態 画面1 KeyTest のウインドウを起動したときの状態

 しかし、このプログラムを実行してみると、ウインドウ上に配置したボタンにフォーカスがあたり(画面1)、いくらキー入力をしても全くウインドウには変化がありません。ボタンをクリックしたら、“Hello,”という文字列が消えるのでプログラムが動いていることは確認できます。

 キー入力が受け付けられない状態になっている理由は、画面1の状態だとボタンにフォーカスがあたっているため、キー入力のすべてのイベントが、このボタンに渡されてしまっているところにあります。コード中のKeyTestのコンストラクタに“setFocusable(true);”の1行を加えてみると、アプリケーションの起動時にはボタンのフォーカスが消えるため(画面2)、入力が可能になります。しかし、この場合でもボタンをクリックするとフォーカスは再びボタンに移り、キー入力が受け付けられない状態になります。

画面2 最初だけはKeyTestがフォーカスされており入力が可能 画面2 最初だけはKeyTestがフォーカスされており入力が可能

 このような状態を解決するためには、「ButtonオブジェクトにもKeyListenerを登録する」のが簡単な解決手段です。コンストラクタに以下の1行を加えます。

b.addKeyListener(this);

 これによって、同じリスナがボタンとKeyTestとに両方登録されるわけですが、問題はありません。

画面3 ボタンにフォーカスが当たっているが、入力が可能 画面3 ボタンにフォーカスが当たっているが、入力が可能

 こうすることで、ボタンにフォーカスが移っている状態でも、キー入力のイベントはボタンに登録したリスナによって処理されます。ToolBarなどを用いて複数個のボタンを登録した場合にも、面倒ですが全てのButton オブジェクトに対してリスナを登録する方法を推奨します。このため、KeyListenerの実装は無名クラスによって登録する際に行うのではなく、(内部)クラスで定義し、複数のコンポーネントに繰り返し登録可能にしておくのがよいでしょう。

Profile

WINGSプロジェクト

小田原大


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。