tags:

views:

72

answers:

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);
    };
}
+1  A: 

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.

JSBangs
Please see updated code sample.
Konrad Garus
A: 

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
+1  A: 

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