正方形の周長 #6

二つのJTextFieldの値の一方から他方を変更できるようにしたが、それが原因の問題が発生する。
JTextFieldに入力した値の確定はActionEventの発生(Enterキーの押下)によって行われるよう実装している。
もし確定時に数値に変換できない入力であった場合は、
もう一方のJTextFieldの値から変更前の値を逆算して設定し直している。
ところが、一方のJTextFieldに不正な入力を行った後、
それを確定することなくもう一方のJTextFieldにキーボードフォーカスを移し、
そちらにも不正な入力を与えてから、どちらかのJTextFieldを確定すると、
参照すべき側のJTextFieldも不正なので対処できなくなり例外が投げられてしまう。
周の長さがJLabelでありユーザによる変更ができなかった時には起きなかった問題である。

解決法として、JTextFieldで発生するActionEventを捕捉するだけでなく、
FocusEventを捕捉してキーボードフォーカスが失われるタイミングで値の確定を行うようにする。

...snip
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.GroupLayout;
...snip

public class SquarePerimeter implements Runnable, ActionListener, FocusListener {
    private JTextField side = new JTextField("0", 10);
...snip
        side.addActionListener(this);
        side.addFocusListener(this);
        peri.addActionListener(this);
        peri.addFocusListener(this);
        f.pack();
...snip
    public void actionPerformed(ActionEvent e) {
        perform(e.getSource());
    }

    public void focusGained(FocusEvent e) {}

    public void focusLost(FocusEvent e) {
        if (e.isTemporary()) return;
        perform(e.getSource());
    }

    private void perform(Object source) {
        if (source == side) {
            try {
                double s = Double.parseDouble(side.getText());
                if (s < 0) throw new NumberFormatException("invalid negative value: " + s);
                peri.setText(String.valueOf(s * 4));
            } catch (NumberFormatException ex) {
                side.setText(String.valueOf(Double.parseDouble(peri.getText()) / 4));
            }
        } else if (source == peri) {
            try {
                double p = Double.parseDouble(peri.getText());
                if (p < 0) throw new NumberFormatException("invalid negative value: " + p);
                side.setText(String.valueOf(p / 4));
            } catch (NumberFormatException ex) {
                peri.setText(String.valueOf(Double.parseDouble(side.getText()) * 4));
            }
        }
    }
...snip

actionPerformed()とfocusLost()で同じ処理を行うので、
処理をperform()として括りだして、それを両者から呼び出すようにしている。
focusLost()中の

        if (e.isTemporary()) return;

は、キーボードフォーカスが失われた原因がもう一方のJTextFieldにフォーカスが移ったためでなく、
他のアプリケーションがアクティブになる等、再びこのアプリケーションがアクティブになった場合に、
フォーカスが元のJTextFieldに戻ることになるような一時的なフォーカスロストでは値を確定しないようにするためである。
どのようなフォーカスの失われ方でも値を確定する動作になってほしい場合はこの行は要らない。

これで正方形の一辺の長さと周の長さを算出するアプリケーションができあがった。