Como limitar um JTextField
Autor: Márcio Júnior | Publicado: 2012-03-09T12:47:00-03:00


É muito comum encontrar nos fóruns especializados em Java a seguinte questão: "Como eu faço para limitar o tamanho de um JtextField?". Ocorre um certo desapontamento dos iniciantes na linguagem por não haver um método chamado setMaxLenght(x). Ou ainda com os que trabalham visualmente, procurar a propriedade no componente que limitaria o tamanho máximo de caracteres e não encontrar. Aí programadores que vieram do Delphi por exemplo dizem "O Delphi é muito melhor, blablabla...". Só digo uma coisa: Revejam seus conceitos sobre o que é Java.

A verdade é que o componente JTextField é definido sobre um modelo de documento que por padrão é chamado PlainDocument. Portanto, para para ter o controle sobre o JTextField devemos criar uma sub classe de PlainDocument, sobrescrever alguns métodos dela e trocar o modelo do JTextField para este novo.

Minha intenção não é entrar a fundo neste assunto, mas sim dar uma solução principalmente pra quem está trabalhando visualmente e não quer ficar editanto código a todo momento.

Então vamos lá, primeiramente crie a seguinte classe no seu projeto:

import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

public class JTextFieldLimit extends PlainDocument {

    private int limit;
    // optional uppercase conversion
    private boolean toUppercase = false;
    
    private int maximo = 500;

    public JTextFieldLimit(int limit) {
        super();
        if (limit == 0)
            this.limit = maximo;
        else
            this.limit = limit;
    }

    public JTextFieldLimit(int limit, boolean upper) {
        super();
        if (limit == 0)
            this.limit = maximo;
        else
            this.limit = limit;
        toUppercase = upper;
    }

    @Override
    public void insertString(int offset, String str, AttributeSet attr)
            throws BadLocationException {
        if (str == null) {
            return;
        }

        if ((getLength() + str.length()) &lt;= limit) {
            if (toUppercase) {
                str = str.toUpperCase();
            }
            super.insertString(offset, str, attr);
        }
    }
}



Esta classe não é nenhuma novidade e pode ser encontrada em vários sites pela internet (na verdade fiz algumas mudanças nela). O que ela faz é permitir a limitação de caracteres e opcionalmente fazer a conversão para maiúsculas. Mas o problema é que pra cada JTextField criado, temos que chamar o método setDocument passando essa classe e os parâmetros. Então para resolver este problema, crie o seguinte método na classe onde estão seus campos de texto:

    public void preparaForm() {

        // JTextFields limitados e com maiúsculas
        for (Component c : this.getComponents()) {
            if (c instanceof JTextField) {
                ((JTextField) c).setDocument(new JTextFieldLimit(((JTextField) c).getColumns(), true));
            }
        }
        
    }



Este método deve ser chamado sempre antes de exibir o seu formulário. Ele vai verificar cada componente do formulário e caso este seja do tipo JTextField, será configurado para utilizar o modelo de documento apresentado. Mas onde vou definir o limite de caracteres para cada campo? Observe que o primeiro parâmetro passado para o documento é: ((JTextField) c).getColumns(). Visualmente você poderá definir este número na propriedade column do componente. Mas porque essa propriedade? Isto está parecendo uma bela gambiarra... Fique tranquilo, a propriedade column é apenas um número usado para calcular o tamanho preferido para a largura do campo. Se ele for zero, o tamanho preferido será qualquer um dependendo da implementação do objeto. Isto é, você define a largura visualmente utilizando seu gerenciador de layout preferido e pouco importará o valor definido para a propriedade column.

Enfim, trabalhando desta forma você não precisará ficar escrevendo código para configurar o documento e limite de caracteres para cada campo de texto. Isto não é problema se o projeto é simples, mas se estiver desenvolvendo vários formulários, não é muito produtivo.



Observação: O método só funciona caso esteja trabalhando com JInternalFrames e caso não esteja, pode haver alguma diferença no método que retorna os componentes (this.getComponents). Se for um JDialog por exemplo, ficaria assim: &nbsp;
this.getRootPane().getContentPane().getComponents().



Por hoje é só! Qualquer dúvida deixe nos comentários.

Editado em 17/12/2012:

Devido a alguns JTextFields estarem dentro de JPanels e JTabbedPanes, desenvolvi um método recursivo que poderá ser chamado dentro do método preparaForm citado anteriormente:
 
    private void preparaJTextFields(Object obj) {

        if (obj instanceof JTextField) {
            ((JTextField) obj).setDocument(new JTextFieldLimit(((JTextField) obj).getColumns(), true));
        }
       
        if (obj instanceof JPanel) {
            for (Component c : ((JPanel) obj).getComponents()) {
                preparaJTextFields(c);
            }
        }

        if (obj instanceof JTabbedPane) {
            for (Component c : ((JTabbedPane) obj).getComponents()) {
                preparaJTextFields(c);
            }
        }

    }   


E a chamada pode ser feita assim:
 
    public void preparaForm() {

        //Centraliza o JInternalFrame
        Dimension d = this.getDesktopPane().getSize();
        this.setLocation((d.width - this.getSize().width) / 2, (d.height - this.getSize().height) / 2);

        //JTextFields limitados
        preparaJTextFields(this.getContentPane());
        
    }


E aproveito ainda para mostrar como não permitir acentuação nos JTextFields, basta trocar a linha super.insertString(offset, str, attr); da classe JTextFieldLimit por:
 
//super.insertString(offset, str, attr);
            
// Sem acentos
String unaccented = Normalizer.normalize(str, Normalizer.Form.NFD);
unaccented = unaccented.replaceAll("[^\\p{ASCII}]", "");
super.insertString(offset, unaccented, attr);