Forum Stats

  • 3,757,144 Users
  • 2,251,201 Discussions
  • 7,869,743 Comments

Discussions

Text Field validation for time format

962397
962397 Member Posts: 6
edited Jun 20, 2013 3:57AM in JavaFX 2.0 and Later

hi All,

I have been breaking my head for nearly 2 weeks but am not able to figure it out.. plz help me to resolve the riddle.. here it goes :

In a text field i want to perform a operation in the following way:

> a default text 00:00:00 is set for time as the text field is for time and the character length of the field is 8.

> if the cursor is in position 0(zero) and if i press a number key it should replace the position 0 value and move the cursor to next position.

eg : 00:00:00

        20:00:00

explanation : now the cursor is in position 0 and i press the key 2 it should replace the value 0 in position 0 and den move the cursor to position 1 ie. to next position..

when i press any non-numeric key it should not replace any of the value but the cursor should move to next position.

962397

Best Answer

  • James_D
    James_D Member Posts: 1,496 Gold Trophy
    Accepted Answer

    This seems to work ok. You should test it more, though. I added some properties for hours, minutes, and seconds as well.

    import java.util.regex.Pattern;
    
    import javafx.application.Application;
    import javafx.beans.binding.Bindings;
    import javafx.beans.binding.IntegerBinding;
    import javafx.beans.property.ReadOnlyIntegerProperty;
    import javafx.beans.property.ReadOnlyIntegerWrapper;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.control.IndexRange;
    import javafx.scene.control.Label;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class TimeTextFieldTest extends Application {
    
     @Override
      public void start(Stage primaryStage) {
      VBox root = new VBox(5);
      root.setPadding(new Insets(5));
      Label hrLabel = new Label();
      Label minLabel = new Label();
      Label secLabel = new Label();
      TimeTextField timeTextField = new TimeTextField();
      hrLabel.textProperty().bind(Bindings.format("Hours: %d", timeTextField.hoursProperty()));
      minLabel.textProperty().bind(Bindings.format("Minutes: %d", timeTextField.minutesProperty()));
      secLabel.textProperty().bind(Bindings.format("Seconds: %d", timeTextField.secondsProperty()));
    
      root.getChildren().addAll(timeTextField, hrLabel, minLabel, secLabel);
      Scene scene = new Scene(root);
      primaryStage.setScene(scene);
      primaryStage.show();
      }
    
      public static void main(String[] args) {
      launch(args);
    
      }
    
      public static class TimeTextField extends TextField {
       
        enum Unit {HOURS, MINUTES, SECONDS};
    
        private final Pattern timePattern ;
        private final ReadOnlyIntegerWrapper hours ;
        private final ReadOnlyIntegerWrapper minutes ;
        private final ReadOnlyIntegerWrapper seconds ;
       
        public TimeTextField() {
          this("00:00:00");
        }
        public TimeTextField(String time) {
          super(time);
          timePattern = Pattern.compile("\\d\\d:\\d\\d:\\d\\d");
          if (! validate(time)) {
            throw new IllegalArgumentException("Invalid time: "+time);
          }
          hours = new ReadOnlyIntegerWrapper(this, "hours");
          minutes = new ReadOnlyIntegerWrapper(this, "minutes");
          seconds = new ReadOnlyIntegerWrapper(this, "seconds");
          hours.bind(new TimeTextField.TimeUnitBinding(Unit.HOURS));
          minutes.bind(new TimeTextField.TimeUnitBinding(Unit.MINUTES));
          seconds.bind(new TimeTextField.TimeUnitBinding(Unit.SECONDS));
        }
       
        public ReadOnlyIntegerProperty hoursProperty() {
          return hours.getReadOnlyProperty();
        }
       
        public int getHours() {
          return hours.get() ;
        }
       
        public ReadOnlyIntegerProperty minutesProperty() {
          return minutes.getReadOnlyProperty();
        }
       
        public int getMinutes() {
          return minutes.get();
        }
       
        public ReadOnlyIntegerProperty secondsProperty() {
          return seconds.getReadOnlyProperty();
        }
       
        public int getSeconds() {
          return seconds.get();
        }
       
        @Override
        public void appendText(String text) {
          // Ignore this. Our text is always 8 characters long, we cannot append anything
        }
       
        @Override
        public boolean deleteNextChar() {
    
          boolean success = false ;
          
          // If there's a selection, delete it:
          final IndexRange selection = getSelection();
          if (selection.getLength()>0) {
            int selectionEnd = selection.getEnd();
            this.deleteText(selection);
            this.positionCaret(selectionEnd);
            success = true ;
          } else {
            // If the caret preceeds a digit, replace that digit with a zero and move the caret forward. Else just move the caret forward.
          int caret = this.getCaretPosition();
          if (caret % 3 != 2) { // not preceeding a colon
            String currentText = this.getText();
            setText(currentText.substring(0, caret) + "0" + currentText.substring(caret+1));
            success = true ;
          }
          this.positionCaret(Math.min(caret+1, this.getText().length()));
          }
          return success ;
        }
       
        @Override
        public boolean deletePreviousChar() {
    
          boolean success = false ;
    
          // If there's a selection, delete it:
          final IndexRange selection = getSelection();
          if (selection.getLength()>0) {
            int selectionStart = selection.getStart();
            this.deleteText(selection);
            this.positionCaret(selectionStart);
            success = true ;
          } else {
          // If the caret is after a digit, replace that digit with a zero and move the caret backward. Else just move the caret back.
            int caret = this.getCaretPosition();
            if (caret % 3 != 0) { // not following a colon
              String currentText = this.getText();
              setText(currentText.substring(0, caret-1) + "0" + currentText.substring(caret));
              success = true ;
            }
            this.positionCaret(Math.max(caret-1, 0));
          }
          return success ;
        }
       
        @Override
        public void deleteText(IndexRange range) {
          this.deleteText(range.getStart(), range.getEnd());
        }
       
        @Override
        public void deleteText(int begin, int end) {
          // Replace all digits in the given range with zero:
          StringBuilder builder = new StringBuilder(this.getText());
          for (int c = begin; c<end; c++) {
            if (c % 3 != 2) { // Not at a colon:
              builder.replace(c, c+1, "0");
            }
          }
          this.setText(builder.toString());
        }
       
        @Override
        public void insertText(int index, String text) {
          // Handle an insert by replacing the range from index to index+text.length() with text, if that results in a valid string:
          StringBuilder builder = new StringBuilder(this.getText());
          builder.replace(index, index+text.length(), text);
          final String testText = builder.toString();
          if (validate(testText)) {
            this.setText(testText);
          }
          this.positionCaret(index + text.length());
        }
       
    
        @Override
        public void replaceSelection(String replacement) {
          final IndexRange selection = this.getSelection();
          if (selection.getLength()==0) {
            this.insertText(selection.getStart(), replacement);
          } else {
            this.replaceText(selection.getStart(), selection.getEnd(), replacement);
          }
        }
       
        @Override
        public void replaceText(IndexRange range, String text) {
          this.replaceText(range.getStart(), range.getEnd(), text);
        }
       
        @Override
        public void replaceText(int begin, int end, String text) {
          if (begin==end) {
            this.insertText(begin, text);
          } else {
          // only handle this if text.length() is equal to the number of characters being replaced, and if the replacement results in a valid string:
          if (text.length() == end - begin) {
            StringBuilder builder = new StringBuilder(this.getText());
            builder.replace(begin, end, text);
            String testText = builder.toString();
            if (validate(testText)) {
              this.setText(testText);
            }
              this.positionCaret(end);
          }
          }
        }
       
        private boolean validate(String time) {
          if (! timePattern.matcher(time).matches()) {
            return false ;
          }
          String[] tokens = time.split(":");
          assert tokens.length == 3 ;
          try {
            int hours = Integer.parseInt(tokens[0]);
            int mins = Integer.parseInt(tokens[1]);
            int secs = Integer.parseInt(tokens[2]);
            if (hours < 0 || hours > 23) {
              return false ;
            }
            if (mins < 0 || mins > 59) {
              return false ;
            }
            if (secs < 0 || secs > 59) {
              return false ;
            }
            return true ;
          } catch (NumberFormatException nfe) {
            // regex matching should assure we never reach this catch block
            assert false ;
            return false ;
          }
        }
       
        private final class TimeUnitBinding extends IntegerBinding {
         
          final Unit unit ;
          TimeUnitBinding(Unit unit) {
            this.bind(textProperty());
            this.unit = unit ;
          }
          @Override
          protected int computeValue() {
            // Crazy enum magic
            String token = getText().split(":")[unit.ordinal()];
            return Integer.parseInt(token);
          }
         
        }
         
      }
    }
    
    962397

Answers

  • James_D
    James_D Member Posts: 1,496 Gold Trophy

    These are hard.

    I would try to subclass TextField, and override the methods

    appendText(String)

    deleteNextChar()

    deletePreviousChar()

    deleteText(IndexRange)

    deleteText(int, int)

    insertText(int, String)

    paste()

    replaceSelection()

    replaceText(IndexRange, String)

    replaceText(int, int, String)

    There is a lot more to think about than you might realize. For example, you have to decide how you are going to handle copy-and-paste into the text field, as well as when the user presses the delete or backspace key, etc.

  • James_D
    James_D Member Posts: 1,496 Gold Trophy
    Accepted Answer

    This seems to work ok. You should test it more, though. I added some properties for hours, minutes, and seconds as well.

    import java.util.regex.Pattern;
    
    import javafx.application.Application;
    import javafx.beans.binding.Bindings;
    import javafx.beans.binding.IntegerBinding;
    import javafx.beans.property.ReadOnlyIntegerProperty;
    import javafx.beans.property.ReadOnlyIntegerWrapper;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.control.IndexRange;
    import javafx.scene.control.Label;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class TimeTextFieldTest extends Application {
    
     @Override
      public void start(Stage primaryStage) {
      VBox root = new VBox(5);
      root.setPadding(new Insets(5));
      Label hrLabel = new Label();
      Label minLabel = new Label();
      Label secLabel = new Label();
      TimeTextField timeTextField = new TimeTextField();
      hrLabel.textProperty().bind(Bindings.format("Hours: %d", timeTextField.hoursProperty()));
      minLabel.textProperty().bind(Bindings.format("Minutes: %d", timeTextField.minutesProperty()));
      secLabel.textProperty().bind(Bindings.format("Seconds: %d", timeTextField.secondsProperty()));
    
      root.getChildren().addAll(timeTextField, hrLabel, minLabel, secLabel);
      Scene scene = new Scene(root);
      primaryStage.setScene(scene);
      primaryStage.show();
      }
    
      public static void main(String[] args) {
      launch(args);
    
      }
    
      public static class TimeTextField extends TextField {
       
        enum Unit {HOURS, MINUTES, SECONDS};
    
        private final Pattern timePattern ;
        private final ReadOnlyIntegerWrapper hours ;
        private final ReadOnlyIntegerWrapper minutes ;
        private final ReadOnlyIntegerWrapper seconds ;
       
        public TimeTextField() {
          this("00:00:00");
        }
        public TimeTextField(String time) {
          super(time);
          timePattern = Pattern.compile("\\d\\d:\\d\\d:\\d\\d");
          if (! validate(time)) {
            throw new IllegalArgumentException("Invalid time: "+time);
          }
          hours = new ReadOnlyIntegerWrapper(this, "hours");
          minutes = new ReadOnlyIntegerWrapper(this, "minutes");
          seconds = new ReadOnlyIntegerWrapper(this, "seconds");
          hours.bind(new TimeTextField.TimeUnitBinding(Unit.HOURS));
          minutes.bind(new TimeTextField.TimeUnitBinding(Unit.MINUTES));
          seconds.bind(new TimeTextField.TimeUnitBinding(Unit.SECONDS));
        }
       
        public ReadOnlyIntegerProperty hoursProperty() {
          return hours.getReadOnlyProperty();
        }
       
        public int getHours() {
          return hours.get() ;
        }
       
        public ReadOnlyIntegerProperty minutesProperty() {
          return minutes.getReadOnlyProperty();
        }
       
        public int getMinutes() {
          return minutes.get();
        }
       
        public ReadOnlyIntegerProperty secondsProperty() {
          return seconds.getReadOnlyProperty();
        }
       
        public int getSeconds() {
          return seconds.get();
        }
       
        @Override
        public void appendText(String text) {
          // Ignore this. Our text is always 8 characters long, we cannot append anything
        }
       
        @Override
        public boolean deleteNextChar() {
    
          boolean success = false ;
          
          // If there's a selection, delete it:
          final IndexRange selection = getSelection();
          if (selection.getLength()>0) {
            int selectionEnd = selection.getEnd();
            this.deleteText(selection);
            this.positionCaret(selectionEnd);
            success = true ;
          } else {
            // If the caret preceeds a digit, replace that digit with a zero and move the caret forward. Else just move the caret forward.
          int caret = this.getCaretPosition();
          if (caret % 3 != 2) { // not preceeding a colon
            String currentText = this.getText();
            setText(currentText.substring(0, caret) + "0" + currentText.substring(caret+1));
            success = true ;
          }
          this.positionCaret(Math.min(caret+1, this.getText().length()));
          }
          return success ;
        }
       
        @Override
        public boolean deletePreviousChar() {
    
          boolean success = false ;
    
          // If there's a selection, delete it:
          final IndexRange selection = getSelection();
          if (selection.getLength()>0) {
            int selectionStart = selection.getStart();
            this.deleteText(selection);
            this.positionCaret(selectionStart);
            success = true ;
          } else {
          // If the caret is after a digit, replace that digit with a zero and move the caret backward. Else just move the caret back.
            int caret = this.getCaretPosition();
            if (caret % 3 != 0) { // not following a colon
              String currentText = this.getText();
              setText(currentText.substring(0, caret-1) + "0" + currentText.substring(caret));
              success = true ;
            }
            this.positionCaret(Math.max(caret-1, 0));
          }
          return success ;
        }
       
        @Override
        public void deleteText(IndexRange range) {
          this.deleteText(range.getStart(), range.getEnd());
        }
       
        @Override
        public void deleteText(int begin, int end) {
          // Replace all digits in the given range with zero:
          StringBuilder builder = new StringBuilder(this.getText());
          for (int c = begin; c<end; c++) {
            if (c % 3 != 2) { // Not at a colon:
              builder.replace(c, c+1, "0");
            }
          }
          this.setText(builder.toString());
        }
       
        @Override
        public void insertText(int index, String text) {
          // Handle an insert by replacing the range from index to index+text.length() with text, if that results in a valid string:
          StringBuilder builder = new StringBuilder(this.getText());
          builder.replace(index, index+text.length(), text);
          final String testText = builder.toString();
          if (validate(testText)) {
            this.setText(testText);
          }
          this.positionCaret(index + text.length());
        }
       
    
        @Override
        public void replaceSelection(String replacement) {
          final IndexRange selection = this.getSelection();
          if (selection.getLength()==0) {
            this.insertText(selection.getStart(), replacement);
          } else {
            this.replaceText(selection.getStart(), selection.getEnd(), replacement);
          }
        }
       
        @Override
        public void replaceText(IndexRange range, String text) {
          this.replaceText(range.getStart(), range.getEnd(), text);
        }
       
        @Override
        public void replaceText(int begin, int end, String text) {
          if (begin==end) {
            this.insertText(begin, text);
          } else {
          // only handle this if text.length() is equal to the number of characters being replaced, and if the replacement results in a valid string:
          if (text.length() == end - begin) {
            StringBuilder builder = new StringBuilder(this.getText());
            builder.replace(begin, end, text);
            String testText = builder.toString();
            if (validate(testText)) {
              this.setText(testText);
            }
              this.positionCaret(end);
          }
          }
        }
       
        private boolean validate(String time) {
          if (! timePattern.matcher(time).matches()) {
            return false ;
          }
          String[] tokens = time.split(":");
          assert tokens.length == 3 ;
          try {
            int hours = Integer.parseInt(tokens[0]);
            int mins = Integer.parseInt(tokens[1]);
            int secs = Integer.parseInt(tokens[2]);
            if (hours < 0 || hours > 23) {
              return false ;
            }
            if (mins < 0 || mins > 59) {
              return false ;
            }
            if (secs < 0 || secs > 59) {
              return false ;
            }
            return true ;
          } catch (NumberFormatException nfe) {
            // regex matching should assure we never reach this catch block
            assert false ;
            return false ;
          }
        }
       
        private final class TimeUnitBinding extends IntegerBinding {
         
          final Unit unit ;
          TimeUnitBinding(Unit unit) {
            this.bind(textProperty());
            this.unit = unit ;
          }
          @Override
          protected int computeValue() {
            // Crazy enum magic
            String token = getText().split(":")[unit.ordinal()];
            return Integer.parseInt(token);
          }
         
        }
         
      }
    }
    
    962397
  • James_D
    James_D Member Posts: 1,496 Gold Trophy

    I should also mention here that this is not the ideal way to do this, but probably the easiest. There are some flaws with this: calling the setText(...) method on the subclass of TextField would enable you to pass in invalid text. setText(...) is final, so you can't override it; but even if you could that would be a bad design from an OO perspective (as well as the fact that there's no easy way to get around textProperty().set(...)).

    The real way to do this would be to mimic Richard Bair's MoneyField example, but there's quite a bit more code and complexity in there.

    962397
  • Here is an example:

    {code}

    import javafx.application.Application;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.TextField;
    import javafx.scene.input.KeyEvent;
    import javafx.scene.layout.StackPane;
    import javafx.stage.Stage;

    public class TextFieldValidation extends Application {

        @Override
        public void start(Stage primaryStage) {
            final TextField tf = new TextField("00:00:00");

            tf.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
                @Override
                public void handle(KeyEvent inputevent) {

                    int c = tf.getCaretPosition();

                    if (c <= 7) {

                        if (tf.getText(c, c + 1).equals(":")) {
                            tf.forward();
                            inputevent.consume();
                        } else if (inputevent.getCharacter().matches("\\d")) {

                            if (tf.getText(c, c + 1).equals("0")) {
                                System.out.println(inputevent.getCharacter());
                                tf.deleteNextChar();

                            }

                        } else if ("abcdefghijklmnoprstuvwxyz".contains(inputevent.getCharacter().toLowerCase())) {
                            tf.forward();
                            inputevent.consume();
                        }
                    } else {

                        inputevent.consume();
                    }
                }
            });
            StackPane root = new StackPane();
            root.getChildren().add(tf);

            Scene scene = new Scene(root, 300, 250);
            primaryStage.setScene(scene);
            primaryStage.show();

        }

        public static void main(String[] args) {
            launch(args);
        }
    }

    {/code}

    962397
  • James_D
    James_D Member Posts: 1,496 Gold Trophy

    But what if the user copies and pastes into the text field?

    962397
  • 962397
    962397 Member Posts: 6

    thks for the code.. will check tat..
    is thr any option to disable the keys like delete or backspace. etc

  • 962397
    962397 Member Posts: 6

    wrkng perfectly.. thks dude .. U R d MAN!!!

  • 962397
    962397 Member Posts: 6

    if u replace d pattern with thz REGX --- " ([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] " will make it easier to validate.. u can remove the if loop to check the condition

  • 962397
    962397 Member Posts: 6

    even thks to u.. for gvng a logical idea...

This discussion has been closed.