在JOptionPane.showOptionDialog()中设置组件焦点

13
为了在输入对话框中具有自定义按钮标题,我创建了以下代码:
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();
}

如何在对话框显示时将焦点(光标)移动到文本字段?

更新

这对我不起作用,我的意思是文本字段没有焦点: 操作系统: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();
    }
  }
}

1
@ehsun7b,有什么问题吗? - mre
@mre,我该如何将光标移动到文本字段? - ehsun7b
请将您的建议发布为答案,以便我可以接受它。 :) - ehsun7b
@ehsun7b,请尝试实现我之前提出的建议。 - mre
我添加了一个答案,似乎修复了使用RequestFocusListener()的最佳解决方案。 - Jim Morris
显示剩余2条评论
8个回答

15

Dialog Focus展示了如何在模态对话框中轻松设置任何组件的焦点。


非常好的文章,但不幸的是它对我没有帮助,因为使用AncestorListener时,default button的requestFocus将在TextField获得焦点之后发生。 :( - ehsun7b
1
@ehsun7b,我在XP上使用JDK6_7时运行良好。请发布一个SSCCE(http://sscce.org),以演示您的问题。您发布的代码不是SSCCE,因为我们不知道所有变量的值,它也无法编译,并且没有main()方法。 - camickr
2
@ehsun7b,您发布的代码对我来说可以工作,所以可能是两个系统之间事件处理方式的差异。您是否添加了任何输出以确保事件监听器被调用?如果您读了博客文章,有人建议还可以使用HierarchyListener。尝试一下看会发生什么。另一个选项是将来自AncestorListener的代码包装在SwingUtltities.invokeLater()中。这将把代码添加到EDT的末尾,以便在焦点放在按钮上后希望执行。 - camickr

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());
    }

在调用dialog.setVisible(true)之前,您应该调用dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE),并在之后调用dialog.dispose()。 - bourne2program

6
将 null 作为最后一个参数传递是解决方案。至少对我而言是有效的。
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();
}

但是,即使这个解决方案解决了一个问题:

焦点组件和默认组件是不同的。默认组件或默认按钮是当你按下ENTER KEY时触发其onclick事件的按钮。最后一个参数定义了获取焦点的默认组件,而传递null会带来没有默认组件的问题!我通过以下方式解决了我的代码,但我认为这不是最佳实践:

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);
                }
              }
            }
          }
        }
      }

    });

4

我在Linux上遇到了RequestFocusListener()不起作用的问题,经过阅读http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5018574中的讨论后,发现添加一个invokeLater可以暂时解决这个问题...

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) {}
}

请看这里:https://dev59.com/mFnUa4cB1Zd3GeqPYDL- - mKorbel
是的,这也适用于那个解决方案。 - Jim Morris

3
窍门在于:(a)在文本组件上使用AncestorListener请求焦点,并在焦点再次丢失时(给默认按钮),使用文本组件上的FocusListener再次请求焦点(但不要继续请求焦点):
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
更好的方法是:使用构造函数创建JOptionPane,重写selectInitialValue以设置焦点,然后使用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);

1

试试这个

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();
}

@ehsun7b,第二个解决方案也适合您。关于我包含的代码片段,请将txtKey作为options的一个项目包含在内,然后在最终参数options[indexOfTextField]中指向它。我已经测试过了,它可以正常工作。 - mre
@ehsun7b,如果不是这样的话,你就看不到显示中的 txtKey 了。无论如何,我建议你选择我给你提供的第二个选项。它已经经过测试和验证。 - mre
@mre:我之前尝试过第二种解决方案,但问题是:它将文本字段显示在按钮旁边,而不是对话框中间。 :) - ehsun7b
@ehsun7b,请看一下我的编辑。我认为那应该就够了。 :) - mre
你的回答引导我找到了真正的解决方案。 :) - ehsun7b
显示剩余5条评论

0

我找到了一个解决方案! 非常原始,但是有效。

只需要使用键盘“Tab”通过java.awt.Robot跳转到字段即可。
我创建了一个工具方法调用“pressTab(..)” 例如:

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)
{
    
}

如果您需要按多次“Tab”才能获取到您的组件,您可以使用以下方法:
GUIUtils.pressTab(3);

定义:

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());
            }
        }
    });
}

如果您的组件位置是动态的,您可以无限制地运行while循环,但需要在组件上添加一些焦点监听器,在到达组件时停止循环。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接