Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java LostFocus and InputVerifier, moving in reverse-tab-order

Tags:

java

focus

swing

I have a GUI application that uses an InputVerifier to check the content of text fields before yielding the focus. This is all very normal. Yesterday, however, discovered a problem - it seems to be a bug, but I cannot find any mention of it anywhere. Before I report this as a bug, I thought I would ask: am I missing something obvious here?

Situation:

  • A set of text fields with InputVerifiers.
  • Listeners for FocusLost and FocusGained on all controls, so I can see what is happening.
  • A separate thread uses a DefaultKeyboardFocusManager to report (every 2 seconds) which control has the focus.
  • I place invalid data in a JTextField in the middle of the form, and try to leave the control.

If I try to move away from this control using the mouse, or using the tab-key, I cannot. The FocusLost event does not fire and the control properly retains the focus.

However, if I try to move away from the control in reverse tab order, using Shift-Tab, sometimes the FocusLost event fires. If this happens, the separate thread reports that no control has the focus, i.e., getFocusOwner() returns null.


Edit: below is a small sample program that shows the problem. The problem has nothing to do with the extra thread - the thread is just there to make the problem more obvious. If there is a race-condition, it is somewhere in Swing.

To see the problem, go to the second text box and empty it. The control should retain the focus, and does so unless you leave it by pressing shift-tab. Unlike the full application, the error seems to occur here 100% of the time. This is true both under OpenJDK 6 and under Oracle Java 7.

This is almost too obvious to be a bug, plus it happens in multiple Java environments. Hence, my suspicion that I am missing something obvious. Anyone?

public class FocusBugDemo extends JFrame {
    static JTextField txtOne = new JTextField("Ignore this control");
    static JTextField txtTwo = new JTextField("Delete this text, then press shift-tab");
    static JLabel lblFocus = new JLabel("");
    static KeyboardFocusManager kfm = new DefaultKeyboardFocusManager();

    public static void main(String[] args) {
        new FocusBugDemo();
        Thread t = new Thread() {
            @Override
            public void run() {
                while(true) {
                    Component c = kfm.getFocusOwner();
                    String focusInfo = "elsewhere";
                    if (c == null) {                        focusInfo = "null";
                    } else if (c == txtOne) {       focusInfo = "txtOne";
                    } else if (c == txtTwo) {       focusInfo = "txtTwo";
                    }
                    lblFocus.setText(System.currentTimeMillis() + " - Focus owner " + focusInfo);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        };
        t.start();
    }

    private FocusBugDemo() {
        super("Focus bug demo");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setPreferredSize(new Dimension(300,100));
        setLayout(new GridLayout(3,1));
        NotEmpty validator = new NotEmpty();
        txtOne.setInputVerifier(validator);
        txtTwo.setInputVerifier(validator);
        add(txtOne);
        add(txtTwo);
        add(lblFocus);
        pack();
        setVisible(true);
    }

    private class NotEmpty extends InputVerifier {
        @Override
        public boolean verify(JComponent input) {
            JTextField txtField = (JTextField) input;
            return (txtField.getText().length() > 0);
        }
    }
}
like image 736
Brad Richards Avatar asked May 20 '26 15:05

Brad Richards


2 Answers

Now reported to Oracle as bug 7167871.

like image 153
Brad Richards Avatar answered May 23 '26 04:05

Brad Richards


Using your sscce, I am unable to reproduce the effect you describe on Mac OS X, Java 6, which supports @CatalinaIsland's observation. In particular, focus never leaves an empty text field using either tab or shift-tab; focus becomes null only when the frame is deactivated.

I see two threads accessing multiple fields with no synchronization at all. At a minimum, you should use EventQueue.invokeLater() in t to update the GUI, as described in Concurrency in Swing and show below.

The broader question is this: What focus problem are you trying to solve using t?

import java.awt.Component;
import java.awt.DefaultKeyboardFocusManager;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

public class FocusDemo {

    private static final JTextField txtOne =
        new JTextField("Ignore this control");
    private static final JTextField txtTwo =
        new JTextField("Delete this text, then press shift-tab");
    private static final JLabel lblFocus = new JLabel("");

    public static void main(String[] args) {
        new FocusDemo();
        Thread t = new Thread() {

            @Override
            public void run() {
                while (true) {
                    EventQueue.invokeLater(new Runnable() {

                        KeyboardFocusManager kfm =
                            new DefaultKeyboardFocusManager();

                        @Override
                        public void run() {
                            Component c = kfm.getFocusOwner();
                            String focusInfo = "elsewhere";
                            if (c == null) {
                                focusInfo = "null";
                            } else if (c == txtOne) {
                                focusInfo = "txtOne";
                            } else if (c == txtTwo) {
                                focusInfo = "txtTwo";
                            }
                            lblFocus.setText(System.currentTimeMillis()
                                + " - Focus owner " + focusInfo);
                        }
                    });
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace(System.err);
                    }
                }
            }
        };
        t.start();
    }

    private FocusDemo() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame f = new JFrame("Focus bug demo");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setPreferredSize(new Dimension(300, 100));
                f.setLayout(new GridLayout(3, 1));
                NotEmpty validator = new NotEmpty();
                txtOne.setInputVerifier(validator);
                txtTwo.setInputVerifier(validator);
                f.add(txtOne);
                f.add(txtTwo);
                f.add(lblFocus);
                f.pack();
                f.setVisible(true);
            }
        });
    }

    private class NotEmpty extends InputVerifier {

        @Override
        public boolean verify(JComponent input) {
            JTextField txtField = (JTextField) input;
            return (txtField.getText().length() > 0);
        }
    }
}
like image 29
trashgod Avatar answered May 23 '26 03:05

trashgod