13

In order to have custom button captions in an input dialog, I created the following code:

String key = null;
JTextField txtKey = new JTextField();        
int answerKey = JOptionPane.showOptionDialog(this, new Object[] {pleaseEnterTheKey, txtKey}, decryptionKey, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] {okCaption, cancelCaption}, okCaption);        
if (answerKey == JOptionPane.OK_OPTION && txtKey.getText() != null) {
  key = txtKey.getText();
}

How can I move the focus (cursor) to the text field as the dialog is displayed?

UPDATE

This does not work for me, I mean the textfield has no focus: OS: Fedora - Gnome

public class Test {
  public static void main(String[] args) {
    String key = null;
    JTextField txtKey = new JTextField();
    txtKey.addAncestorListener(new RequestFocusListener());
    int answerKey = JOptionPane.showOptionDialog(null, new Object[]{"Please enter the key:", txtKey}, "Title", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[]{"OKKK", "CANCELLLL"}, "OKKK");
    if (answerKey == JOptionPane.OK_OPTION && txtKey.getText() != null) {
      key = txtKey.getText();
    }
  }
}
ehsun7b
  • 4,796
  • 14
  • 59
  • 98

8 Answers8

15

Dialog Focus shows how you can easily set the focus on any component in a modal dialog.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Very nice article but unfortunately it won't help me since the requestFocus for the default button will occur after gaining the focus by the TextField using AncestorListener. :( – ehsun7b Jun 06 '11 at 19:58
  • 1
    @ehsun7b, Works fine for my using JDK6_7 on XP. Post your SSCCE (http://sscce.org) that demonstrates your problem. The code you posted is not a SSCCE since we don't know what the value of all your varialbes is and it doesn't compile and there is no main() method. – camickr Jun 06 '11 at 20:33
  • 2
    @ehsun7b, Your posted code works for me so maybe their is a difference in event processing between the two systems. Did you add any output to make sure the event listener is invoked? If you read the blog, someone suggested that an HierarchyListener can also be used. Try that to see what happens. Or another option is to wrap the code from the AncestorListener in a SwingUtltities.invokeLater(). This will add the code to the end of the EDT so that is hopefully executes after focus has been placed on the button. – camickr Jun 08 '11 at 00:34
12
    public static String getPassword(String title) {
        JPanel panel = new JPanel();
        final JPasswordField passwordField = new JPasswordField(10);
        panel.add(new JLabel("Password"));
        panel.add(passwordField);
        JOptionPane pane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION) {
            @Override
            public void selectInitialValue() {
                passwordField.requestFocusInWindow();
            }
        };
        pane.createDialog(null, title).setVisible(true);
        return passwordField.getPassword().length == 0 ? null : new String(passwordField.getPassword());
    }
Frank M.
  • 997
  • 1
  • 10
  • 19
  • You should call dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE) before dialog.setVisible(true) and dialog.dispose() afterwards – bourne2program Oct 28 '20 at 22:03
6

passing null as the last argument is the solution. At least it worked for me.

String key = null;
JTextField txtKey = new JTextField();        
int answerKey = JOptionPane.showOptionDialog(this, new Object[] {pleaseEnterTheKey, txtKey}, decryptionKey, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] {okCaption, cancelCaption}, null);        
if (answerKey == JOptionPane.OK_OPTION && txtKey.getText() != null) {
  key = txtKey.getText();
}

But even this solution bring another problem:

Focused component and Default component are different. Default component or default button is the button which its onclick fires if you press ENTER KEY.The last argument define the default component which gets the focus too and passing null brings the problem of having no default component! I solved it for my code this way but I guess it is not a best practice:

String key = null;
    final JTextField txtKey = new JTextField();
    txtKey.addKeyListener(new KeyAdapter() {

      @Override
      public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        if (keyCode == 10) { //enter key
          Container parent = txtKey.getParent();              
          while (!(parent instanceof JOptionPane)) {
            parent = parent.getParent();
          }

          JOptionPane pane = (JOptionPane) parent;
          final JPanel pnlBottom = (JPanel) pane.getComponent(pane.getComponentCount() - 1);
          for (int i = 0; i < pnlBottom.getComponents().length; i++) {
            Component component = pnlBottom.getComponents()[i];
            if (component instanceof JButton) {
              final JButton okButton = ((JButton)component);
              if (okButton.getText().equalsIgnoreCase(okCaption)) {
                ActionListener[] actionListeners = okButton.getActionListeners();
                if (actionListeners.length > 0) {
                  actionListeners[0].actionPerformed(null);
                }
              }
            }
          }
        }
      }

    });
ehsun7b
  • 4,796
  • 14
  • 59
  • 98
4

I had the same problem with the RequestFocusListener() not working on Linux, after following the discussion on http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5018574 I found that adding an invokeLater fixed it for now...

public class RequestFocusListener implements AncestorListener
{
public void ancestorAdded(final AncestorEvent e)
{
    final AncestorListener al= this;   
    SwingUtilities.invokeLater(new Runnable(){

        @Override
        public void run() {
            JComponent component = (JComponent)e.getComponent();
            component.requestFocusInWindow();
            component.removeAncestorListener( al );
        }
    });
}

public void ancestorMoved(AncestorEvent e) {}
public void ancestorRemoved(AncestorEvent e) {}
}
Jim Morris
  • 2,870
  • 24
  • 15
3

The trick is to (a) use an AncestorListener on the text component to request focus, and when the focus is lost again (given to the default button), ask for focus a second time using a FocusListener on the text component (but don't keep asking for focus after that):

final JPasswordField accessPassword = new JPasswordField();

accessPassword.addAncestorListener( new AncestorListener()
{
  @Override
  public void ancestorRemoved( final AncestorEvent event )
  {
  }
  @Override
  public void ancestorMoved( final AncestorEvent event )
  {
  }
  @Override
  public void ancestorAdded( final AncestorEvent event )
  {
    // Ask for focus (we'll lose it again)
    accessPassword.requestFocusInWindow();
  }
} );

accessPassword.addFocusListener( new FocusListener()
{
  @Override
  public void focusGained( final FocusEvent e )
  {
  }
  @Override
  public void focusLost( final FocusEvent e )
  {
    if( isFirstTime )
    {
      // When we lose focus, ask for it back but only once
      accessPassword.requestFocusInWindow();
      isFirstTime = false;
    }
  }
  private boolean isFirstTime = true;
} );
2

Better way to do it: create the JOptionPane using the constructor, override selectInitialValue to set the focus, and then build the dialog using createDialog.

// Replace by the constructor you want
JOptionPane pane = new JOptionPane(panel, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION) {
  @Override
  public void selectInitialValue() {
    textArea.requestFocusInWindow();
  }
};

JDialog dialog = pane.createDialog(owner, title);
dialog.setVisible(true);
ceklock
  • 6,143
  • 10
  • 56
  • 78
1

Try this

String key = null;
JTextField txtKey = new JTextField();
Object[] foo = {pleaseEnterTheKey, txtKey};      
int answerKey = JOptionPane.showOptionDialog(this, foo, decryptionKey, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] {okCaption, cancelCaption}, foo[1]);        
if (answerKey == JOptionPane.OK_OPTION && txtKey.getText() != null) {
  key = txtKey.getText();
}
mre
  • 43,520
  • 33
  • 120
  • 170
  • I guess the first solution is suits me since I need to have a textfield and let the user to type his/her input as text. – ehsun7b Jun 06 '11 at 13:08
  • @ehsun7b, the second solution is suitable for you as well. with respect to the code snippet I included, just include `txtKey` as an item of `options`, and then point to it in the final argument `options[indexOfTextField]`. I've tested it, and it works just fine. – mre Jun 06 '11 at 13:12
  • The componentShown never execute! – ehsun7b Jun 06 '11 at 13:13
  • @ehsun7b, it should, otherwise you wouldn't see `txtKey` in the display. anyway, I'd recommend you go with the second option I presented you with. it's been tested and verified. – mre Jun 06 '11 at 13:14
  • @mre: I have tried the second solution before, the problem with that is: It shows the textfield besides the buttons, not in the middle of the dialog. :) – ehsun7b Jun 06 '11 at 13:14
  • @ehsun7b, please see my edit. I think that should suffice. :) – mre Jun 06 '11 at 13:20
  • Your answers guided me to the real solution. :) – ehsun7b Jun 06 '11 at 13:21
  • @ehsun7b, I'm not sure if passing `null` is really the "best" solution, but I'm glad it's working now. – mre Jun 06 '11 at 13:23
  • You know I guess passing null could be a platform dependent solution. I mean in some environments the first input gets the focus. But passing the textField as the last argument and doing nothing else seems to be the best solution in all platforms. – ehsun7b Jun 06 '11 at 13:40
  • @mre: Please read my update in my answer. I still think your first solution (`componentAdapter`) is the best solution if it works. Since it leaves the OK button as the default button but the focus will goes to the `TextField` but it doesn't work. – ehsun7b Jun 06 '11 at 14:23
0

I found a solution ! Very primitive, but works.

Just jump to the field by java.awt.Robot using key "Tab".
I've created utils method calls "pressTab(..)" For example:

GuiUtils.pressTab(1);   <------------- // add this method before popup show

int result = JOptionPane.showConfirmDialog(this, inputs, "Text search window", JOptionPane.PLAIN_MESSAGE);
if (result == JOptionPane.OK_OPTION)
{
    
}

If you should press multiple times on "Tab" to get your Component you can use below method:

GUIUtils.pressTab(3);

Definition:

public static void pressTab(int amountOfClickes)
{
    SwingUtilities.invokeLater(new Runnable()
    {
        public void run()
        {
            try
            {
                Robot robot = new Robot();
                int i = amountOfClickes;
                while (i-- > 0)
                {
                    robot.keyPress(KeyEvent.VK_TAB);
                    robot.delay(100);
                    robot.keyRelease(KeyEvent.VK_TAB);
                }
            }
            catch (AWTException e)
            {
                System.out.println("Failed to use Robot, got exception: " + e.getMessage());
            }
        }
    });
}

If your Component location is dynamic, you can run over the while loop without limitation, but add some focus listener on the component, to stop the loop once arrived to it.

Adir Dayan
  • 1,308
  • 13
  • 21