3

How can I instruct my Swing component to grab focus right now? requestFocus() doesn't seem to be dispatched instantly.

Ideally, I would like this (ran from EDT):

textInput.requestFocusInWindow();
System.out.println(textInput.hasFocus());

To print true.

Below is the SSCCE. Notes/requirements:

  1. Table is navigated with keyboard. Column C2 has a compound editor.
  2. When I type a letter in column C2, editing starts. Text component inside the compound editor gains focus. It needs to type the letter that started the editor. Implementation of this point is marked with comments saying "Trick".
  3. The text field is a 3rd party editor that has a focus listener interfering with my code. Here it's simulated as selectAll().

Presently the order of dispatch is: Type letter into text component, then dispatch focus listeners. Then the next letters are dispatched correctly because it's the text field what has focus.

I need it to set focus on the text component, dispatch focus listeners, and then pass key event to it.


public class JTableIssue extends JFrame {
    public JTableIssue() {
        JTable table = new JTable(new Object[][] {
                { "Apple", "Orange", "Strawberry" },
                { "Pineapple", "Orange", "Zergz" } }, new Object[] { "C1",
                "C2", "C3" });
        table.getColumn("C2").setCellEditor(new MyEditor());
        add(new JScrollPane(table));
        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new JTableIssue().setVisible(true);
    }
}

class MyEditor extends AbstractCellEditor implements TableCellEditor {
    MyTextField textField = new MyTextField();
    JPanel panel;

    MyEditor() {
        panel = new JPanel(new BorderLayout()){
            // Trick: Pass all key typed to text field
            @Override
            protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
                    int condition, boolean pressed) {
                if (ks.getKeyEventType() == KeyEvent.KEY_TYPED) {
                    textField.processKeyBinding(ks, e, condition, pressed);
                }
                return super.processKeyBinding(ks, e, condition, pressed);
            }
        };
        textField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                textField.selectAll();
            }
        });
        panel.add(textField, BorderLayout.CENTER);
        // Trick: Pass focus to text field when editor is added to table
        panel.addAncestorListener(new AncestorListener() {
            public void ancestorRemoved(AncestorEvent event) {
            }

            public void ancestorMoved(AncestorEvent event) {
            }

            public void ancestorAdded(AncestorEvent event) {
                textField.requestFocus();
            }
        });
    }

    public Object getCellEditorValue() {
        return textField.getText();
    }

    public Component getTableCellEditorComponent(JTable table, Object value,
            boolean isSelected, int row, int column) {
        textField.setText(value.toString());
        return panel;
    }
}

class MyTextField extends JTextField {
    // Trick: "public"
    @Override
    public boolean processKeyBinding(javax.swing.KeyStroke ks,
            java.awt.event.KeyEvent e, int condition, boolean pressed) {
        return super.processKeyBinding(ks, e, condition, pressed);
    };
}
Konrad Garus
  • 53,145
  • 43
  • 157
  • 230
  • What is the actual problem you are having? Post your SSCCE (http://sscce.org) showing the problem an maybe someone can provide a solution. I would guess you could use a FocusListener, but without more details I can't say for sure. – camickr Aug 02 '10 at 16:37
  • @camickr I updated the question with SSCCE. – Konrad Garus Aug 02 '10 at 18:13

3 Answers3

2

I figured it out.

  1. Events in AncestorListener and processKeyBinding() are a part of handling the same event: "key typed".
  2. Apparently the only way to grab focus is requestFocus(), which is added to event queue after the current stream of events triggered by "key typed". So grabbing focus and executing FocusListeners will always be executed later.

The solution is: In processKeyBinding(), don't pass the key to the inner component immediately. Enqueue it in event queue, so that it's performed after focus transfer and listeners. That is, wrap:

if (ks.getKeyEventType() == KeyEvent.KEY_TYPED) {
    textField.processKeyBinding(ks, e, condition, pressed);
}

Into SwingUtilities.invokeLater().

Konrad Garus
  • 53,145
  • 43
  • 157
  • 230
0

I don't think that this is possible. UI actions are inherently asynchronous (although usually very fast), and there's no way to force them to behave synchronously. If you really need this, you can fire an event in the text input's focus handler, and wait on that event in the other thread.

JSBձոգչ
  • 40,684
  • 18
  • 101
  • 169
  • This isn’t very informative. State changes in response to user input are typically *synchronous* once the event has begun dispatching (e.g. JButton state changes when mousePressed is dispatched). It is an aberration that focus changes have to go back onto the EventQueue in AWT. – yonran Jun 29 '12 at 20:19
0

I don't see how to control the order of events and in general that is not something you should be trying to do.

3.The text field is a 3rd party editor that has a focus listener interfering with my code

Maybe you can remove the FocusListener from the editor. Maybe you can then invoke the listener directly by using

savedFocusListener.focusGained(FocusEvent);

in the AncestorListener code that set focus on the text field.

camickr
  • 321,443
  • 19
  • 166
  • 288