ClientProperty

最近は文句ばかり書いているが別にSwingの全てが気に入らないわけではない。完成度が高いだけに、設計の古さや、複雑さなどのちょっとした所が気になるのだ。

例えばJComponentクラスのClientPropertyが何気に便利だ。これは内部に持っているテーブル(正確にはArrayTableクラス)に名前付きのパラメタ値を格納することができる機能であり、クラスを継承すること無しに、ちょっとした機能の拡張を行うことができる。

.NET WindowFormsのコントロールにはCausesValidationというブール型のプロパティがあり、これはコントロールがフォーカスを得た際に、その直前にフォーカスを失ったコントロールのバリデーション(検証)イベントを発火するのを制御するものだ。

private System.Windows.Forms.Button button1;
private void InitializeComponent()
{
    //button1がフォーカスを得た際にフォーカスを失ったコントロールのValidationイベントは発火しない
    this.button1.CausesValidation = false;
}

GUIアプリケーションは入力コントロールのフォーカスが消失した際(値が変わった際)にバリデーションを実施する機能が一般的に実装されるが※、そのようにした場合は処理を決定するボタンを押下したり、メニューを選択するだけで意図しないバリデーションが発火してしまうことになる。CausesValidationプロパティはそのような不用意なバリデーションを抑制できるのだ。

このように非常に有用なプロパティだが、Swingには同様のプロパティは存在しない。(フォーカス消失時に発生するFocusEventオブジェクトにおけるisTemporary()メソッドで、現在のフォーカス遷移が一時的なものかを判定できるが、これだけでは不足だ)
このような場合、ClientPropertyに同様のプロパティを仕込んで置くことで同様の機能が実現できる。

  • ボタン生成時にCausesValidationにfalseを設定
JButton button = new JButton();
//このボタンにフォーカスが遷移してもバリデーションを開始しないことを示す
button.putClientProperty("causesValidation" , Boolean.FALSE);
  • フォーカス消失時にフォーカスを得たコンポーネントを取得し、クライアントプロパティ"CausesValidation"にFalse値が設定されていた場合はバリデーションを実施せずハンドラを抜ける
@Override
public void focusLost(FocusEvent e) {
    if ( e.isTemporary() ) return; 
    
    if ( e.getOppositeComponent() != null ) {
        if ( e.getOppositeComponent() instanceof JComponent ) {
            JComponent jc = (JComponent)e.getOppositeComponent();
            if ( jc.getClientProperty("causesValidation") == Boolean.FALSE) {
                return; //バリデーション無し
            }
        }
    }
    // 項目バリデーションの実施処理
    〜
}

この方法を応用することで他にもいろいろなことができるだろう。

※Swing JComponentではjavax.swing.InputVerifierクラスによるバリデーションも使える。同クラスによるバリデーションはフォーカス遷移前に実行することができるが、今回のネタでは対象外とした。