J2SE1.3以降のSwingのキーボードアクションのお作法?
Weblogic6.1からWeblogic8.1に移行するにあたり、大量にあるejbのキャッシュ値を更新するツールを作成している。クライアントアプリケーションとして作成し、jar/earファイルの書き換えを行うわけだが、画面で、まず躓いた。
AbstractActionでアクションを定義する。
1つのアクションをAbstractAction
で定義することにより、JMenuItemとJButtonの両方に割り当てあたり、任意のActionListenerをとるコンポーネントに渡すことでアクションの挙動と状態を一元管理する。
public class Foo extends JPanel { ... private final Action _actionXXX = new AbstractAction( "XXX" ) { public void actionPerformed( final ActionEvent e ) { Foo.this.onXXX(); } } public Foo() { super( new BorderLayout( 5, 5 ) ); this.add( new JButton( this._actionXXX ), BorderLayout.CENTER ); } protected void onXXX() { //TODO: アクション } ... }
ActionインターフェイスにはAction#setEnabled(boolean)
メソッド等あり、これで関連するボタンやメニューなどを一括して状態管理できる。
キーボードとアクションのマッピング*1はInputMapとActionMapで管理する。
キーボードから直接アクションを実行できるようにするには、J2SE1.3からはInputMapとActionMapを使う。カスタムコンポーネントの場合はComponentInputMap
を使うらしい。
final InputMap im = new ComponentInputMap( this ); im.put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0), this._actionCANCEL ); final ActionMap am = new ActionMap(); am.put( this._actionCANCEL , this._actionCANCEL ); this.setInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, im ); this.setActionMap( am );
WHEN_ANCESTOR_OF_FOCUSED_COMPONENTというのは、自身か、自身の子にフォーカスがある場合のマッピングを意味する。
親コンポーネント(JFrame/JDialog)のマッピングと結合させる。
さすがにSwingは良く出来ていて親コンポーネントで設定されたマッピングと子コンポーネントで設定したマッピングは巧く結合される。
JFrameやJDialogはJRootPane
でInputMapやActionMapを割り付けるが、このときはgetActionMapや、getInputMapを使ってマッピングを取得する。
このとき、getInputMap(void)を使うのではなく、JComponent#getInputMap(int)
を使って、明示的にWHEN_ANCESTOR_OF_FOCUSED_COMPONENTを指定してあげる必要がある。
さもないと、フォーカスが子に移った時点でせっかく設定したマッピングが効かなくなってしまう。
この単純な事実に気が付かず、もしかして子コンポーネントでsetInputMapをすると親コンポーネントの設定が消えるのではないか、とかいろいろ勘ぐって無駄に時間を潰してしまった。(深夜1時〓深夜3時)
あいにく手持ちの本「CORE JAVA2(ASIN:475613582X)」がJ2SE1.2時代のものなので、このあたりがさっぱり書かれておらず、こんな単純なことでもハマってしまった。
# しかし、別の問題が…。コンポーネントにフォーカスがあるときに、そのコンポーネントを削除すると、フォーカスが行方不明になってしまうのは、どうすればよいのか皆目検討がつかず。これはなぞのままだ。