Search code examples
javaswinguser-interfacewindowbuilderdocumentfilter

JTextField to remove characters automatically when overwritting selected


I'm working with Java and I have a JTextField which can only have 4 digits. I'm using a class that extends DocumentFilter to filter out any other character, and to limit the number of characters to 4.

The problem is that once I have 4 digits, if I select all of them and try to overwrite them by typing another digit it doesn't auto overwrite and does nothing, I have to explicitly type "Backspace" or "Delete" in my keyboard to remove the 4 digits and then (once the field is clear) I can type again.

How can I make the JTextField act as the rest of the operating system that once I have some text selected if I type a character it “removes all, then writes the character” (it substitutes the contents).

I have one aux class, JustLimitDigitFilter.java:

import javax.swing.text.DocumentFilter;
import javax.swing.text.BadLocationException;

import java.awt.Toolkit;

import javax.swing.text.AttributeSet;

public class JustLimitDigitFilter extends DocumentFilter {

  int limit;

  public JustLimitDigitFilter(int limit) {
    this.limit = limit;
  }

  @Override
  public void insertString(DocumentFilter.FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {

    // if (text == null) {
    //   return;
    // }
    String str = text.replaceAll("\\D", "");
    if (!str.isEmpty() && (fb.getDocument().getLength() + str.length()) <= limit) {
      super.insertString(fb, offset, str, attr);
    } else {
      Toolkit.getDefaultToolkit().beep();
    }

  }

  @Override
  public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attr)
      throws BadLocationException {

    // if (text == null) {
    //   return;
    // }
    String str = text.replaceAll("\\D", "");
    if (!str.isEmpty() && (fb.getDocument().getLength() + str.length()) <= limit) {
      super.replace(fb, offset, length, str, attr);
    } else {
      Toolkit.getDefaultToolkit().beep();
    }

  }

}

And the main class, App.java:

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;
import javax.swing.text.DocumentFilter;

public class App {

  private JFrame frame;
  private JTextField textField;

  /**
   * Launch the application.
   */
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        try {
          App window = new App();
          window.frame.setVisible(true);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }

  /**
   * Create the application.
   */
  public App() {
    initialize();
  }

  /**
   * Initialize the contents of the frame.
   */
  private void initialize() {
    frame = new JFrame();
    frame.setBounds(100, 100, 191, 96);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().setLayout(null);

    JPanel panel = new JPanel();
    panel.setBounds(6, 6, 179, 62);
    frame.getContentPane().add(panel);
    panel.setLayout(null);

    textField = new JTextField();
    textField.setBounds(6, 6, 167, 26);
    panel.add(textField);
    textField.setColumns(10);

    // without this code below this, the textfield is “normal” when
    // something is selected if I write it overwrites the selection

    AbstractDocument doc = (AbstractDocument) textField.getDocument();
    doc.setDocumentFilter(new JustLimitDigitFilter(4));
  }

}

Any suggestion is welcome since I'm new to this, apart from the doubt I explicitly have.


Solution

  • Method replace() in class DocumentFilter actually performs two operations. First it removes length characters starting at offset, after which it inserts text at offset. Hence the following line in your replace() method causes nothing to happen when the JTextField contains the maximum allowable number of characters...

    if (!str.isEmpty() && (fb.getDocument().getLength() + str.length()) <= limit) {
    

    If the JTextField is full, then its length will be the maximum number of characters, so adding the length of str will always be greater than limit.