JButton 在 setEnabled(true) 后不会立即获得焦点
JButton does not gain focus immediately after setEnabled(true)
我正在尝试制作一个直观的用户界面,用户可以在其中将数值输入 JTextFields
,然后使用 TAB 键前进,最后激活按钮开始处理输入。
一开始按钮是禁用的,只有当所有的数据都输入到文本字段时才应该启用。
我正在使用 javax.swing.InputVerifier
来限制仅输入 最多 4 位小数的正数 并且工作正常。
有 3 个可聚焦对象、两个文本字段和按钮。在文本字段中键入(有效)数字后按 TAB 键,如果 all 文本字段包含有效输入,则启用该按钮。这也很好用。
问题是:
在第一个文本字段已包含有效数据并按下 TAB 后,在第二个文本字段中键入有效数据后,按钮没有获得应有的焦点。相反,焦点将转移到一行中的下一个可聚焦对象(再次)是第一个文本字段。
我尝试使用两种不同的方法:
- 按钮的
enabled
属性 通过 FocusListener
内部覆盖的 focusLost()
方法 更改
- 按钮的
enabled
属性 在重写的 shouldYieldFocus()
方法中改变了
在这两种情况下,焦点都会在启用按钮 后立即跳过按钮。但是,如果我们然后 continue 使用 TAB 和 SHIFT+ 更改焦点TAB 键,按钮获得焦点 - 就在第二个文本字段之后。
在我看来,opposite
组件在启用按钮之前已经预先确定,因此按钮即使在启用后也不会获得焦点。
我什至尝试在启用按钮后使用 requestFocusInWindow()
强制按钮获得焦点,但这也不起作用所以问题是 如何强制 LayoutFocusTraversalPolicy
重新评估布局,以便它可以立即考虑到禁用之前新引入的按钮?
这是我尝试过的两种方法的代码:
- 按钮的
enabled
属性 通过 FocusListener
在 focusLost()
方法中更改:
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);
}
}
}
- 按钮的
enabled
属性 在重写的 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()
)后,这里是可以作为概念证明的代码:
@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 #02
是否有类似的解决方案 - 因为我不知道是否可以从 shouldYieldFocus()
内部引用 opposite
当没有' FocusListener
.
注:
使用此解决方案时,可以清楚地观察到在输入第二个数字并按下 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<JTextField> textFields = new ArrayList<JTextField>();
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 来做你想做的事。以下是一个可能的黑客攻击。
据我了解,您遇到的问题有两种情况:
- 当焦点位于表单的最后一个字段并且您使用 Tab
- 当焦点位于表单的第一个字段并且您使用 Shift-Tab 时
所以也许您可以做的是创建带有两个参数的 InputVerifier,第一个和最后一个组件。然后当你使用 FocusListener 和
- 当前焦点在第一个组件上,相反的组件是最后一个
- 当前焦点在最后一个组件上,相反的组件在第一个组件上
您知道您正在环绕表格。在这两种情况下,您希望将焦点放在 "Save" 按钮上,因此您需要手动请求将焦点放在“保存”按钮上。所以你可以通过使用来做到这一点:
saveButton.requestFocusInWindow();
请注意,焦点仍然会先转到对面的组件,然后再转到按钮。您可能还需要将该代码包装在 SwingUtilities.invokeLater()
.
中
我正在尝试制作一个直观的用户界面,用户可以在其中将数值输入 JTextFields
,然后使用 TAB 键前进,最后激活按钮开始处理输入。
一开始按钮是禁用的,只有当所有的数据都输入到文本字段时才应该启用。
我正在使用 javax.swing.InputVerifier
来限制仅输入 最多 4 位小数的正数 并且工作正常。
有 3 个可聚焦对象、两个文本字段和按钮。在文本字段中键入(有效)数字后按 TAB 键,如果 all 文本字段包含有效输入,则启用该按钮。这也很好用。
问题是:
在第一个文本字段已包含有效数据并按下 TAB 后,在第二个文本字段中键入有效数据后,按钮没有获得应有的焦点。相反,焦点将转移到一行中的下一个可聚焦对象(再次)是第一个文本字段。
我尝试使用两种不同的方法:
- 按钮的
enabled
属性 通过FocusListener
内部覆盖的focusLost()
方法 更改
- 按钮的
enabled
属性 在重写的shouldYieldFocus()
方法中改变了
在这两种情况下,焦点都会在启用按钮 后立即跳过按钮。但是,如果我们然后 continue 使用 TAB 和 SHIFT+ 更改焦点TAB 键,按钮获得焦点 - 就在第二个文本字段之后。
在我看来,opposite
组件在启用按钮之前已经预先确定,因此按钮即使在启用后也不会获得焦点。
我什至尝试在启用按钮后使用 requestFocusInWindow()
强制按钮获得焦点,但这也不起作用所以问题是 如何强制 LayoutFocusTraversalPolicy
重新评估布局,以便它可以立即考虑到禁用之前新引入的按钮?
这是我尝试过的两种方法的代码:
- 按钮的
enabled
属性 通过FocusListener
在focusLost()
方法中更改:
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);
}
}
}
- 按钮的
enabled
属性 在重写的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()
)后,这里是可以作为概念证明的代码:
@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 #02
是否有类似的解决方案 - 因为我不知道是否可以从 shouldYieldFocus()
内部引用 opposite
当没有' FocusListener
.
注:
使用此解决方案时,可以清楚地观察到在输入第二个数字并按下 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<JTextField> textFields = new ArrayList<JTextField>();
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 来做你想做的事。以下是一个可能的黑客攻击。
据我了解,您遇到的问题有两种情况:
- 当焦点位于表单的最后一个字段并且您使用 Tab
- 当焦点位于表单的第一个字段并且您使用 Shift-Tab 时
所以也许您可以做的是创建带有两个参数的 InputVerifier,第一个和最后一个组件。然后当你使用 FocusListener 和
- 当前焦点在第一个组件上,相反的组件是最后一个
- 当前焦点在最后一个组件上,相反的组件在第一个组件上
您知道您正在环绕表格。在这两种情况下,您希望将焦点放在 "Save" 按钮上,因此您需要手动请求将焦点放在“保存”按钮上。所以你可以通过使用来做到这一点:
saveButton.requestFocusInWindow();
请注意,焦点仍然会先转到对面的组件,然后再转到按钮。您可能还需要将该代码包装在 SwingUtilities.invokeLater()
.