VerifyInputWhenFocusTarget 属性 无效

VerifyInputWhenFocusTarget property has no effect

我正在尝试使用 javax.swing.InputVerifier 验证文本字段用户输入,并且输入验证按预期工作,但我有一个关于 VerifyInputWhenFocusTarget 属性.

的问题

我制作了一个标签来显示 InputVerifier 子类的状态和重写 verify()shouldYieldFocus() 方法并且工作正常。

下一步我想做的是设置按钮的 VerifyInputWhenFocusTarget,这样它就不会获得焦点,以防当前焦点所有者的验证为假,但我没有注意到任何将 VerifyInputWhenFocusTarget 属性 设置为 true 的效果,即使当前焦点所有者的验证为假,也可以按下按钮。

可能我看不懂文档 - 我认为将按钮的 VerifyInputWhenFocusTarget 属性 设置为 true 会阻止按钮在以下情况下单击时获得焦点文本字段的错误验证。此外,我(误解)理解如果按钮无法获得焦点,那么它的 actionPerformed() 方法将不会被调用。

然而,按钮可以被点击并且它的 actionPerformed() 方法仍然被执行 javax.swing.InputVerifier.

对文本字段 'guarded' 的错误验证

这里是剥离代码:

package verifiertest;

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;

import javax.swing.UIManager;
import java.awt.GridLayout;
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 String statusText = "Input the numbers and press the \"Start!\" button...";

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            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);

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

        JButton btnHelp = new JButton("?");
        btnHelp.setVerifyInputWhenFocusTarget(false);   // <-- NO EFFECT!?
        btnHelp.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frmInputverifierTest, "Help button pressed...", "Help", JOptionPane.PLAIN_MESSAGE);
            }
        });
        panelActions.add(btnHelp);

        JButton btnStart = new JButton("Start!");
        btnStart.setVerifyInputWhenFocusTarget(true);   // <-- NO EFFECT!?
        btnStart.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frmInputverifierTest, "Start button pressed...", "Start", JOptionPane.PLAIN_MESSAGE);
            }
        });
        panelActions.add(btnStart);
        btnHelp.setPreferredSize(btnStart.getPreferredSize());  // make buttons equal in size
    }

    // 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();
            try {
                BigDecimal value = new BigDecimal(text);
                if(value.floatValue() <= 0.0)
                    return false;
                return (value.scale() <= Math.abs(4));  // why not 4 instead of Math.abs(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);
            // return super.shouldYieldFocus(input);
            return isOK;
        }
    }

}

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

可以看到,有两个按钮。其中一个将 VerifyInputWhenFocusTarget 属性 设置为 true 而另一个将相同的 属性 设置为 false 但是当在错误的文本字段验证的情况下单击按钮。通过输入负数或一些超过 4 位小数的数字可以引发错误验证。 InputVerifier 确实会阻止将焦点转移到其他文本字段,但不会阻止激活按钮。因为它没有意义(至少对我来说不是)按钮可以在没有首先获得焦点的情况下被激活,所以不应该有可能激活 Start! 按钮当文本字段验证方法 verify() 返回 false.


编辑:

我现在更改了代码以适应 trashgod 的建议(使用 FocusListener 调节 Start! 按钮的启用状态),这里是工作示例:

package verifiertest;

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
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.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() {
            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);

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

        JButton btnHelp = new JButton("?");
        btnHelp.setVerifyInputWhenFocusTarget(false);   // <-- NO EFFECT!?
        btnHelp.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frmInputverifierTest, "Help button pressed...", "Help", JOptionPane.PLAIN_MESSAGE);
            }
        });
        panelActions.add(btnHelp);

        btnStart = new JButton("Start!");
        btnStart.setEnabled(false);
        btnStart.setVerifyInputWhenFocusTarget(true);   // <-- NO EFFECT!?
        btnStart.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frmInputverifierTest, "Start button pressed...", "Start", JOptionPane.PLAIN_MESSAGE);
            }
        });
        panelActions.add(btnStart);
        btnHelp.setPreferredSize(btnStart.getPreferredSize());  // make buttons equal in size
    }

    // 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() <= Math.abs(4));  // why not 4 instead of Math.abs(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);
        }
    }
}

我稍微更改了 verify() 方法的代码,允许在未输入任何内容时更改焦点(focusLost() 方法检查是否所有文本字段都包含一些输入,它还会检查输入是否是通过为每个文本字段显式调用 verify() 有效。

当然,代码需要进行一些小的调整(Tab 键顺序、更新状态……),但这超出了本主题的范围。

结论:

尽管 VerifyInputWhenFocusTarget 属性 显然很有用(在示例中 ? 按钮即使在文本字段验证的情况下也可以获得焦点是 false),我仍然认为文档在描述所有相当违反直觉的重要副作用方面并不十分准确,因此需要进行进一步的测试和调查(也许分析源代码)只是阅读文档。

您对 setVerifyInputWhenFocusTarget() do 的调用会产生影响——正是确定 "whether [the] input verifier for the current focus owner will be called before this component requests focus." [强调我的] 的影响 特别是,建立以下状态:

  • statusText有它的初始值,或者通过输入一个有效值来恢复初始值。

  • tfFirstNumtfSecondNum 在包含无效值时获得焦点。

然后观察

  • 单击 ? 会使 statusText 保持不变,这意味着焦点组件的输入验证器 不是 按照

    的规定进行调用
    btnHelp.setVerifyInputWhenFocusTarget(false);
    
  • 单击 开始 设置 statusText 以反映错误,这意味着焦点组件的输入验证器 调用,按照

    的规定
    btnStart.setVerifyInputWhenFocusTarget(true);
    

请注意,必须满足上述两个条件才能在单击任一按钮时查看效果。