2011年8月11日木曜日

JSpinnerのちょっと困った話

JSpinnerを使おうと思いまして


Java+Swingのお仕事が続く毎日、yu1rowです。
スピナーってありますよね。
「増やす」ボタンと「減らす」ボタンが付いてるテキストボックス、みたいな。
数値入力ができるモードで、ちょっと困ったんで、その話。

止まっちゃうんですが!?


50~200まで入力できて、「増やす」と「減らす」ボタンを押すと、25ずつ増えたり減ったりするスピナーを作りました。
「↑」とか「↓」のキーボード入力でも増やしたり減らしたりできます。

ところが。

キーボードからも入力できるんで、「199」とかも入力できるんですね。
でも、「199」の状態で「増やす」ボタンを押しても増えてくれないんですよ。
199+25=224なんで、上限の「200」を超えちゃうからなんでしょうけど…。
こっちとしては、「200」になって欲しいわけです。

反対に、「55」の状態から「減らす」ボタンを押しても同じです。
「50」になって欲しいなぁ…と。

結局どうしたのか?



 ボタンを押したときのイベントと、テキストボックスでキー入力したときのイベントを捕まえて対処しました。
JSpinnerの中には以下のものが入っています
  • JButton (2個)
  • SpinnerModel (1個)
さらに、SpinnerModelの中には以下のものが入っています
  • JFormattedTextField (1個))
この2個のJButtonと1個のJFormattedTextFieldのイベントを捕まえちゃいます。
2個のJButtonにはそれぞれ以下の名前がついています
  • 「Spinner.nextButton」
  • 「Spinner.previousButton」
フレームに"spinner"という名前のJSpinnerがあるとする場合、以下のコードで2個のボタンにイベントを登録できます。
※コード中の「setMinimumValue()」と「setMaximumValue()」というメソッドは後述

for (Component c : spinner.getComponents()) {
    if ("Spinner.nextButton".equals(c.getName())) {
        // 次ボタンのイベント登録
        ((JButton) c).addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setMinimumValue();
            }
        });
    } else if ("Spinner.previousButton".equals(c.getName())) {
        // 前ボタンのイベント登録
        ((JButton) c).addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setMaximumValue();
            }
        });
    }
}

イベントから呼び出す「setMinimumValue()」と「setMaximumValue()」、そこから使用される「isSpinnerNumberModel()」は以下のように記述します。

/**
 * スピナーのモデルが {@link SpinnerNumberModel} かどうかを取得します。
 * @return スピナーのモデルが {@code SpinnerNumberModel} の場合 {@code true} 、
 * そうでない場合 {@code false}
 */
private boolean isSpinnerNumberModel() {
    SpinnerModel model = spinner.getModel();
    // modelがnull
    if (model == null) { return false; }
    // 数値モデル以外
    if (model instanceof SpinnerNumberModel == false) { return false; }
    return true;
}

/**
 * 最大値を設定します。
 * <p>
 * このメソッドは以下のどれかの条件に当てはまる場合は何も行いません。
 * <ul>
 * <li>スピナーのモデルが {@link SpinnerNumberModel} ではない</li>
 * <li>既に最大値がセットされている</li>
 * <li>次にセットすべき値 ({@link JSpinner#getNextValue()}) が {@code null} ではない</li>
 * </ul>
 * 以上の条件以外の場合、最大値 ({@link SpinnerNumberModel#getMaximum()}) が設定されます。
 * </p>
 */
private void setMinimumValue() {
    if (!isSpinnerNumberModel()) { return; }
    // SpinnerNumberModelにキャスト
    SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
    // 既にMaximumであれば無視
    if (model.getValue().equals(model.getMaximum())) { return; }
    // 次にセットするべき値がある場合は無視
    if (spinner.getNextValue() != null) { return; }
    // 最大値をセット
    if (model.getMaximum() != null) { model.setValue(model.getMaximum()); }
}

/**
 * 最小値を設定します。
 * <p>
 * このメソッドは以下のどれかの条件に当てはまる場合は何も行いません。
 * <ul>
 * <li>スピナーのモデルが {@link SpinnerNumberModel} ではない</li>
 * <li>既に最小値がセットされている</li>
 * <li>次にセットすべき値 ({@link JSpinner#getPreviousValue()}) が {@code null} ではない</li>
 * </ul>
 * 以上の条件以外の場合、最小値 ({@link SpinnerNumberModel#getMinimum()}) が設定されます。
 * </p>
 */
private void setMaximumValue() {
    if (!isSpinnerNumberModel()) { return; }
    // SpinnerNumberModelにキャスト
    SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
    // 既にMinimumであれば無視
    if (model.getValue().equals(model.getMinimum())) { return; }
    // 次にセットするべき値がある場合は無視
    if (spinner.getPreviousValue() != null) { return; }
    // 最小値をセット
    if (model.getMinimum() != null) { model.setValue(model.getMinimum()); }
}

キーボードの「↑」と「↓」キーのイベントを捕まえて値を変更させる場合のイベントは、以下のように登録します。
※ここでも前述の「setMinimumValue()」、「setMaximumValue()」、「isSpinnerNumberModel()」というメソッドを使用しています

// SpinnerNumberModelでない場合はイベント登録しない
if (isSpinnerNumberModel()) {
    // JFormattedTextFieldを取得
    JFormattedTextField textField 
        = (JFormattedTextField) spinner.getEditor().getComponents()[0];
    // keyPressedイベントの登録
    textField.addKeyListener(new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_UP:
                    setMinimumValue();
                    break;
                case KeyEvent.VK_DOWN:
                    setMaximumValue();
                    break;
            }
        }
    });
}

一応、これで目的の動作をしているようです。
※ご使用に際しては、自己の責任と計画を以てお願いします

これでよかったんかいな?


上記のようなモノを実装したカスタムしたコンポーネントを作って、今回はやり過ごしました。
ホンマはプロパティーひとつで出来ちゃうような、スマートなやり方があるのかもしれません。
今回はそれが見つけられなかったので、こんな強引なやり方で対処することになりました。
もっとスマートなやり方、募集中です。

0 件のコメント:

コメントを投稿