JButton在setEnabled(true)之后不会立即获得焦点 [英] JButton does not gain focus immediately after setEnabled(true)

查看:310
本文介绍了JButton在setEnabled(true)之后不会立即获得焦点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

开始时按钮被禁用,只有当 all 数据



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

有3个可调焦的对象,两个文本字段和按钮。在文本字段中输入(有效)号码后按下 TAB 键,如果全部文本字段包含有效输入,则启用该按钮。这样也可以正常工作。

问题是:

将有效数据输入第二个文本字段后第一个文本字段已经包含有效数据并按下 TAB 该按钮不会获​​得焦点,因为它应该是。相反,焦点转移到一个行中的下一个可聚焦对象,这是(也是)第一个文本字段。



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


  1. 通过 FocusListener $ c>内部重载 focusLost()方法

  2. 按钮的已启用属性在重写中 shouldYieldFocus()方法

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

在我看来,对面组件已经预先确定在启用按钮之前,即使它被启用,按钮也不会获得焦点。



我甚至试着用 requestFocusInWindow()启用按钮后,但没有工作,所以问题是如何强制 LayoutFocusTraversalPolicy 要重新评估布局,以便它可以立即考虑到之前被禁用的新引入的按钮?

这里是代码我试过的两种方式:


  1. 按钮的启用属性通过 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实现了FocusListener {

    private JFrame frmInputverifierTest;
    private JTextField tfFirstNum;
    private JTextField tfSecondNum;
    private JLabel lblStatus;
    私人JButton btnStart;
    private String statusText =输入数字并按下\Start!\按钮...;

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

    $ b $ 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);
    //居中窗口
    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));
    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.setHorizo​​ntalAlignment(SwingConstants.TRAILING);
    panelInput.add(lblFirstNum);

    tfFirstNum = new JTextField();
    panelInput.add(tfFirstNum);
    tfFirstNum.setColumns(10);
    //设置验证者
    MyTxtVerifier txtVerifier = new MyTxtVerifier();
    tfFirstNum.setInputVerifier(txtVerifier);
    //添加焦点侦听器
    tfFirstNum.addFocusListener(this);

    JLabel lblSecondNum = new JLabel(Number#2:);
    lblSecondNum.setHorizo​​ntalAlignment(SwingConstants.TRAILING);
    panelInput.add(lblSecondNum);

    tfSecondNum = new JTextField();
    panelInput.add(tfSecondNum);
    tfSecondNum.setColumns(10);
    //设置验证器
    tfSecondNum.setInputVerifier(txtVerifier);
    //添加焦点侦听器
    tfSecondNum.addFocusListener(this);

    JPanel panelOutput = new JPanel();
    panelOutput.setBorder(new TitledBorder(UIManager.getBorder(TitledBorder.border),Output(not used at now),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);
    }

    //一个内部类,所以它可以访问父字段
    public class MyTxtVerifier extends InputVerifier {
    //这个方法应该没有副作用
    @Override
    public boolean verify(JComponent input){
    String text =((JTextField)input).getText();
    //在没有输入时允许改变焦点
    if(text.isEmpty())
    return true;
    尝试{
    BigDecimal value = new BigDecimal(text);
    if(value.floatValue()<= 0.0)
    返回false;
    return(value.scale()< = 4);
    } catch(Exception e){
    return false;



    //这个方法可以有副作用
    @Override
    public boolean shouldYieldFocus(JComponent input){
    String statusOld , 状态;

    statusOld = statusText; //记住原始文本
    boolean isOK = verify(input); //调用重写的方法
    if(isOK)
    status = statusOld;
    else {
    btnStart.setEnabled(false);
    status =错误:参数应该是正数,最多4个小数位;
    }
    lblStatus.setText(status);
    // return super.shouldYieldFocus(input);
    return isOK;



    @Override
    public void focusGained(FocusEvent e){
    //在焦点上无所事事
    }

    @Override
    public void focusLost(FocusEvent e){
    //如果我们要在focusLost()中显示一个消息框,不要被触发两次
    if(e.isTemporary())
    return;
    final JTextComponent c =(JTextComponent)e.getSource();
    //如果有更多的文本字段,但
    //我们只验证其中的一些
    if(c.equals(tfFirstNum)|| c.equals(tfSecondNum)){
    //是否都是文本字段有效?
    if(c.getInputVerifier()。verify(tfFirstNum)& c.getInputVerifier()。verify(tfSecondNum)&&
    !tfFirstNum.getText()。isEmpty()& &!tfSecondNum.getText()。isEmpty())
    btnStart.setEnabled(true);
    else
    btnStart.setEnabled(false);



    code
    $ b $ ol $ start $ $ $
  2. 按钮的已启用属性在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;
私人JButton btnStart;
private String statusText =输入数字并按下\Start!\按钮...;

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

$ b $ 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);
//居中窗口
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));
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.setHorizo​​ntalAlignment(SwingConstants.TRAILING);
panelInput.add(lblFirstNum);

tfFirstNum = new JTextField();
panelInput.add(tfFirstNum);
tfFirstNum.setColumns(10);
//设置验证者
MyTxtVerifier txtVerifier = new MyTxtVerifier();
tfFirstNum.setInputVerifier(txtVerifier);

JLabel lblSecondNum = new JLabel(Number#2:);
lblSecondNum.setHorizo​​ntalAlignment(SwingConstants.TRAILING);
panelInput.add(lblSecondNum);

tfSecondNum = new JTextField();
panelInput.add(tfSecondNum);
tfSecondNum.setColumns(10);
//设置验证器
tfSecondNum.setInputVerifier(txtVerifier);

JPanel panelOutput = new JPanel();
panelOutput.setBorder(new TitledBorder(UIManager.getBorder(TitledBorder.border),Output(not used at now),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);
}

//一个内部类,所以它可以访问父字段
public class MyTxtVerifier extends InputVerifier {
//这个方法应该没有副作用
@Override
public boolean verify(JComponent input){
String text =((JTextField)input).getText();
//在没有输入时允许改变焦点
if(text.isEmpty())
return true;
尝试{
BigDecimal value = new BigDecimal(text);
if(value.floatValue()<= 0.0)
返回false;
return(value.scale()< = 4);
} catch(Exception e){
return false;



//这个方法可以有副作用
@Override
public boolean shouldYieldFocus(JComponent input){
String statusOld , 状态;

statusOld = statusText; //记住原始文本
boolean isOK = verify(input); //调用重写的方法
if(isOK)
status = statusOld;
else {
status =错误:参数应该是正数,最多4个小数位;
}
lblStatus.setText(status);
setBtnState(input); //启用或禁用按钮
//btnStart.requestFocusInWindow(); //< - 没有帮助
// return super.shouldYieldFocus(input);
return isOK;



private void setBtnState(JComponent input){
if(input.equals(tfFirstNum)|| input.equals(tfSecondNum)){
//都是文本字段有效吗?
if(input.getInputVerifier()。verify(tfFirstNum)&& input.getInputVerifier()。verify(tfSecondNum)
&&!tfFirstNum.getText()。isEmpty()& &!tfSecondNum.getText()。isEmpty())
btnStart.setEnabled(true);
else
btnStart.setEnabled(false);





下面是截图测试申请:



注意:

该代码与我之前询问的问题中包含的代码相关,这是另一个话题。



编辑:

尝试建议(使用 invokeLater ()来运行被接受的答案作者提出的 requestFocusInWindow()),这里是可以作为概念验证的代码:

$ $ p $ @Override
public void focusLost(FocusEvent e){
//如果我们想要在focusLost()中显示一个消息框 - 不被触发两次
if(e.isTemporary())
return;
final JTextComponent c =(JTextComponent)e.getSource();
//如果有更多的文本字段,但
//我们只验证其中的一些
if(c.equals(tfFirstNum)|| c.equals(tfSecondNum)){
//是否都是文本字段有效?
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();
}
});




$ p

这只是改变后的方法#01 有关的focusLost()方法。我不知道是否有类似的解决方案用于方法#02 - 因为我不知道是否可以引用如果没有 FocusListener ,则在 shouldYieldFocus()内部的注意:使用此解决方案时,可以清楚地看到,在输入第二个数字并按下之后, TAB 按钮,先聚焦(暂时),跳到第一个文本字段,然后移动到按钮。

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

使用 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实现DocumentListener
{
private JButton button;
私人列表< JTextField> textFields = new ArrayList< JTextField>();

public DataEntered(JButton button)
{
this.button = button;


public void addTextField(JTextField textField)
{
textFields.add(textField);
textField.getDocument()。addDocumentListener(this); ($ text){
}

public boolean isDataEntered()
{
for(JTextField textField:textFields)
{
if(textField.getText() .trim()。length()== 0)
return false;
}

return true;

$ b @Override
public void insertUpdate(DocumentEvent e)
{
checkData();

$ b @Override
public void removeUpdate(DocumentEvent e)
{
checkData();

$ b $覆盖
public void changedUpdate(DocumentEvent e){}
$ b $ 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()中。


I am trying to make an intuitive user interface where the user would enter the numeric values into the JTextFields, advance with the TAB key and finally activate the button to start processing the input.

At the beginning the button is disabled and it should be enabled only when all of the data is entered into the text fields.

I am using javax.swing.InputVerifier to restrict entering only positive numbers up to 4 decimal places and that works fine.

There are 3 focusable objects, two text fields and the button. Pressing the TAB key after typing the (valid) number into the text field, and if all the text fields contain valid inputs, enables the button. That works fine too.

The problem is:
After typing the valid data into the second text field when the first text field already contains valid data and pressing the TAB, the button does not gain the focus as it should. Instead, the focus is transfered to the next focusable object in a row which is (again) the first text field.

I tried to use two different approaches:

  1. The button's enabled property is changed via FocusListener inside overriden focusLost() method
  2. The button's enabled property is changed inside overriden shouldYieldFocus() method

In both cases the focus skips the button immediately after enabling the button. However, if we then continue to change the focus using TAB and SHIFT+TAB keys, the button gains focus as it should - right after the second text field.

It seems to me as the opposite component has been predetermined before enabling the button so the button does not gain the focus even after it gets enabled.

I even tried to force the button to gain the focus using requestFocusInWindow() after enabling the button but that didnt'n work either so the question is how to force the LayoutFocusTraversalPolicy to re-evaluate the Layout so it can immediately take into account the newly introduced button which was before disabled?

Here is the code for both the approaches I tried:

  1. The button's enabled property is changed via FocusListener inside focusLost() method:

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. The button's enabled property is changed inside overriden shouldYieldFocus() method:

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

Here is the screenshot of the test application:

Note:
The code is related to the code contained in the question I asked before, which was another topic.

EDIT:
Upon trying out the suggestion (using invokeLater() to run the requestFocusInWindow()) proposed by the author of the accepted answer, here is the code that can serve as a proof of concept:

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

This is just the changed focusLost() method pertaining to the approach #01. I am not aware if there is similar solution to be used with the approach #02 - since I don't know if it is possible to reference the opposite from inside the shouldYieldFocus() when there isn't a FocusListener.

Note:
When using this solution it can be clearly observed that after entering the 2nd number and pressing the TAB button, focus first (for the moment of time) jumps to the first text field and only then moves to the button.

解决方案

I would suggest you don't use an InputVerifier, but instead use a DocumentListener.

The benefit of using the DocumentListener is that the text field can be edited as each character is entered, so the user has immediate feedback. Then as soon as you enter the first digit the button can be enabled (if it passes your editing criteria).

Since the button will now be enabled before the user attempts to enter the Tab key you will not have any focus issues.

Here is a basic example to get your started:

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

The basic code enables the button whenver any text is entered. You would need to modify the dataEntered() method to apply your editing criteria.

Edit:

I don't know any way using the API to do what you want. Following is a possible hack.

As I understand it you will have the problem is two situations:

  1. When focus is on the last field of the form and you use Tab
  2. When focus is on the first field of the form and you use Shift-Tab

So maybe what you can do is create you InputVerifier with two parameter, the first and last components. Then when you use the FocusListener and

  1. current focus is on the first component and the opposite component is the last
  2. current focus is on the last component and the opposite component is on the first

You know you are wrapping around the form. In these two situations you want focus to be placed on the "Save" button so you need to manually request focus on the Save button. So you could do this by just using:

saveButton.requestFocusInWindow();

Note focus would still go the to opposite component first and then to the button. You might also need to wrap that code in a SwingUtilities.invokeLater().

这篇关于JButton在setEnabled(true)之后不会立即获得焦点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆