|
|
Daniel Gonçalves
Como limitar caracteres de um JTextField? Você vai aprender a resolver este e outros problemas neste tutorial.
Introdução
Muitos estudantes da linguagem Java sentem alguma (senão muita) dificuldade ao descobrirem que, controlar a entrada de texto em um componente JTextField, pode ser um pouco mais complicado do que poderiam imaginar. Neste artigo, tentarei mostrar um caminho para que você possa criar suas próprias soluções.
Programadores Visual Basic e Delphi, que tentam se aventurar no universo da orientação à objetos da linguagem Java, ficam geralmente, abismados com o fato de não existir uma simples propriedade que determine o máximo de caracteres aceitos em um JTextField. O "raciocínio procedural" atrapalha bastante e os candidatos à migração para o Java ficam intimidados quando encontram a inteface Document e a classe abstrata AbstractDocument. De fato, uma olhada na documentação da API é intimidador para os iniciantes.
Um início simples
Vamos ver como poderia ser implementada uma solução para restringir o número de caracteres no JTextField.
A classe javax.swing.JTextField é uma subclasse da classe abstrata JTextComponent. O modelo de dados padrão é um objeto da classe PlainDocument (uma subclasse de AbstractDocument que, por sua vez, implementa a interface Document).
Se você estudar a classe PlainDocument, verá que ela herda o método insertString() da sua superclasse abstrata AbstractDocument (na verdade, insertString() é definida na interface Document). Veja o cabeçalho de insertString():
1 public void insertString(int offset, String str, AttributeSet attr) throws
2 BadLocationException
|
Sempre que o modelo de dados (o conteúdo propriamente dito) de um JTextField for modificado, o método insertString() daquele modelo será invocado (no caso, o de um objeto PlainDocument).
O argumento offset indica o deslocamento (a posição) inicial onde o objeto String str deverá ser inserido (o argumento AttributeSet não é útil para o que desejamos fazer, além de PlainDocument gerenciar modelos de conteúdo de texto que não possuem atributos de caracteres).
Agora, finalmente, ao invés de extendermos a classe JTextField, vamos extender a classe PlainDocument e criar um novo modelo de dados que seja capaz de restringir o número de caracteres aceitos no modelo de dados de qualquer JTextField.
01 /* FixedLengthDocument.java */
02
03 import javax.swing.*;
04 import javax.swing.text.*;
05
06 public class FixedLengthDocument extends PlainDocument
07 {
08 private int iMaxLength;
09
10 public FixedLengthDocument(int maxlen) {
11 super();
12 iMaxLength = maxlen;
13 }
14
15 public void insertString(int offset, String str, AttributeSet attr)
16 throws BadLocationException {
17 if (s == null) return;
18
19 if (iMaxLength <= 0) // aceitara qualquer no. de caracteres
20 {
21 super.insertString(offset, str, attr);
22 return;
23 }
24
25 int ilen = (getLength() + str.length());
26 if (ilen <= iMaxLength) // se o comprimento final for menor...
27 super.insertString(offset, str, attr); // ...aceita str
28 }
29 }
|
Bem, parece um pouco trabalhoso, e realmente é. No entanto, uma vez implementada, esta subclasse de PlainDocument é capaz de oferecer a funcionalidade exigida em muitas situações onde é necessário impedir que o usuário entre com mais caracteres do que o permitido.
Vamos dar uma olhada na classe que acabamos de criar. O construtor recebe um argumento int que deverá ser o número máximo de caracteres aceito. Dentro do corpo do construtor, a chamada super() deixa que a classe ancestral faça toda a inicialização necessária. Em seguida, a variável de instância iMaxLength recebe o valor do argumento do construtor.
O único método da superclasse PlainDocument que nos interessa sobrepor, é o método insertString(). Não se preocupe com o fato do método poder lançar uma exceção (BadLocationException) por que não há um momento em que a posição de inserção será inválida (pelo menos no nosso caso, mesmo porque, não estamos interessados na posição de inserção e, sim, no comprimento final que esta inserção provocará).
Já no corpo do método insertString(), a primeira coisa que fazemos é garantir que não estamos recebendo um objeto String que seja um ponteiro nulo.Em seguida, verificamos o nosso valor máximo de caracteres a serem aceitos: se esse valor for menor ou igual a zero, então faremos com que esta classe tenha um comportamento idêntico da sua ancestral PlainDocument, chamando o método insertString() da superclasse e simplesmente retornando. Ou seja, se o máximo de caracteres for menor ou igual a zero, o número de caracteres aceitos será indeterminado.
Agora, o trabalho real. Se você der uma olhada curiosa na documentação da API da interface Document, verá, na introdução, os métodos "que permitem o acesso aos dados que formam o conteúdo". São eles: getLength() e getText(). A classe AbstractDocument implementa estes métodos de tal modo que seja possível conhecer o número de caracteres atual do conteúdo (getLength()) e/ou obter o conteúdo propriamente (getText()). Sendo assim, iniciamos um inteiro (ilen) com um valor que é a soma do comprimento atual do conteúdo mais o comprimento da String (str) a ser inserida. Se essa soma for menor ou igual ao número máximo de caracteres (iMaxLength), então aceitaremos str fazendo uma chamada ao método insertString() da superclasse. Se não, não ocorrerá uma chamada a insertString() e o conteúdo a ser inserido (str) é ignorado.
Usando nossa classe de documento
Agora tudo ficou bastante simples: basta que se faça a substituição do modelo de dados padrão de qualquer JTextField que se queira restringir o número de caracteres aceitos:
01 /* Teste.java */
02
03 import java.awt.*;
04 import java.awt.event.*;
05 import javax.swing.*;
06
07 public class Teste {
08 public static void main(String[] args)
09 {
10 JFrame f = new TesteFrame();
11 f.setVisible(true);
12 }
13 }
14
15 class TesteFrame extends JFrame {
16 private JTextField txtExemplo;
17
18 public TesteFrame()
19 {
20 this.setTitle("Exemplo");
21 this.setSize(200, 180);
22
23 this.addWindowListener(new WindowAdapter() {
24 public void windowClosing(WindowEvent e) {
25 System.exit(0);
26 }
27 }); // nao por nada, mas adoro classes internas...
28
29 /* o codigo que interessa esta abaixo */
30 txtExemplo = new JTextField(10); // 10 é o número de colunas...
31 // ...nada a ver com que queremos!
32
33 txtExemplo.setDocument(new FixedLengthDocument(5));
34 /* isso, agora sim, teremos um JTextField que aceitará
35 no máximo 5 caracteres
36 */
37
38 this.getContentPane().add(txtExemplo, "South");
39 }
40 }
|
Note que, a classe FixedLengthDocument (FixedLengthDocument.java) deverá estar no mesmo diretório de Teste.java (ou nalgum diretório especificado em CLASSPATH).
"Adoráveis" problemas
Se você realmente testou o que acabamos de implementar, poderá ter notado um comportamento indesejável. Se não notou, tente o seguinte: Suponha que tenhamos um objeto JTextField cujo modelo de dados seja a nossa classe FixedLengthDocument, aceitando até 10 caracteres. Na execução, digite apenas 7 caracteres. Em seguida selecione 4 desses 7 caracteres e pressione Ctrl-C (no estilo visual Metal, os caracteres selecionados serão copiados na área de transferência). Em seguida, desmarque os caracteres selecionados e, em qualquer posição do cursor, tente colar os caracteres da área de transferência (Ctrl-V). Ugh! Os caracteres não são inseridos!
Isso ocorre por que o modelo de dados contém 7 caracteres e estamos tentando inserir, de uma só vez, mais 4 caracteres. A soma dá 11, que é superior ao número máximo de caracteres que queremos aceitar. Um comportamento natural seria que o modelo aceitasse os primeiros 3 caracteres e descartasse o(s) restante(s).
Vamos tentar modificar a nossa classe FixedLengthDocument para que esse bug seja removido:
01 // ... (na classe FixedLengthDocument (FixedLengthDocument.java)
02
03 public void insertString(int offset, String str, AttributeSet attr)
04 throws BadLocationException {
05 if (s == null) return;
06
07 if (iMaxLength <= 0) // aceitara qualquer no. de caracteres
08 {
09 super.insertString(offset, str, attr);
10 return;
11 }
12
13 int ilen = (getLength() + str.length());
14 if (ilen <= iMaxLength) // se o comprimento final for menor...
15 super.insertString(offset, str, attr); // ...aceita str
16 else
17 {
18 if (getLength() == iMaxLength) return; // nada a fazer
19 String newStr = str.substring(0, (iMaxLength - getLength()));
20
21 super.insertString(offset, newStr, attr);
22 }
23 }
|
Agora sim! Repita o teste citado anteriormente e tudo correrá perfeitamente bem. O que fizemos para solucionar o problema de inserção em massa (bulk insert) foi acrescentar o bloco else como uma alternativa, caso ilen seja maior que iMaxLength. Se for maior, o bloco else será executado e então fizemos o seguinte: Se o comprimento atual do conteúdo do modelo for igual ao comprimento máximo, então simplesmente retornamos, pois não há mais espaço para inserir caracteres; caso contrário, criamos um novo objeto String que contém apenas os caracteres de str que deveriam caber no modelo de dados (no exemplo anterior, eram 4 caracteres e deveriam caber apenas 3); em seguida, realizamos uma chamada a insertString() da superclasse.
Conclusão
O assunto abordado neste artigo mostra como a interface Document e as classes AbstractDocument e PlainDocument (entre outras) podem ser extendidas para que objetivos especificos e mais especializados sejam atingidos de uma maneira bastante simples.
|
|
|