Search code examples
javaswingsimpledateformatjformattedtextfield

Java: where is source of unwanted behavior in my JFormattedTextField?


My current Java project requires me to work with dates and through my research and tinkering around I discovered the setLenient() method is defaulted to true. When I set it to false I begin to have problems with my code.

I have a JFormattedTextField intialized with a SimpleDateFormat. For my project, I need to have / placeholders always present so using the answer to this question, I was able to install /s as place holders in the form of MM/dd/yyyy

When the SimpleDateFormat is set to setLenient(false), and an incomplete date is entered (such as 12/34/5), and then have the focus shift away from the formatted textfield, the entire field including the /s is removed.

When SimpleDateFormat is set to setLenient(true), this doesn't happen. Instead, the incomplete entry of 12/34/5 is set to 01/03/0006 once focus is lost.

I need to be able to usesetLenient(false) on my simple date formatter while also always having / placeholders. I do not really know where the core issue lies in the unwanted behavior of the program and would appreciate any insight.

Simplified program concerning issue:

public class Hello implements ActionListener{
    private JFrame frame = new JFrame();
    private JPanel panel = new JPanel();
    private Date endingDate = new Date();
    private String endingString = null;
    private SimpleDateFormat dateFormatter = new SimpleDateFormat("MM/dd/yyyy");
    private JFormattedTextField formattedText = new JFormattedTextField(dateFormatter);
    private JLabel label1 = new JLabel();
    private JLabel label2 = new JLabel();
    private TextArea ta = new TextArea();
    private Button b = new Button("click");

    public Hello() {
        //setPreferredSize(new Dimension(500,500));
        b.addActionListener(this);
        label1 = new JLabel("test");
        label2 = new JLabel("test");
        formattedText.setColumns(10);
        dateFormatter.setLenient(false); // not efficient 
        try {
            MaskFormatter dateMask = new MaskFormatter("##/##/####");
            dateMask.install(formattedText);
        } catch (ParseException ex) {
            Logger.getLogger(Hello.class.getName()).log(Level.SEVERE, null, ex);
        }

        panel.add(formattedText);
        panel.add(label1);
        panel.add(label2);
        panel.add(ta);
        panel.add(b);
        frame.add(panel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);


    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                new Hello();    
            }
        });

        System.out.println("Hello, World");
    }

    public void actionPerformed(ActionEvent e) {
        System.out.println("Action performed");
        System.out.println(formattedText);
        endingDate = (Date) formattedText.getValue();

        System.out.println(endingDate);
        endingString = dateFormatter.format(endingDate);
        System.out.println(endingString);

    }

}

Solution

  • You can use an InputVerifier that will verify the contents. If the date is not valid, you can popup a JOptionPane.

    See the example below. I try to parse the input in the method isValidDate. If it's not a valid date, it will return false, causing the InputVerifier to return false in it's verify method.

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    import javax.swing.Box;
    import javax.swing.InputVerifier;
    import javax.swing.JComponent;
    import javax.swing.JFormattedTextField;
    import javax.swing.JFrame;
    import javax.swing.JOptionPane;
    import javax.swing.JTextField;
    import javax.swing.SwingUtilities;
    import javax.swing.border.EmptyBorder;
    import javax.swing.text.MaskFormatter;
    
    public class InputVerifyDate {
    
        private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy");
    
        public InputVerifyDate() {
            JFormattedTextField formattedField = createFormattedTextField();
            JTextField field = new JTextField(10);
            format.setLenient(false);
    
            Box box = Box.createVerticalBox();
            box.add(formattedField);
            box.add(Box.createVerticalStrut(10));
            box.add(field);
            box.setBorder(new EmptyBorder(10, 10, 10, 10));
    
            JFrame frame = new JFrame();
            frame.add(box);
            frame.pack();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        private JFormattedTextField createFormattedTextField() {
            JFormattedTextField formattedField = null;
            try {
                MaskFormatter dateMask = new MaskFormatter("##/##/####");
                formattedField = new JFormattedTextField(dateMask);
            } catch (ParseException ex) {
                Logger.getLogger(InputVerifyDate.class.getName()).log(Level.SEVERE, null, ex);
            }
            formattedField.setColumns(10);
            formattedField.setInputVerifier(getInputVerifier());
            return formattedField;
        }
    
        private InputVerifier getInputVerifier() {
            InputVerifier verifier = new InputVerifier() {
    
                @Override
                public boolean verify(JComponent input) {
                    JFormattedTextField field = (JFormattedTextField) input;
                    String text = field.getText();
                    return isValidDate(text);
                }
    
                @Override
                public boolean shouldYieldFocus(JComponent input) {
                    boolean valid = verify(input);
                    if (!valid) {
                        JOptionPane.showMessageDialog(null, "Please enter a valid date in format dd/mm/yyyy");
                    }
                    return valid;
                }
    
            };
            return verifier;
        }
    
        public boolean isValidDate(String dateString) {
            try {
                format.parse(dateString);
                return true;
            } catch (ParseException ex) {
                return false;
    
            }
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    new InputVerifyDate();
                }
            });
    
        }
    }