Java com café: Como limitar um JTextField

Como limitar um JTextField

É 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()) <= 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:  
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);

8 comentários:

  1. Eu não entendi onde posso limitar tamanhos diferentes para cada um de meus JTextFields, tem como passar um parametro do tipo int por exemplo?

    ResponderExcluir
    Respostas
    1. Olá, implementando o exemplo que passei, você pode colocar a quantidade desejada na propriedade "column" do seu JTextField. Isso pode ser feito tanto visualmente como via código: textField.setColumns(50);

      Excluir
  2. @Márcio, até interessante sua implementação, mas..

    não posso evitar de aproveitar este espaço para um desabafo, a respeito do SWING p/ variar: realmente componetes como o 'JTextField' (e principalmente o 'JFormmatedTextField') são um negação; não dá para acreditar como tão decepcionante o SWING == uma porcaria.

    Pode Delphi não ser melhor q Java, mas, com certeza dá de 10x0 no SWING.
    Espero q nós (desenvolvedores Javas) não fiquenos no descaso (agora da mercenária Oracle arght) em relação ao JavaFX as coisas sejam bastante deferentes. :O (foi mal)

    ResponderExcluir
    Respostas
    1. Derlon, fique a vontade pois este espaço é pra isso mesmo.

      Na minha visão, as pessoas que estão acostumadas ao Delphi/VB6, que montam sistemas orientados a eventos só arrastando componentes, não conseguem compreender a profundidade dos componentes Swing. Eles podem parecer complicados a primeira vista, mas estudando melhor, logo se vê o quão poderosos eles são.
      Prefiro não entrar na discussão de qual linguagem é melhor, pois como dizem por aí: a melhor linguagem é aquela que você sabe usar. Cada uma vai servir bem em determinadas situações, porém atualmente o Java é uma das linguagem mais utilizadas (ficando pouco atrás do C), enquanto o Delphi está caindo em desuso.

      Você disse "agora da mercenária Oracle", mas veja que já fazem mais de 4 anos que a Oracle comprou a Sun.

      Excluir
  3. Olá Márcio!!
    gostei muito da sua explicação e eu realmente precisava saber dessas informações, mas mesmo assim não consegui editar o numero de caracteres..
    ja tentei de todas as formas (e tambem visualmente e nao visualmente) mas de nenhuma forma adiantou.. nao sei mais o que fazer.. nao sei se é minha versao de netbeans(IDE 7.4) que nao permite ou o que.. mas realmente preciso de ajuda (é para meu tcc)
    Você me sugere algo??
    obrigada!!

    ResponderExcluir
    Respostas
    1. Olá, primeiramente me desculpe pela demora em responder. Acredito que seu problema não esteja relacionado a versão do NetBeans, porém sem mais detalhes fica difícil sugerir algo.

      Excluir