パネル上にいくつかのボタンを持ったGUIアプリケーションにおいて、キー入力を受け付けさせたくても、入力できなくなることがあります。これはなぜなのでしょうか。また対策はどうすればよいのでしょうか。
Javaは、GUIにおけるイベントの処理に「委譲イベントモデル」を採用しています。このモデルではイベントを発生させる「イベントソース」と、そのイベントを処理するための「リスナ」という2つの役割が存在します。イベントを受け取ってリスナに渡すためには、GUI上の適当なコンポーネントにリスナを登録する必要があります。
例えば「マウスをパネル上でクリックするとその場所に点が表示される」場合では、マウスがMouseEventを発生させる「イベントソース」となり、そのイベントが「リスナ」としてのパネルに渡されることになります。リスナはパネルに登録され、パネルはマウスがその上で発生させるイベントを受け取ります。
キー入力を扱う場合にも同じモデルが用いられています。キーボードがイベントソースで、そこから発生するイベントはjava.awt.event.KeyEventというクラスで表現されます。リスナはKeyListenerというインターフェイスを実装します。
入力したキーの文字を画面に表示させ、ボタンをクリックしたらそれがクリアされるという簡単なアプリケーションを考えてみます。
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)、いくらキー入力をしても全くウインドウには変化がありません。ボタンをクリックしたら、“Hello,”という文字列が消えるのでプログラムが動いていることは確認できます。
キー入力が受け付けられない状態になっている理由は、画面1の状態だとボタンにフォーカスがあたっているため、キー入力のすべてのイベントが、このボタンに渡されてしまっているところにあります。コード中のKeyTestのコンストラクタに“setFocusable(true);”の1行を加えてみると、アプリケーションの起動時にはボタンのフォーカスが消えるため(画面2)、入力が可能になります。しかし、この場合でもボタンをクリックするとフォーカスは再びボタンに移り、キー入力が受け付けられない状態になります。
このような状態を解決するためには、「ButtonオブジェクトにもKeyListenerを登録する」のが簡単な解決手段です。コンストラクタに以下の1行を加えます。
b.addKeyListener(this);
これによって、同じリスナがボタンとKeyTestとに両方登録されるわけですが、問題はありません。
こうすることで、ボタンにフォーカスが移っている状態でも、キー入力のイベントはボタンに登録したリスナによって処理されます。ToolBarなどを用いて複数個のボタンを登録した場合にも、面倒ですが全てのButton オブジェクトに対してリスナを登録する方法を推奨します。このため、KeyListenerの実装は無名クラスによって登録する際に行うのではなく、(内部)クラスで定義し、複数のコンポーネントに繰り返し登録可能にしておくのがよいでしょう。
Copyright © ITmedia, Inc. All Rights Reserved.