在setEnabled(true)之后,JButton不会立即获得焦点

我正在尝试创建一个直观的用户界面,用户可以在其中输入数值到JTextFields ,使用TAB键前进,最后激活按钮开始处理输入。

在开始时,该按钮被禁用,只有在将所有数据输入文本字段时才应启用该按钮。

我使用javax.swing.InputVerifier来限制只输入最多4位小数的正数,并且工作正常。

有3个可聚焦对象,两个文本字段和按钮。 在文本字段中键入(有效)数字后按TAB键,如果所有文本字段都包含有效输入,则启用该按钮。 这也很好。

问题是:
在第一个文本字段已包含有效数据并按下TAB后 ,将有效数据键入第二个文本字段后, 该按钮无法获得焦点 。 相反,焦点被转移到行中的下一个可聚焦对象,该行是(再次)第一个文本字段。

我尝试使用两种不同的方法:

  1. 按钮的enabled属性通过覆盖focusLost()方法内的FocusListener更改
  2. 按钮的enabled属性在overriden shouldYieldFocus()方法中更改

在这两种情况下,焦点在启用按钮后立即跳过按钮 。 但是,如果我们继续使用TABSHIFT + TAB键更改焦点,则按钮会在第二个文本字段后立即获得焦点。

在我看来,在启用按钮之前已经预先确定了opposite组件,因此即使在启用按钮后按钮也不会获得焦点。

我甚至试图在启用按钮后使用requestFocusInWindow()强制按钮获得焦点,但是这也没有用,所以问题是如何强制 LayoutFocusTraversalPolicy 重新评估布局,以便它可以立即考虑到新的介绍按钮是在禁用之前?

以下是我尝试的两种方法的代码:

  1. 按钮的enabled属性通过focusLost()方法中的FocusListener更改:
 package verifiertest; import java.awt.EventQueue; import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.BorderLayout; import java.awt.Dimension; import javax.swing.border.EmptyBorder; import javax.swing.border.TitledBorder; import javax.swing.text.JTextComponent; import javax.swing.UIManager; import java.awt.GridLayout; import java.awt.Toolkit; import java.math.BigDecimal; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.SwingConstants; import javax.swing.JTextField; import javax.swing.InputVerifier; import javax.swing.JButton; import javax.swing.JComponent; import java.awt.FlowLayout; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.ActionEvent; public class TestVerifier implements FocusListener { private JFrame frmInputverifierTest; private JTextField tfFirstNum; private JTextField tfSecondNum; private JLabel lblStatus; private JButton btnStart; private String statusText = "Input the numbers and press the \"Start!\" button..."; public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { TestVerifier window = new TestVerifier(); window.frmInputverifierTest.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } public TestVerifier() { initialize(); } private void initialize() { frmInputverifierTest = new JFrame(); frmInputverifierTest.setTitle("InputVerifier Test"); frmInputverifierTest.setBounds(100, 100, 500, 450); frmInputverifierTest.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // center the window Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); frmInputverifierTest.setLocation(dim.width/2 - frmInputverifierTest.getWidth()/2, dim.height/2 - frmInputverifierTest.getHeight()/2); JPanel panelContainer = new JPanel(); panelContainer.setBorder(new EmptyBorder(5, 5, 5, 5)); frmInputverifierTest.getContentPane().add(panelContainer, BorderLayout.CENTER); panelContainer.setLayout(new BorderLayout(0, 0)); JPanel panelInput = new JPanel(); panelInput.setBorder(new TitledBorder(null, "Input", TitledBorder.LEADING, TitledBorder.TOP, null, null)); panelContainer.add(panelInput, BorderLayout.NORTH); panelInput.setLayout(new GridLayout(2, 2, 10, 4)); JLabel lblFirstNum = new JLabel("Number #1:"); lblFirstNum.setHorizontalAlignment(SwingConstants.TRAILING); panelInput.add(lblFirstNum); tfFirstNum = new JTextField(); panelInput.add(tfFirstNum); tfFirstNum.setColumns(10); // setup the verifier MyTxtVerifier txtVerifier = new MyTxtVerifier(); tfFirstNum.setInputVerifier(txtVerifier); // add focus listener tfFirstNum.addFocusListener(this); JLabel lblSecondNum = new JLabel("Number #2:"); lblSecondNum.setHorizontalAlignment(SwingConstants.TRAILING); panelInput.add(lblSecondNum); tfSecondNum = new JTextField(); panelInput.add(tfSecondNum); tfSecondNum.setColumns(10); // setup the verifier tfSecondNum.setInputVerifier(txtVerifier); // add focus listener tfSecondNum.addFocusListener(this); JPanel panelOutput = new JPanel(); panelOutput.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Output (not used at the moment)", TitledBorder.LEADING, TitledBorder.TOP, null, null)); panelContainer.add(panelOutput, BorderLayout.CENTER); JPanel panelSouth = new JPanel(); panelSouth.setBorder(null); panelContainer.add(panelSouth, BorderLayout.SOUTH); panelSouth.setLayout(new GridLayout(0, 1, 0, 0)); JPanel panelStatus = new JPanel(); FlowLayout flowLayout_1 = (FlowLayout) panelStatus.getLayout(); flowLayout_1.setAlignment(FlowLayout.LEFT); panelStatus.setBorder(new TitledBorder(null, "Status", TitledBorder.LEADING, TitledBorder.TOP, null, null)); panelSouth.add(panelStatus); lblStatus = new JLabel(statusText); panelStatus.add(lblStatus); JPanel panelActions = new JPanel(); panelActions.setBorder(new TitledBorder(null, "Actions", TitledBorder.LEADING, TitledBorder.TOP, null, null)); FlowLayout flowLayout = (FlowLayout) panelActions.getLayout(); flowLayout.setAlignment(FlowLayout.RIGHT); panelSouth.add(panelActions); btnStart = new JButton("Start!"); btnStart.setEnabled(false); btnStart.setVerifyInputWhenFocusTarget(true); btnStart.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(frmInputverifierTest, "Start button pressed...", "Start", JOptionPane.PLAIN_MESSAGE); } }); panelActions.add(btnStart); } // an inner class so it can access parent fields public class MyTxtVerifier extends InputVerifier { // This method should have no side effects @Override public boolean verify(JComponent input) { String text = ((JTextField)input).getText(); // to allow changing focus when nothing is entered if(text.isEmpty()) return true; try { BigDecimal value = new BigDecimal(text); if(value.floatValue() <= 0.0) return false; return (value.scale() <= 4); } catch (Exception e) { return false; } } // This method can have side effects @Override public boolean shouldYieldFocus(JComponent input) { String statusOld, status; statusOld = statusText; // remember the original text boolean isOK = verify(input); // call overridden method if(isOK) status = statusOld; else { btnStart.setEnabled(false); status = "Error: The parameter should be a positive number up to 4 decimal places"; } lblStatus.setText(status); // return super.shouldYieldFocus(input); return isOK; } } @Override public void focusGained(FocusEvent e) { // nothing to do on focus gained } @Override public void focusLost(FocusEvent e) { // in case we want to show a message box inside focusLost() - not to be fired twice if(e.isTemporary()) return; final JTextComponent c = (JTextComponent)e.getSource(); // in case there are more text fields but // we are validating only some of them if(c.equals(tfFirstNum) || c.equals(tfSecondNum)) { // are all text fields valid? if(c.getInputVerifier().verify(tfFirstNum) && c.getInputVerifier().verify(tfSecondNum) && !tfFirstNum.getText().isEmpty() && !tfSecondNum.getText().isEmpty()) btnStart.setEnabled(true); else btnStart.setEnabled(false); } } } 
  1. 按钮的enabled属性在overriden shouldYieldFocus()方法中更改:
 package verifiertest; import java.awt.EventQueue; import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.BorderLayout; import java.awt.Dimension; import javax.swing.border.EmptyBorder; import javax.swing.border.TitledBorder; import javax.swing.UIManager; import java.awt.GridLayout; import java.awt.Toolkit; import java.math.BigDecimal; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.SwingConstants; import javax.swing.JTextField; import javax.swing.InputVerifier; import javax.swing.JButton; import javax.swing.JComponent; import java.awt.FlowLayout; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; public class TestVerifier { private JFrame frmInputverifierTest; private JTextField tfFirstNum; private JTextField tfSecondNum; private JLabel lblStatus; private JButton btnStart; private String statusText = "Input the numbers and press the \"Start!\" button..."; public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { TestVerifier window = new TestVerifier(); window.frmInputverifierTest.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } public TestVerifier() { initialize(); } private void initialize() { frmInputverifierTest = new JFrame(); frmInputverifierTest.setTitle("InputVerifier Test"); frmInputverifierTest.setBounds(100, 100, 500, 450); frmInputverifierTest.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // center the window Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); frmInputverifierTest.setLocation(dim.width/2 - frmInputverifierTest.getWidth()/2, dim.height/2 - frmInputverifierTest.getHeight()/2); JPanel panelContainer = new JPanel(); panelContainer.setBorder(new EmptyBorder(5, 5, 5, 5)); frmInputverifierTest.getContentPane().add(panelContainer, BorderLayout.CENTER); panelContainer.setLayout(new BorderLayout(0, 0)); JPanel panelInput = new JPanel(); panelInput.setBorder(new TitledBorder(null, "Input", TitledBorder.LEADING, TitledBorder.TOP, null, null)); panelContainer.add(panelInput, BorderLayout.NORTH); panelInput.setLayout(new GridLayout(2, 2, 10, 4)); JLabel lblFirstNum = new JLabel("Number #1:"); lblFirstNum.setHorizontalAlignment(SwingConstants.TRAILING); panelInput.add(lblFirstNum); tfFirstNum = new JTextField(); panelInput.add(tfFirstNum); tfFirstNum.setColumns(10); // setup the verifier MyTxtVerifier txtVerifier = new MyTxtVerifier(); tfFirstNum.setInputVerifier(txtVerifier); JLabel lblSecondNum = new JLabel("Number #2:"); lblSecondNum.setHorizontalAlignment(SwingConstants.TRAILING); panelInput.add(lblSecondNum); tfSecondNum = new JTextField(); panelInput.add(tfSecondNum); tfSecondNum.setColumns(10); // setup the verifier tfSecondNum.setInputVerifier(txtVerifier); JPanel panelOutput = new JPanel(); panelOutput.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Output (not used at the moment)", TitledBorder.LEADING, TitledBorder.TOP, null, null)); panelContainer.add(panelOutput, BorderLayout.CENTER); JPanel panelSouth = new JPanel(); panelSouth.setBorder(null); panelContainer.add(panelSouth, BorderLayout.SOUTH); panelSouth.setLayout(new GridLayout(0, 1, 0, 0)); JPanel panelStatus = new JPanel(); FlowLayout flowLayout_1 = (FlowLayout) panelStatus.getLayout(); flowLayout_1.setAlignment(FlowLayout.LEFT); panelStatus.setBorder(new TitledBorder(null, "Status", TitledBorder.LEADING, TitledBorder.TOP, null, null)); panelSouth.add(panelStatus); lblStatus = new JLabel(statusText); panelStatus.add(lblStatus); JPanel panelActions = new JPanel(); panelActions.setBorder(new TitledBorder(null, "Actions", TitledBorder.LEADING, TitledBorder.TOP, null, null)); FlowLayout flowLayout = (FlowLayout) panelActions.getLayout(); flowLayout.setAlignment(FlowLayout.RIGHT); panelSouth.add(panelActions); btnStart = new JButton("Start!"); btnStart.setEnabled(false); btnStart.setVerifyInputWhenFocusTarget(true); btnStart.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(frmInputverifierTest, "Start button pressed...", "Start", JOptionPane.PLAIN_MESSAGE); } }); panelActions.add(btnStart); } // an inner class so it can access parent fields public class MyTxtVerifier extends InputVerifier { // This method should have no side effects @Override public boolean verify(JComponent input) { String text = ((JTextField)input).getText(); // to allow changing focus when nothing is entered if(text.isEmpty()) return true; try { BigDecimal value = new BigDecimal(text); if(value.floatValue() <= 0.0) return false; return (value.scale() <= 4); } catch (Exception e) { return false; } } // This method can have side effects @Override public boolean shouldYieldFocus(JComponent input) { String statusOld, status; statusOld = statusText; // remember the original text boolean isOK = verify(input); // call overridden method if(isOK) status = statusOld; else { status = "Error: The parameter should be a positive number up to 4 decimal places"; } lblStatus.setText(status); setBtnState(input); // enable or disable the button //btnStart.requestFocusInWindow(); // <-- does not help // return super.shouldYieldFocus(input); return isOK; } } private void setBtnState(JComponent input) { if (input.equals(tfFirstNum) || input.equals(tfSecondNum)) { // are all text fields valid? if (input.getInputVerifier().verify(tfFirstNum) && input.getInputVerifier().verify(tfSecondNum) && !tfFirstNum.getText().isEmpty() && !tfSecondNum.getText().isEmpty()) btnStart.setEnabled(true); else btnStart.setEnabled(false); } } } 

以下是测试应用程序的屏幕截图:

测试应用程序截图

注意:
代码与我之前提出的问题中包含的代码有关,这是另一个主题。

编辑:
在尝试了接受答案的作者提出的建议(使用invokeLater()来运行requestFocusInWindow() )时,这里的代码可以作为概念certificate:

 @Override public void focusLost(FocusEvent e) { // in case we want to show a message box inside focusLost() - not to be fired twice if(e.isTemporary()) return; final JTextComponent c = (JTextComponent)e.getSource(); // in case there are more text fields but // we are validating only some of them if(c.equals(tfFirstNum) || c.equals(tfSecondNum)) { // are all text fields valid? if(c.getInputVerifier().verify(tfFirstNum) && c.getInputVerifier().verify(tfSecondNum) && !tfFirstNum.getText().isEmpty() && !tfSecondNum.getText().isEmpty()) btnStart.setEnabled(true); else btnStart.setEnabled(false); } if (btnStart.isEnabled() && e.getOppositeComponent()==tfFirstNum) { EventQueue.invokeLater(new Runnable() { @Override public void run() { btnStart.requestFocusInWindow(); } }); } } 

这只是与approach #01有关的改变的focusLost()方法。 我不知道是否有类似的approach #02approach #02 – 因为我不知道在没有FocusListener时是否可以从shouldYieldFocus()内部引用oppositeshouldYieldFocus()

注意:
使用此解决方案时,可以清楚地看到,在输入第二个数字并按下TAB按钮后,首先聚焦(暂时)跳转到第一个文本字段,然后再移动到按钮。

我建议你不要使用InputVerifier ,而是使用DocumentListener

使用DocumentListener的好处是可以在输入每个字符时编辑文本字段,因此用户可以立即获得反馈。 然后,只要您输入第一个数字,就可以启用该按钮(如果它通过了您的编辑标准)。

由于现在在用户尝试输入Tab键之前启用该按钮,因此您不会有任何焦点问题。

以下是启动您的基本示例:

 import java.awt.*; import java.awt.event.*; import java.util.List; import java.util.ArrayList; import javax.swing.*; import javax.swing.event.*; public class DataEntered implements DocumentListener { private JButton button; private List textFields = new ArrayList(); public DataEntered(JButton button) { this.button = button; } public void addTextField(JTextField textField) { textFields.add( textField ); textField.getDocument().addDocumentListener( this ); } public boolean isDataEntered() { for (JTextField textField : textFields) { if (textField.getText().trim().length() == 0) return false; } return true; } @Override public void insertUpdate(DocumentEvent e) { checkData(); } @Override public void removeUpdate(DocumentEvent e) { checkData(); } @Override public void changedUpdate(DocumentEvent e) {} private void checkData() { button.setEnabled( isDataEntered() ); } private static void createAndShowUI() { JButton submit = new JButton( "Submit" ); submit.setEnabled( false ); JTextField textField1 = new JTextField(10); JTextField textField2 = new JTextField(10); DataEntered de = new DataEntered( submit ); de.addTextField( textField1 ); de.addTextField( textField2 ); JFrame frame = new JFrame("SSCCE"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(textField1, BorderLayout.WEST); frame.add(textField2, BorderLayout.EAST); frame.add(submit, BorderLayout.SOUTH); frame.pack(); frame.setLocationByPlatform( true ); frame.setVisible( true ); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { createAndShowUI(); } }); } } 

输入任何文本时,基本代码启用按钮。 您需要修改dataEntered()方法以应用编辑条件。

编辑:

我不知道使用API​​做任何你想做的事情。 以下是可能的黑客攻击。

据我了解你会遇到的问题是两种情况:

  1. 当焦点位于表单的最后一个字段时,您使用Tab
  2. 当焦点位于表单的第一个字段时,您使用Shift-Tab

所以你可以做的就是用两个参数创建InputVerifier,即第一个和最后一个组件。 然后当你使用FocusListener和

  1. 当前焦点在于第一个组件,而相反的组件是最后一个组件
  2. 当前焦点在最后一个组件上,而相反的组件在第一个组件上

你知道你正在环绕表格。 在这两种情况下,您希望将焦点放在“保存”按钮上,这样您就需要手动请求“保存”按钮。 所以你可以通过使用:

 saveButton.requestFocusInWindow(); 

注意焦点仍将首先转到相反的组件,然后转到按钮。 您可能还需要将该代码包装在SwingUtilities.invokeLater()