I am attempting to validate the text field user inputs using javax.swing.InputVerifier
and the input validation works as expected but I have a problem regarding VerifyInputWhenFocusTarget
property.
I've made a label to show the status and overridden verify()
and shouldYieldFocus()
methods of InputVerifier
subclass and that works fine.
The next step I wanted to do was to set the VerifyInputWhenFocusTarget
of the button so it wouldn't get the focus in case the validation of the current focus owner was false, but I did not notice any effect of setting the VerifyInputWhenFocusTarget
property to true
and the button could be pressed even when the validation of the current focus owner was false.
Maybe I do not understand the docs - I thought setting the VerifyInputWhenFocusTarget
property of the button to true
would prevent the button to get the focus when clicked under the circumstances of the false validation of the text field. Furthermore, I (mis)understood if the button couldn't get the focus then its actionPerformed()
method wouldn't be called.
However the button can be clicked and its actionPerformed()
method gets executed nevertheless of the false validation of the text field(s) 'guarded' by javax.swing.InputVerifier
.
Here is the stripped code:
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;
}
}
}
And here is the screenshot of the test application:
As can be seen, there are two buttons. One of them has the VerifyInputWhenFocusTarget
property set to true
and the other one has the same property set to false
but there isn't any difference when the button is clicked under the circumstance of false text field validation. The false validation can be provoked by entering negative number or some number with more than 4 decimal digits. The InputVerifier
indeed prevents transfering the focus to the other text field but it does not prevent activating the button. Since it does not make sense (at least not to me) the button could be activated without first getting the focus, there shouldn't be a possibility to activate the Start! button when the text field validation method verify()
returned false
.
EDIT:
I have now changed the code to accommodate the trashgod's suggestion (conditioning the enabled state of the Start! button with FocusListener
) and here is the working example:
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);
}
}
}
I have slightly changed the code of verify()
method to allow changing the focus if nothing is entered (focusLost()
method checks if all text fields contain some input and it also checks if the inputs are valid by explicitly calling verify()
for each of the text fields).
The code, of course, needs some minor tweaking (tab order, updating the status, ...) but that is out of the scope of this topic.
Conclusion:
Although VerifyInputWhenFocusTarget
property is apparently useful (here in the example the ? button can gain focus even in case of text field(s) validation was false
), I am still holding my opinion the documentation is not quite precise in describing all the important side effects which are rather counterintuitive so there is a need of doing further testing and investigations (perhaps analyzing the source code) besides just reading the docs.
Your calls to setVerifyInputWhenFocusTarget()
do have a effect—precisely the effect of determining "whether [the] input verifier for the current focus owner will be called before this component requests focus." [emphasis mine] In particular, establish the following state:
Let
statusText
have its initial value, or restore the initial value by entering a valid value.Let
tfFirstNum
ortfSecondNum
have focus while containing an invalid value.
Then observe that
Clicking on ? leaves
statusText
unchanged, meaning that the focused component's input verifier was not called, as prescribed bybtnHelp.setVerifyInputWhenFocusTarget(false);
Clicking on Start sets
statusText
to reflect an error, meaning that the focused component's input verifier was called, as prescribed bybtnStart.setVerifyInputWhenFocusTarget(true);
Note that both conditions above must be met to see the effect when either button is clicked.
来源:https://stackoverflow.com/questions/38680552/verifyinputwhenfocustarget-property-has-no-effect