I have two textfield in my swing component. In one text field i need to have only numbers
(no string,empty spaces,special charaters allowed) and in another textfield i need to have only string(no numbers,empty spaces,special charaters allowed). How can i implement that..???
You can use the Pattern Class (Regular Expressions) to validate the input. A short tutorial is available here.
I am pretty sure that the basic tutorial covers all this...
"^//d+$" //The text must have at least one digit, no other characters are allowed
"^[a-zA-Z]+$" //The text must have at least one letter, no other characters are allowed
You have two choices, you can validate the text in the fields either 1) on entry or 2) when the user performs an action such as clicks a confirmation button.
For 2) npinti's answer should steer you in the right direction, just get the value of the field and validate it with a regular expression.
For 1) you might want to write a KeyListener that intercepts key presses and only allows the correct type of character for the field.
You can extend javax.swing.text.PlainDocument class, and call setDocument method textfield. Here is one of the example ;
package textfield;
import java.awt.Toolkit;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
public class LimitedValuePositiveIntegerDocument extends PlainDocument {
int maxValue;
int maxLength;
Toolkit toolkit;
/**
* Constructor for the class.
* #param max maximum value of the number
*/
public LimitedValuePositiveIntegerDocument(int max){
maxValue = max;
maxLength = (""+max).length();
toolkit = Toolkit.getDefaultToolkit();
}
/**
* Inserts the input string to the current string after validation.
* #param offs offset of the place where the input string is supposed to be inserted.
* #param str input string to be inserted
* #param a attribute set
*/
#Override
public void insertString(int offs, String str, AttributeSet a)
throws BadLocationException {
if(str == null)return;
String currentText = getText(0,getLength());
String resultText = new String();
int i;
boolean errorFound = false;
boolean deleteFirstZero = false;
int accepted=0;
for (i = 0; (i<str.length())&&(!errorFound); i++) {
if (Character.isDigit(str.charAt(i))) { /* if it is digit */
if (offs==currentText.length()) { /* calculates the resultant string*/
resultText = currentText+str.substring(0,i+1);
} else if (offs==0) {
resultText = str.substring(0,i+1)+currentText;
} else {
resultText = currentText.substring(0, offs)+str.substring(0,i+1)+currentText.substring(offs,currentText.length());
}
if (Integer.parseInt(resultText) > maxValue) {
errorFound = true;
toolkit.beep();
} else {
if ( resultText.length() == maxLength+1) {
deleteFirstZero = true;
}
accepted++;
}
} else {
errorFound = true;
toolkit.beep();
}
}
if ( accepted>0 ) { /* insert string */
super.insertString(offs, str.substring(0,accepted), a);
if (deleteFirstZero) {
super.remove(0,1);
}
}
}
/**
* Removes a part of the current string.
* #param offs offset of the place to be removed.
* #param len length to be removed
*/
#Override
public void remove(int offs, int len) throws BadLocationException{
super.remove(offs, len);
}
/**
* Returns max value of the number.
* #return max value
*/
public int getMaxValue() {
return maxValue;
}
/**
* Sets max value of the number.
* #param max maximum value of the number
*/
public void setMaxValue(int max) {
this.maxValue = max;
}
} // end of class
EDIT :
and its usage;
LimitedValuePositiveIntegerDocument doc = new LimitedValuePositiveIntegerDocument(999);
JTextField numberField = new JtextField();
numberField.setDocument(doc);
You can only enter positive numbers less than 1000, and it check while you are pressing the key..
Related
I'm creating a four digit class (like a clock) while calling methods from a two digit class.
The four digit class has two segments. When segment one reaches my set maximum value, the second segment must increase by one.
these are my methods from my first class:
/*
* method to set the value
*/
public void setValue(int anyValue){
if((anyValue < TOO_HIGH) && (anyValue >= 0)){
value = anyValue;}
}
/*
* method to set the value plus one
*/
public void setValuePlusOne(){
int tempValue = value + 1;
if(tempValue < TOO_HIGH){
value = tempValue;}
else{
value = 0;
// value = tempValue % TOO_HIGH;}
}
This is from my second four digit class.
/*
* method to set the increment
*/
public void setIncrement(int increment){
rightSegment.setValuePlusOne();
if(increment == 0)
leftSegment.setValuePlusOne();
}
I think there might be something wrong with my increment == 0. It doesn't compile when I try
if(rightsegment.setValuePlusOne()==0)
any advise would help. Thank you!!
setValuePlusOne(...) does not return anything. Call setValuePlusOne before the if and then use (rightsegment.getValue() == 0) for the if.
Please try the code below. Hope the below code will help you to achieve your implementation.
Instead of setting the TOO_HIGH integer value in the if else block of the below given code, you can set it in the RightSegment and LeftSegment classes respectively which is extending the Clock class.
Thanks
package stackoverflow;
public class Clock {
private int value;
private int TOO_HIGH;
private Clock rightSegment;
private Clock leftSegment;
/*
* method to set the value
*/
public void setValue(int anyValue, String position){
if(position.equals("right")){
TOO_HIGH = 60;
}else if(position.equals("left")){
TOO_HIGH = 13;
}
if((anyValue < TOO_HIGH) && (anyValue >= 0)){
value = anyValue;}
}
/*
* method to set the value plus one
*/
public void setValuePlusOne(){
int tempValue = value + 1;
if(tempValue < TOO_HIGH){
value = tempValue;}
else{
value = 0;
}
// value = tempValue % TOO_HIGH;}
}
/*
* method to set the i`ncrement
*/
public void setIncrement(int increment, Clock right, Clock left){
rightSegment = right;
leftSegment = left;
//rightSegment = new Clock();
//leftSegment = new Clock();
rightSegment.setValuePlusOne();
if(increment == 0){
leftSegment.setValuePlusOne();
}
}
public static void main (String args[]){
Clock clock = new Clock();
clock.rightSegment = new Clock();
clock.leftSegment = new Clock();
clock.rightSegment.setValue(12, "right");
clock.leftSegment.setValue(12, "left");
clock.rightSegment.setIncrement(0, clock.rightSegment, clock.leftSegment);
System.out.println("Time "+clock.leftSegment.value+":"+clock.rightSegment.value);
}
}
I'm relatively new to java and learning OOP and I have a project to make a number converter that can convert a value of any base to decimal, or a decimal value to any base.
I've tested decimal to a different base and that's working fine for me, but the decimal to base [2, 8, whatever] isn't working. Any help?
public class NumberConverter
{
private int decimal; // always stores the decimal equivalent, regardless of base
private int base;
private String strValue;
/** default set to base 10 w/ a value of 0 */
public NumberConverter()
{
decimal = 0;
base = 10;
strValue="0";
}
/** base 10 value is used to set decimal, base and strValue
* note: toBaseX can do this for you also */
public NumberConverter(int value)
{
decimal = value;
base = 10;
strValue = "" + value;
}
/** sets the strValue and base based on parameters
* strValue is only stored in uppercase
* decimal is set here as well provided newValue is valid */
public NumberConverter(int newValue, int newBase)
{
decimal = 0;
strValue = newValue + "";
base = newBase;
}
//**** Accessor Methods ****//
public String getValue()
{
return strValue;
}
public int getBase()
{
return base;
}
public int getDecimal()
{
return decimal;
}
/** sets the strValue and base based on parameters
* decimal is set here as well, with a call to baseXToDec() */
public void setValue(String newValue, int newBase)
{
strValue = newValue;
base = newBase;
if (isValid())
decimal = baseXToDec();
else
decimal = 0;
}
public boolean isValid()
{
boolean valid = true;
for (int i=0; i<strValue.length()-1; i++)
{
if ((strValue.charAt(i)>47 && strValue.charAt(i)<58) || (strValue.charAt(i)>64 && strValue.charAt(i)<71))
valid = true;
else
valid = false;
}
return valid;
}
/** base of this object is set to x and strValue is the String value in base x
* strValue is also returned, just for good measure
* #param x the number base to convert to */
public String toBaseX(int x)
{
String strResult = "", strRev;
base = x;
int div = decimal, mod;
if (decimal == 0)
return "0";
while(div != 0)
{
mod = div % x;
if (mod > 10)
strResult += (char)(mod+55);
else
strResult += (char)(mod+48);
div = div / x;
}
strRev = reverseString(strResult);
strValue = strRev;
return strRev;
}
/** take the currently stored strValue and calculate and return the decimal value */
public int baseXToDec()
{
int exponent = strValue.length()-1;
for (int i=0; i<strValue.length(); i++)
{
// update result
decimal += (strValue.charAt(i) * Math.pow(base, exponent));
// decrement exponent
exponent--;
}
base = 10;
strValue = "" + decimal;
return decimal;
}
/** this is a helper method only
* the integer value of digit is returned
* -1 is the return value for an error
* #param ch a valid digit for the given number base */
private int charToValue(char ch)
{
}
/** This is a helper method that returns strRev as a reversed version
* #param strFwd the string to be reversed */
private String reverseString(String strResult)
{
String strRev = "";
for ( int i = strResult.length() - 1 ; i >= 0 ; i-- )
strRev= strRev + strResult.charAt(i);
return strRev;
}
/** a String with the current base and value is returned */
public String toString()
{
String result = "In base " + getBase() + " the value is "+ getValue() + ".\n";
return result;
}
}
strValue.charAt(i) this is the problem: it returns a character code for the digit, not the integer value it represents. For example, the code for character '0' is 48 etc. You need to convert it into the actual integer value before you can use it like that.
One (non-portable, and frowned upon) way is to subtract 48 (or '0'), as suggested in the comment. This relies on the fact that the digits in ASCII are coded sequentially, so 49 would be '1', 50 - '2' etc. It'll do what you want.
A (slightly) better way is Character.getNumericValue(strValue.charAt(i)).
It'll do the same thing behind the curtain, but will also work in exotic languages (like Indian, Easter Arabic etc.), that use different symbols for digits. Not that your little program will ever need that, just a good practice to adopt for the future.
Edit: same applies to isValid() function as well. Character.isDigit() is a better option thаn explicitly looking at character codes. You could also potentially simplify it by replacing the whole thing with strValue.matches("\\d+"); (\\d is a regex code for "digit", so this returns true when your string contains only digits.
I believe you are trying re-invent the wheel here. Java already has pretty easy implementation.
Integer.toString(number, base) method will help you to convert to another base.
Example:
System.out.println(Integer.toString(10, 5));
Look Integer.parseInt() method also where ever needed.
I haven't gotten around to isValid yet; subtracting the value of 0 as suggested and using a separate local variable did give me the correct output.
public int baseXToDec()
{
int decVal = 0, exponent = strValue.length()-1;
for (int i=0; i<strValue.length(); i++)
{
// update result
decVal += ((strValue.charAt(i)-'0') * Math.pow(base, exponent));
// decrement exponent
exponent--;
}
base = 10;
strValue = "" + decVal;
decimal = decVal;
return decimal;
}
Your method isValid contains true if the last char is valid and false if the last char is not valid.
Change it:
public boolean isValid()
{
for (int i=0; i<strValue.length()-1; i++)
{
if (!((strValue.charAt(i)>47 && strValue.charAt(i)<58) || (strValue.charAt(i)>64 && strValue.charAt(i)<71)))
return false;
}
return true;
}
I'm having trouble being able to figure out why this is giving the incorrect value.
public static void main(String[] args) {
SimpleReader in = new SimpleReader1L();
SimpleWriter out = new SimpleWriter1L();
boolean x = true;
out.print("Enter a URL or file name for an XML source: ");
String url = in.nextLine();
XMLTree xml = new XMLTree2(url);
out.print("Enter the name of a tag: ");
String tag = in.nextLine();
out.println(findTag(xml, tag));
}
/**
* Reports whether the given tag appears in the given {#code XMLTree}.
*
* #param xml
* the {#code XMLTree}
* #param tag
* the tag name
* #return true if the given tag appears in the given {#code XMLTree}, false
* otherwise
* #ensures <pre>
* findTag =
* [true if the given tag appears in the given {#code XMLTree}, false otherwise]
* </pre>
*/
private static boolean findTag(XMLTree xml, String tag) {
boolean result = false;
if (xml.isTag()) {
for (int i = 0; i < xml.numberOfChildren(); i++) {
findTag(xml.child(i), tag);
System.out.println("label is " + xml.label());
if (xml.label().equals(tag)) {
result = true;
System.out.println(result);
}
}
}
return result;
}
}
when given an xml tree the code runs so that it finds all of the tags that is present in the tree. It compares every single tag to the tag being searched. How would I make it so that when the given tag is found in the XML it updates the boolean variable and stops it from searching for more.
There are 2 ways to do what you want.
1). Make the boolean variable as static global variable to the method.
2). Pass the boolean variable to the method as an argument.
Before processing the method just check the state of the variable.
Something like this
private static boolean findTag(XMLTree xml, String tag,boolean result) {
if (xml.isTag() && !result) {
for (int i = 0; i < xml.numberOfChildren(); i++) {
findTag(xml.child(i), tag,result);
System.out.println("label is " + xml.label());
if (xml.label().equals(tag)) {
result = true;
System.out.println(result);
return result;
}
}
}
return result;
}
EDIT - added at then end of the post the answer we were able to achieve
This is my first post in SO, so i hope i can ask everything right!
I searched and didn't quite find a answer to my question despite similar questions being posted, so i hope this isn't a repost.
This is what a i got, a small application that uses JTextField to receive user's input and on top of that i have a DocumentFilter so the user can only input integers and a period in order to receive values that represent weight.
My problem is, with my DocumentFilter I'm not being able to filter "copy pasted" text and i can't filter a selected text removal.
Here is the code for the Filter
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
/**
* The Class IntAndDotFilter.
*/
public class IntAndDotFilter extends DocumentFilter {
/** The period counter. */
private int periodCounter = 0;
/** The number counter. */
private int numberCounter = 0;
private boolean canRemove = true;
public void setCanRemove(boolean canRemove) {
this.canRemove = canRemove;
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text,
AttributeSet attrs) throws BadLocationException {
if (periodCounter == 0) { // If there is no . on the text
if (text.matches("\\.")) { // Checks if the input is a dot
super.replace(fb, offset, length,
text.replaceAll("[^0-9.]", ""), attrs);
periodCounter++; // If it is, inserts it and saves that info
} else {
super.replace(fb, offset, length,
text.replaceAll("[^0-9]", ""), attrs);
// If not, checks if the input is a digit
// and inserts if it is
}
} else { // If there is already a .
if (text.matches("\\.")) { // Checks if the input is another .
super.replace(fb, offset, length,
text.replaceAll("[^0-9]", ""), attrs);
// If it is, filters so that cannot be more than one .
} else {
if (text.matches("[0-9]")) { // Checks if it's a digit
if (numberCounter != 2) {
super.replace(fb, offset, length,
text.replaceAll("[^0-9]", ""), attrs);
numberCounter++;
// If yes, and if that is only the second one (0.00)
// inserts and
// saves the info that there are digits after the 1st .
// for removal purposes
} else {
super.replace(fb, offset, length,
text.replaceAll(".", ""), attrs);
// if it is the third+ digit after . , doesn't allow the
// input
}
} else {
super.replace(fb, offset, length, text.replaceAll(".", ""),
attrs);
// Not being a digit, doesn't allow the
// insertion of the given input
}
}
}
}
#Override
public void remove(FilterBypass fb, int offset, int length)
throws BadLocationException {
if (canRemove) {
if (periodCounter == 1) { // If there is a . in the text
if (numberCounter != 0) { // and If there are digits after the .
numberCounter--; // It means you'r removing a digit, so it
// saves
// that info
super.remove(fb, offset, length); // And removes it
} else { // If there are no digits it means you'r removing a .
periodCounter--; // It saves that info allowing a new . to
// be
// inserted
super.remove(fb, offset, length); // and removes it
}
} else { // If there is no . in the text there are no problems
super.remove(fb, offset, length); // so it just removes whatever
// there is (digit)
}
} else {
}
}
}
the insertString method does the same has the replace method so i left it out, but in the application it's implemented.
Thanks in advance for your time!
EDIT - Plus it now has a filter to restrain the height input too
public class IntAndDotFilter extends DocumentFilter {
/** The Constant _maxCharacters. */
private static final int _maxCharacters = 10;
/** The _is weight. */
private Boolean _isWeight = null;
public IntAndDotFilter(Boolean isWeight) {
super();
_isWeight = isWeight;
}
public void replace(FilterBypass fb, int offset, int length, String string,
AttributeSet attr) throws BadLocationException {
String text = fb.getDocument().getText(0, fb.getDocument().getLength());
text += string;
if (_isWeight) {
if ((fb.getDocument().getLength() + string.length() - length) <= _maxCharacters
&& text.matches("^[1]?[0-9]{1,2}([.][0-9]{0,2})?$")) {
super.replace(fb, offset, length, string, attr);
} else {
Toolkit.getDefaultToolkit().beep();
}
} else {
if ((fb.getDocument().getLength() + string.length() - length) <= _maxCharacters
&& text.matches("^([1]([.][0-9]{0,2})?)|([2]([.][0-5]?)?)$")) {
super.replace(fb, offset, length, string, attr);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
}
#Override
public void remove(FilterBypass fb, int offset, int length)
throws BadLocationException {
String text = fb.getDocument().getText(0, fb.getDocument().getLength());
if (_isWeight) {
if (text.matches("^[1]?[0-9]{1,2}([.][0-9]{0,2})?$")) {
super.remove(fb, offset, length);
} else {
Toolkit.getDefaultToolkit().beep();
}
} else {
if (text.matches("^([1]([.][0-9]{0,2})?)|([2]([.][0-5]?)?)$")) {
super.remove(fb, offset, length);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
}
You're making the filtering more complicated than it has to be. For inserting (if the code is same is replace), you are not able to enter probably because of the \\. check. You will only be able to paste a period, as that's what you are checking for. As for the remove, the suggestion below will apply.
To simplify things, you should just get the entire text of the document, then use regex to check if the entire document string matches the regex. It's much simpler than what you are trying to do. You can get a good explanation of the filtering process here.
Here's an example, using just insertString and replace. For the remove, it's no different, just get the text, and check if it matches a regex. I took part of the example from the answer in the link above. The scenario was that the OP wanted max charcters, along with only one decimal place allowed. That's what the regex matches. But could also match anything as you're typing or inserting such as 00 00. 00.0
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
public class FilterDemo {
public FilterDemo() {
JFrame frame = new JFrame();
frame.setLayout(new GridBagLayout());
frame.setSize(300, 300);
frame.add(createFilteredField());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public JTextField createFilteredField() {
JTextField field = new JTextField();
AbstractDocument document = (AbstractDocument) field.getDocument();
final int maxCharacters = 10;
document.setDocumentFilter(new DocumentFilter() {
public void replace(FilterBypass fb, int offs, int length,
String str, AttributeSet a) throws BadLocationException {
String text = fb.getDocument().getText(0,
fb.getDocument().getLength());
text += str;
if ((fb.getDocument().getLength() + str.length() - length) <= maxCharacters
&& text.matches("^[0-9]+[.]?[0-9]{0,1}$")) {
super.replace(fb, offs, length, str, a);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
public void insertString(FilterBypass fb, int offs, String str,
AttributeSet a) throws BadLocationException {
String text = fb.getDocument().getText(0,
fb.getDocument().getLength());
text += str;
if ((fb.getDocument().getLength() + str.length()) <= maxCharacters
&& text.matches("^[0-9]+[.]?[0-9]{0,1}$")) {
super.insertString(fb, offs, str, a);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
});
return field;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new FilterDemo();
}
});
}
}
This is a version of Paul Samsotha's answer with some bugs fixed, and the regular expression passed as an argument for more generic use. The original allowed insertion or replacement of an invalid character at the beginning of the input, or removal of a leading character that caused the field to become invalid (it didn't implement remove(). Once an invalid character was present, the input became locked.
This version should fix all those issues.
Sample usage:
// Store the labels and textfields for later retrieval
Vector<JLabel> jLabels = new Vector<JLabel>(8);
Vector<JTextField> jTextFields = new Vector<JTextField>(8);
//Create and populate the panel.
Container contentPane = getContentPane();
// Wrap everything in a BorderLayout
JPanel borderPanel = new JPanel(new BorderLayout());
contentPane.add(borderPanel);
// Put the input fields in the CENTER position
JPanel fieldPanel = new JPanel(new SpringLayout());
borderPanel.add(fieldPanel, BorderLayout.CENTER);
// Put the input fields in the CENTER position
JPanel fieldPanel = new JPanel(new SpringLayout());
borderPanel.add(fieldPanel, BorderLayout.CENTER);
String[] labels = {"Player Name: ", "Initial Chips: "};
String[] filters = {"^[A-Za-z][A-Za-z0-9_ ]*$", "^[1-9][0-9]*$"};
final int numPairs = labels.length;
// Create and associate the field inputs and labels and regex filters
for (int i = 0; i < numPairs; i++) {
JLabel label = new JLabel(labels[i], JLabel.TRAILING);
fieldPanel.add(label);
JTextField textField = createFilteredField(filters[i]);
label.setLabelFor(textField);
fieldPanel.add(textField);
jLabels.add(label);
jTextFields.add(textField);
}
Code:
/* Filtered Text Field
* #param regex
* the regular expression to which this string is to be matched
*
* #return {#code true} if, and only if, this string matches the
* given regular expression
*
* #throws PatternSyntaxException
* if the regular expression's syntax is invalid
*/
public JTextField createFilteredField(String regex) {
JTextField field = new JTextField(12);
AbstractDocument document = (AbstractDocument) field.getDocument();
final int maxCharacters = 20;
document.setDocumentFilter(new DocumentFilter() {
/**
* Invoked prior to removal of the specified region in the
* specified Document. Subclasses that want to conditionally allow
* removal should override this and only call supers implementation as
* necessary, or call directly into the <code>FilterBypass</code> as
* necessary.
*
* #param fb FilterBypass that can be used to mutate Document
* #param offset the offset from the beginning >= 0
* #param length the number of characters to remove >= 0
* #exception BadLocationException some portion of the removal range
* was not a valid part of the document. The location in the exception
* is the first bad position encountered.
*/
public void remove(FilterBypass fb, int offset, int length) throws
BadLocationException {
String text = fb.getDocument().getText(0, fb.getDocument().getLength());
String newText = text.substring(0, offset) + text.substring(offset + length);
if (newText.matches(regex) || newText.length() == 0) {
super.remove(fb, offset, length);
}
}
/**
* Invoked prior to replacing a region of text in the
* specified Document. Subclasses that want to conditionally allow
* replace should override this and only call supers implementation as
* necessary, or call directly into the FilterBypass.
*
* #param fb FilterBypass that can be used to mutate Document
* #param offset Location in Document
* #param length Length of text to delete
* #param _text Text to insert, null indicates no text to insert
* #param attrs AttributeSet indicating attributes of inserted text,
* null is legal.
* #exception BadLocationException the given insert position is not a
* valid position within the document
*/
public void replace(FilterBypass fb, int offset, int length,
String _text, AttributeSet attrs) throws BadLocationException {
String text = fb.getDocument().getText(0, fb.getDocument().getLength());
String newText = text.substring(0, offset) + _text + text.substring(offset + length);
if (newText.length() <= maxCharacters && newText.matches(regex)) {
super.replace(fb, offset, length, _text, attrs);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
/**
* Invoked prior to insertion of text into the
* specified Document. Subclasses that want to conditionally allow
* insertion should override this and only call supers implementation as
* necessary, or call directly into the FilterBypass.
*
* #param fb FilterBypass that can be used to mutate Document
* #param offset the offset into the document to insert the content >= 0.
* All positions that track change at or after the given location
* will move.
* #param string the string to insert
* #param attr the attributes to associate with the inserted
* content. This may be null if there are no attributes.
* #exception BadLocationException the given insert position is not a
* valid position within the document
*/
public void insertString(FilterBypass fb, int offset, String string,
AttributeSet attr) throws BadLocationException {
String text = fb.getDocument().getText(0, fb.getDocument().getLength());
String newText = text.substring(0, offset) + string + text.substring(offset);
if ((fb.getDocument().getLength() + string.length()) <= maxCharacters
&& newText.matches(regex)) {
super.insertString(fb, offset, string, attr);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
});
return field;
}
In one of my java applications, there is a field where the user is supposed to enter a time. I know, I can simply keep it as a normal JTextField and at the time of validation I can check the value and so on...
But I think there is a better way than that..
For example, if this is a matter with a date, then JXDatePicker (which comes with swingx) can be used very handily. Eventually, the users selection is guaranteed to be a date.
Likewise, there must be a better way for time selection too. Will someone share your knowledge if you know a better solution.
Any thought is appreciated. Thank you!
you could use a JFormattedTextField have a look at the example below, this will create a JFormattedTextField which will accept only numbers and put them in the form XXhXXminXXs then add a ActionListener to the JFormattedTextField and in that attempt to parse to a valid time object when ENTER for now I only show the use of the JFormattedTextField and the ActionListener I didn't attempt to convert or any converting and parsing for valid time:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.ParseException;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.text.MaskFormatter;
public class FormattedTextFieldExample {
public FormattedTextFieldExample() {
initComponents();
}
private void initComponents() {
JFrame frame = new JFrame("JFormattedTextField Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MaskFormatter mask = null;
try {
mask = new MaskFormatter("##h##min##s");//the # is for numeric values
mask.setPlaceholderCharacter('#');
} catch (ParseException e) {
e.printStackTrace();
}
//
// Create a formatted text field that accept a valid time.
//
final JFormattedTextField timeField = new JFormattedTextField(mask);
//Add ActionListener for when enter is pressed
timeField.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
Object source = ae.getSource();
if (source == timeField) {
//parse to a valid time here
System.out.println(timeField.getText());
}
}
});
frame.add(timeField);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new FormattedTextFieldExample();
}
});
}
}
Multiple JSpinner instances allowing to select hours, minutes and seconds
Use a JFormattedTextField with some improvements to provide immediate user feedback (for example color the background red as soon as the input becomes invalid)
I did a prototype of a TimeField some time ago, it still needs a little work, but the concept is pretty basic.
It basically presents two JTextFields, one for the hour and one for the minutes and makes them appear to be a single field.
Through the use of some DocumentFilters, it restricts the users input.
I did this some time ago and it needs work, but the basic idea is there...if you have problems, you will need to try and figure it out yourself ;)
/**
*
* #author MadProgrammer
*/
public class TimeField extends javax.swing.JPanel {
// The time of day...
public enum TimeOfDay {
AM,
PM
}
private HourDocumentFilter hourDocumentFilter;
private MinuteDocumentFilter minDocumentFilter;
private HourKeyHandler hourKeyHandler;
private MinuteKeyHandler minuteKeyHandler;
private HourFocusHandler hourFocusHandler;
private MinuteFocusHandler minuteFocusHandler;
private boolean use24HourClock;
private ActionHandler actionHandler;
/**
* Creates new form TimeField
*/
public TimeField() {
initComponents();
pnlFields.setBorder(new CompoundBorder(UIManager.getBorder("TextField.border"),new EmptyBorder(0, 2, 0, 2)));
set24HourClock(false);
setTime(new Date());
fldHour.addKeyListener(new HourKeyHandler());
}
#Override
public void addNotify() {
super.addNotify();
// Add all the required functionality to make this thing work...
((AbstractDocument) fldHour.getDocument()).setDocumentFilter(getHourDocumentFilter());
((AbstractDocument) fldMin.getDocument()).setDocumentFilter(getMinuteDocumentFilter());
fldHour.addFocusListener(getHourFocusHandler());
fldMin.addFocusListener(getMinuteFocusHandler());
fldHour.addKeyListener(getHourKeyHandler());
fldMin.addKeyListener(getMinuteKeyHandler());
fldHour.addActionListener(getActionHandler());
fldMin.addActionListener(getActionHandler());
cmbTimeOfDay.addActionListener(getActionHandler());
}
#Override
public void removeNotify() {
// Clean up our listeners...
((AbstractDocument) fldHour.getDocument()).setDocumentFilter(null);
((AbstractDocument) fldMin.getDocument()).setDocumentFilter(null);
fldHour.removeFocusListener(getHourFocusHandler());
fldMin.removeFocusListener(getMinuteFocusHandler());
fldHour.removeKeyListener(getHourKeyHandler());
fldMin.removeKeyListener(getMinuteKeyHandler());
fldHour.removeActionListener(getActionHandler());
fldMin.removeActionListener(getActionHandler());
cmbTimeOfDay.removeActionListener(getActionHandler());
super.removeNotify();
}
/**
* Adds an action listener to the component. Actions are fired when the user
* presses the enter key
*
* #param listener
*/
public void addActionListener(ActionListener listener) {
listenerList.add(ActionListener.class, listener);
}
public void removeActionListener(ActionListener listener) {
listenerList.remove(ActionListener.class, listener);
}
/**
* Returns the field that is acting as the hour editor
*
* #return
*/
public JTextField getHourEditor() {
return fldHour;
}
/**
* Returns the field that is acting as the minute editor
*
* #return
*/
public JTextField getMinuteEditor() {
return fldMin;
}
/**
* Returns the combo box that provides the time of day selection
*
* #return
*/
public JComboBox getTimeOfDayEditor() {
return cmbTimeOfDay;
}
/**
* Returns the internal action handler. This handler monitors actions on the
* individual components and merges them into one.
*
* #return
*/
protected ActionHandler getActionHandler() {
if (actionHandler == null) {
actionHandler = new ActionHandler();
}
return actionHandler;
}
/**
* Returns the hour key listener
*
* #return
*/
protected HourKeyHandler getHourKeyHandler() {
if (hourKeyHandler == null) {
hourKeyHandler = new HourKeyHandler();
}
return hourKeyHandler;
}
/**
* Returns the minute key listener
*
* #return
*/
protected MinuteKeyHandler getMinuteKeyHandler() {
if (minuteKeyHandler == null) {
minuteKeyHandler = new MinuteKeyHandler();
}
return minuteKeyHandler;
}
/**
* Returns the document filter used to filter the hour field
*
* #return
*/
protected HourDocumentFilter getHourDocumentFilter() {
if (hourDocumentFilter == null) {
hourDocumentFilter = new HourDocumentFilter();
}
return hourDocumentFilter;
}
/**
* Returns the document filter user to filter the minute field
*
* #return
*/
protected MinuteDocumentFilter getMinuteDocumentFilter() {
if (minDocumentFilter == null) {
minDocumentFilter = new MinuteDocumentFilter();
}
return minDocumentFilter;
}
/**
* Returns the focus listener used to monitor the hour field
*
* #return
*/
protected HourFocusHandler getHourFocusHandler() {
if (hourFocusHandler == null) {
hourFocusHandler = new HourFocusHandler();
}
return hourFocusHandler;
}
/**
* Used the focus listener used to monitor the minute field
*
* #return
*/
protected MinuteFocusHandler getMinuteFocusHandler() {
if (minuteFocusHandler == null) {
minuteFocusHandler = new MinuteFocusHandler();
}
return minuteFocusHandler;
}
/**
* Sets the time based on the supplied date
*
* #param date
*/
public void setTime(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
int hour = cal.get(Calendar.HOUR);
int min = cal.get(Calendar.MINUTE);
int dayPart = cal.get(Calendar.AM_PM);
TimeOfDay timeOfDay = TimeOfDay.AM;
switch (dayPart) {
case Calendar.PM:
timeOfDay = TimeOfDay.PM;
break;
}
setTime(hour, min, timeOfDay);
}
/**
* Sets the time based on a 24 hour clock. The field does not need to be in 24
* hour mode to use this method, the method will automatically correct the
* hour appropriately.
*
* #param hour
* #param min
*/
public void setTime(int hour, int min) {
hour = correctHour(hour);
min = correctMinute(min);
TimeOfDay timeOfDay = TimeOfDay.AM;
if (hour >= 12) {
timeOfDay = TimeOfDay.PM;
}
setTime(hour, min, timeOfDay);
}
/**
* Corrects the minute value to make sure it is within allowable ranges.
*
* For example, if you pass in 90 the method, it will automatically correct
* the value to 30, discard the overflow.
*
* This will not effect the hour value...although this might be worth
* consideration in the future
*
* #param min
* #return
*/
protected int correctMinute(int min) {
// Make sure the value is positive.
// If we were interested in altering the hour value as well, we wouldn't
// want to do this...
if (min < 0) {
min += (min * -2);
}
// Correct the minute value....
if (min > 59) {
// How many hours fit into this value
float part = min / 60f;
part = (float) (part - Math.floor(part)); // Get remainder
min = (int) (60 * part); // Calculate the number of minutes...
}
return min;
}
/**
* Basically, this method will attempt to correct the hour value and bring the
* it into range of a single day.
*
* We are basically going to try and figure out how many parts of the day that
* the hour falls in and make it equal to a single day...
*
* That is, if the hour is 35, it's actually 1.458... days, which is roughly 1
* day and 11 hours. We are only interested in the 11 hours, cause the date is
* irrelevant to us
*
* #param hour
* #return
*/
protected int correctHour(int hour) {
if (hour < 0) {
hour += (hour * -2);
}
if (hour > 23) {
float part = hour / 24f;
part = (float) (part - Math.floor(part));
hour = (int) (24 * part);
}
return hour;
}
/**
* Sets the time value for this field...
*
* #param hour
* #param min
* #param timeOfDay
*/
public void setTime(int hour, int min, TimeOfDay timeOfDay) {
hour = correctHour(hour);
min = correctMinute(min);
// Now that we have a correct hour value, we need to know if it will
// actually fit within the correct part of the day...
switch (timeOfDay) {
case AM:
cmbTimeOfDay.setSelectedIndex(0);
break;
case PM:
cmbTimeOfDay.setSelectedIndex(1);
break;
}
if (!is24HourClock()) {
if (hour > 12) {
hour -= 12;
}
} else {
if (hour < 12 && timeOfDay.equals(TimeOfDay.PM)) {
hour += 12;
}
}
fldHour.setText(pad(Integer.toString(hour), 2));
fldMin.setText(pad(Integer.toString(min), 2));
}
public int getHour() {
return Integer.parseInt(getHourEditor().getText());
}
public int getMinute() {
return Integer.parseInt(getMinuteEditor().getText());
}
public TimeOfDay getTimeOfDay() {
TimeOfDay tod = null;
switch (cmbTimeOfDay.getSelectedIndex()) {
case 0:
tod = TimeOfDay.AM;
break;
case 1:
tod = TimeOfDay.PM;
break;
}
return tod;
}
/**
* Sets if we should be using 24 or 12 hour clock. This basically configures
* the time of day field and the validation ranges of the various fields
*
* #param value
*/
public void set24HourClock(boolean value) {
if (value != use24HourClock) {
use24HourClock = value;
cmbTimeOfDay.setVisible(!use24HourClock);
if (cmbTimeOfDay.getSelectedIndex() == 1) {
setTime(getHour() + 12, getMinute(), getTimeOfDay());
}
invalidate();
firePropertyChange("24HourClock", !use24HourClock, value);
}
}
/**
* Returns if this is using a 24 or 12 hour clock
*
* #return
*/
public boolean is24HourClock() {
return use24HourClock;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
#SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
cmbTimeOfDay = new javax.swing.JComboBox();
pnlFields = new javax.swing.JPanel();
lblSeperator = new javax.swing.JLabel();
fldHour = new javax.swing.JTextField();
fldMin = new javax.swing.JTextField();
addFocusListener(new java.awt.event.FocusAdapter() {
public void focusGained(java.awt.event.FocusEvent evt) {
doFocusGained(evt);
}
});
setLayout(new java.awt.GridBagLayout());
cmbTimeOfDay.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"am", "pm"}));
cmbTimeOfDay.setBorder(null);
cmbTimeOfDay.setEditor(null);
cmbTimeOfDay.setOpaque(false);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.insets = new java.awt.Insets(0, 4, 0, 0);
add(cmbTimeOfDay, gridBagConstraints);
pnlFields.setBackground(new java.awt.Color(255, 255, 255));
pnlFields.setLayout(new java.awt.GridBagLayout());
lblSeperator.setText(":");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 2);
pnlFields.add(lblSeperator, gridBagConstraints);
fldHour.setBorder(null);
fldHour.setColumns(2);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
pnlFields.add(fldHour, gridBagConstraints);
fldMin.setBorder(null);
fldMin.setColumns(2);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 0;
pnlFields.add(fldMin, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
add(pnlFields, gridBagConstraints);
}// </editor-fold>
private void doFocusGained(java.awt.event.FocusEvent evt) {
fldHour.requestFocus();
}
// Variables declaration - do not modify
private javax.swing.JComboBox cmbTimeOfDay;
private javax.swing.JTextField fldHour;
private javax.swing.JTextField fldMin;
private javax.swing.JLabel lblSeperator;
private javax.swing.JPanel pnlFields;
// End of variables declaration
/**
* Moves the focus forward to the next field.
*
* This is used to provide "automatic" focus movement
*/
protected void moveFocusForward() {
if (fldHour.hasFocus()) {
fldMin.requestFocus();
} else if (fldMin.hasFocus()) {
cmbTimeOfDay.requestFocus();
}
}
/**
* Moves the focus backwards to the previous field.
*
* This is used to provide "automatic" focus movement
*/
protected void moveFocusBackward() {
if (fldMin.hasFocus()) {
fldHour.requestFocus();
} else if (cmbTimeOfDay.hasFocus()) {
fldMin.requestFocus();
}
}
/**
* Fires the action performed event to all registered listeners
*
* #param evt
*/
protected void fireActionPerformed(ActionEvent evt) {
List<ActionListener> lstListeners = Arrays.asList(listenerList.getListeners(ActionListener.class));
if (!lstListeners.isEmpty()) {
Collections.reverse(lstListeners);
for (ActionListener listener : lstListeners) {
listener.actionPerformed(evt);
}
}
}
/**
* Hour key handler, used to monitor "special" keys for the hour field.
*
* This looks for the user pressing the ":" key and the right arrow key in
* order to perform special navigation
*/
protected class HourKeyHandler extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
boolean numLock = false;
try {
// Get the state of the nums lock
numLock = Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_NUM_LOCK);
} catch (Exception exp) {
}
// Move focus forward if the user presses the ":"
if (e.getKeyCode() == KeyEvent.VK_SEMICOLON && e.isShiftDown()) {
moveFocusForward();
// Move focus forward if the user pressed the left arrow key
} else if ((e.getKeyCode() == KeyEvent.VK_NUMPAD6 && !numLock) || e.getKeyCode() == KeyEvent.VK_RIGHT) {
// If we are in the last edit position
if (fldHour.getCaretPosition() >= 2) {
moveFocusForward();
// Or we are in the first edit position and the field only contains a single character
} else if (fldHour.getText().trim().length() == 1 && fldHour.getCaretPosition() == 1) {
moveFocusForward();
}
}
}
}
/**
* Minute key handler, used to monitor "special" keys for the hour field.
*
* This looks for the user pressing the left arrow key in order to perform
* special navigation
*/
protected class MinuteKeyHandler extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
boolean numLock = false;
try {
numLock = Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_NUM_LOCK);
} catch (Exception exp) {
}
if ((e.getKeyCode() == KeyEvent.VK_NUMPAD4 && !numLock) || e.getKeyCode() == KeyEvent.VK_LEFT) {
// Only want to move backwards if we are at the first edit position
if (fldMin.getCaretPosition() == 0) {
moveFocusBackward();
}
}
}
}
/**
* Hour field focus handler. This watches for focus lost events a
* automatically pads the field with a leading "0" if the field is only 1
* character in length
*/
protected class HourFocusHandler extends FocusAdapter {
#Override
public void focusLost(FocusEvent e) {
String text = fldHour.getText();
if (text.length() < 2) {
text = pad(text, 2);
fldHour.setText(text);
}
}
}
/**
* Minute field focus handler, watches for focus lost events and automatically
* adds a "0" to the end of the field if it is only 1 character in length
*/
protected class MinuteFocusHandler extends FocusAdapter {
#Override
public void focusLost(FocusEvent e) {
String text = fldMin.getText();
if (text.length() < 2) {
fldMin.setText(text + "0");
}
}
}
/**
* The document filter used to filter the hour field.
*/
protected class HourDocumentFilter extends DocumentFilter {
#Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
System.out.println("insert: offset = " + offset + "; text = " + text);
super.insertString(fb, offset, text, attr);
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
try {
boolean isAcceptable = false;
boolean passOnFocus = false;
int strLength = text.length();
// We convert the value here to make sure it's a number...
int value = Integer.parseInt(text);
// If the length of the string been replaced is only 1 character
if (strLength == 1) {
// If we are at the start of the editing position
if (offset == 0) {
// What clock type are we using...
if (!is24HourClock()) {
// only accept 0 or 1...
if (value <= 1) {
isAcceptable = true;
}
} else if (value <= 2) {
isAcceptable = true;
}
// If we are at the second editing position
} else if (offset == 1) {
// Get the preceeding value, should be 0, 1 or 2
String upperPart = fb.getDocument().getText(0, 1);
// Convert the value to an int
int upperValue = Integer.parseInt(upperPart);
// The acceptable range of values for the given position
int lowerRange = 0;
int upperRange = 9;
// Which clock are we using
if (is24HourClock()) {
// If the first value is 2, we can only accept values from 0-3 (20-23)
if (upperValue == 2) {
upperRange = 3;
}
} else {
// 12 hour clock
// If the first value is 1, we can only accept values from 0-2 (10-12)
if (upperValue == 1) {
upperRange = 2;
}
}
// Is the value within accpetable range...
if (value >= lowerRange && value <= upperRange) {
isAcceptable = true;
}
// Pass on focus (only if the value is accepted)
passOnFocus = true;
}
} else {
// First, we need to trim the value down to a maximum of 2 characters
// Need to know at what offest...
// 2 - offset..
// offset == 0, length = 2 - offset = 2
// offset == 1, length = 2 - offset = 1
strLength = 2 - offset;
String timeText = text.substring(offset, strLength);
value = Integer.parseInt(timeText);
// this will only work if we are using a 24 hour clock
if (value >= 0 && value <= 23) {
while (value > 12 && is24HourClock()) {
value -= 12;
}
// Pad out the text if required
text = pad(value, 2);
isAcceptable = true;
}
}
if (isAcceptable) {
super.replace(fb, offset, length, text, attrs);
if (passOnFocus) {
moveFocusForward();
}
}
} catch (NumberFormatException exp) {
}
}
}
/**
* The document filter used to filter the minute field.
*/
protected class MinuteDocumentFilter extends DocumentFilter {
#Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
System.out.println("insert: offset = " + offset + "; text = " + text);
super.insertString(fb, offset, text, attr);
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
try {
boolean isAcceptable = false;
boolean passOnFocus = false;
// How long is the text been added
int strLength = text.length();
// Convert the value to an integer now and save us the hassel
int value = Integer.parseInt(text);
// If the length is only 1, probably a new character has been added
if (strLength == 1) {
// The valid range of values we can accept
int upperRange = 9;
int lowerRange = 0;
if (offset == 0) {
// If we are at the first edit position, we can only accept values
// from 0-5 (50 minutes that is)
upperRange = 5;
} else if (offset == 1) {
// Second edit position...
// Every thing is valid here...
// We want to pass on focus if the clock is in 12 hour mode
passOnFocus = !is24HourClock();
}
// Is the value acceptable..
if (value >= lowerRange && value <= upperRange) {
isAcceptable = true;
}
} else {
// Basically, we are going to trim the value down to at max 2 characters
// Need to know at what offest...
// 2 - offset..
// offset == 0, length = 2 - offset = 2
// offset == 1, length = 2 - offset = 1
strLength = 2 - offset;
String timeText = text.substring(offset, strLength);
value = Integer.parseInt(timeText);
if (value >= 0 && value <= 59) {
// Pad out the value as required
text = pad(value, 2);
isAcceptable = true;
}
}
if (isAcceptable) {
super.replace(fb, offset, length, text, attrs);
if (passOnFocus) {
moveFocusForward();
}
}
} catch (NumberFormatException exp) {
}
}
}
/**
* This is a simple "pass" on action handler...
*/
protected class ActionHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
ActionEvent evt = new ActionEvent(TimeField.this, e.getID(), e.getActionCommand(), e.getModifiers());
fireActionPerformed(evt);
}
}
public static String pad(long lValue, int iMinLength) {
return pad(Long.toString(lValue), 2);
}
public static String pad(int iValue, int iMinLength) {
return pad(Integer.toString(iValue), iMinLength);
}
public static String pad(String sValue, int iMinLength) {
StringBuilder sb = new StringBuilder(iMinLength);
sb.append(sValue);
while (sb.length() < iMinLength) {
sb.insert(0, "0");
}
return sb.toString();
}
}
why should someone chose this bulky implementation over above given DavidKroukamp's simple solution. Also, this has some bugs when manually entering numbers. I prefer DavidKroukamp's way..
Simple answer, validation. JFormattedTextField does not valid the values, but only limits the fact that you can enter digits, you could just as easily enter 99:99 into a JFormattedTextField. The intention of this implementation is to provide real time validation while providing simple data entry requirements
I don't see any reasons for multiplay JSpinner, or JFormattedTextField, this doens't make me any sence, those concepts aren't user-friendly, have look at:
use Java Calendar
use simple JSpinner with SpinnerDateModel