Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the proper way to set initially focused component for a dialog in Swing?

I have a bug lurking where the focus is sporadically not ending up on the text field I want it on after popping up a JOptionPane containing the text field.

I eventually boiled it down to a reasonable example:

import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.Component;

public class FocusIssueTest {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                // The real thing has many more components in there,
                // but I removed them for the demo.
                MyInputPane myInputPane = new MyInputPane();
                myInputPane.showDialog(null);
            }
        });
    }

    public static class MyInputPane extends JPanel {
        private final JTextField textField;

        protected MyInputPane() {
            textField = new JTextField();
            textField.selectAll();
            textField.setColumns(30);

            setLayout(new BorderLayout());
            add(textField, BorderLayout.CENTER);
        }

        public boolean showDialog(Component parentComponent) {

            final JOptionPane optionPane = new JOptionPane(
                this, JOptionPane.PLAIN_MESSAGE,
                JOptionPane.OK_CANCEL_OPTION);

            JDialog dialog = optionPane.createDialog(
                parentComponent, "Select a Thing");

            /* Attempted solution #1 - wait until the window is active
            dialog.addWindowListener(new WindowAdapter() {
                @Override
                public void windowActivated(WindowEvent event) {
                    textField.requestFocusInWindow();
                }
            });
            */

            /* Attempted solution #2 - camickr's RequestFocusListener
            textField.addAncestorListener(new AncestorListener() {
                @Override
                public void ancestorAdded(AncestorEvent event) {
                    JComponent component = event.getComponent();
                    component.requestFocusInWindow();
                    component.removeAncestorListener(this);
                }

                @Override
                public void ancestorRemoved(AncestorEvent event) {

                }

                @Override
                public void ancestorMoved(AncestorEvent event) {

                }
            });
            */

            /* Attempted solution #3 - HierarchyListener
            textField.addHierarchyListener(new HierarchyListener() {
                @Override
                public void hierarchyChanged(HierarchyEvent event) {
                    Component component = event.getComponent();
                    if ((HierarchyEvent.SHOWING_CHANGED &
                         event.getChangeFlags()) != 0 &&
                            component.isShowing()) {
                        component.requestFocusInWindow();
                        component.removeHierarchyListener(this);
                    }
                }
            });
            */

            // Attempted solution #4 - appears to work but can't be
            // right, because eww.
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            SwingUtilities.invokeLater(new Runnable() {
                                @Override
                                public void run() {
                                    textField.requestFocusInWindow();
                                }
                            });
                        }
                    });
                }
            });

            dialog.setVisible(true);
            Object selectedValue = optionPane.getValue();
            return selectedValue instanceof Integer &&
                   (int) selectedValue == 0;
        }
    }
}

Attempted solutions #1 through #3 all fail to put the focus in the text field. Attempted solution #4 works but having to use three levels of nested SwingUtilities.invokeLater calls can't possibly be the proper way to do this.

So what is the proper way?

I notice that JOptionPane.showInputDialog' text field does receive focus, so clearly there is a way to do it.

like image 517
Hakanai Avatar asked Sep 15 '25 16:09

Hakanai


2 Answers

I don't know the canonical solution, but you could try just one call to queue on the event thread within your window listener. For e.g.,

     dialog.addWindowListener(new WindowAdapter() {
        @Override
        public void windowActivated(WindowEvent event) {
           SwingUtilities.invokeLater(new Runnable() {
              public void run() {
                 textField.requestFocusInWindow();
              }
           });
        }
     });

Other possible "kludges" include using a short single-run Swing timer.

like image 153
Hovercraft Full Of Eels Avatar answered Sep 18 '25 11:09

Hovercraft Full Of Eels


Have you considered subclassing / initializing a JDialog directly instead of using JOptionPane ?

The reason why java does not have a "setFocusInWindow" is because on some platforms it is not possible to directly "set" the focus (as much as request it. )

To me it seems like the call to "setVisible()" is putting a event on the EDT, to make the window visible, which in turn is changing the focus away from your text field.

like image 39
Damian Nikodem Avatar answered Sep 18 '25 09:09

Damian Nikodem