Mascaras e Validação em JTextField

Esse tópico surgiu depois de tentar entender o artigo Controlando um JTextField e outro, baseado nesse primeiro, chamado JTextField´s formatado em tempo execução.

Basicamente queria extender PlainDocument e utilizar JTextField para a entrada dos dados; e não utilizar MaskFormatter e JFormattedTextField.

Imagino chegar numa solução (será que uma única classe resolve?) para controlar textos e valores monetários - a princípio.

Vou postar aqui inicialmente duas classes:

PARA CONTROLAR TEXTOS (peguei do site javadesk e modifiquei ela):

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

public class FormataTexto extends PlainDocument {

    private int iMaxLength = 0;
    private char tipo;
    Pattern texto;
    
    public FormataTexto(int maxlen, char tp) {
        super();
        iMaxLength = maxlen;
        tipo = tp;
    }
    
    public String formatar (String entrada, Integer valor){
        // 1 - somente letras e números
        // 2 - somente numeros
        // 3 - somente letras
        
        if (valor == 1){
            texto = Pattern.compile("[0-9a-z]",Pattern.CASE_INSENSITIVE);  
        } else if (valor == 2){
            texto = Pattern.compile("[0-9]",Pattern.CASE_INSENSITIVE);
        } else if (valor == 3){
            texto = Pattern.compile("[a-z]",Pattern.CASE_INSENSITIVE);
        }

        Matcher encaixe = texto.matcher(entrada);  
        StringBuffer saida = new StringBuffer();  
        while(encaixe.find())  
            saida.append(encaixe.group());  
        return saida.toString();        
    }
    
    @Override
    public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException {
        if ( str == null ) return;

        boolean ok = true;

        if ( iMaxLength <= 0 ) {// aceitara qualquer no. de caracteres
            super.insertString( offset, str, attr );
            return;
        }

        // A = TUDO
        // B - SOMENTE LETRAS E NÚMEROS EM MAIÚSCULO
        // C - somente letras e números em minúsculo
        // D - SOMENTE NÚMEROS
        // E - SOMENTE LETRAS MAIÚSCULO
        // F - SOMENTE LETRAS minúsculo
        
        switch(tipo) {
            case 'B': str = formatar(str.toUpperCase(), 1); break;
            case 'C': str = formatar(str.toLowerCase(), 1); break;
            case 'D': str = formatar(str, 2); break;
            case 'E': str = formatar(str.toUpperCase(), 3); break;
            case 'F': str = formatar(str.toLowerCase(), 3); break;
        }

        int ilen = (getLength() + str.length());

        if ( ilen <= iMaxLength ) // se o comprimento final for menor...
            super.insertString( offset, str, attr ); // aceita str
        else {
            if ( getLength() == iMaxLength ) return; // nada a fazer
            String newStr = str.substring(0, (iMaxLength - getLength()));

            super.insertString( offset, newStr, attr );
        }
    }
}

uso ela dessa maneira:

JTextFiledNome = new JTextField(10); //tamanho do jtextfield JTextFiledNome .setDocument(new FormataTexto(20, 'E')); //permite no máximo 20 caracteres, somente letras maiusculas

PARA CONTROLAR VALORES MONETÁRIOS (peguei esse código aqui no GUJ, parece que foi de um post do Mark_Ameba:

[code]
import javax.swing.text.*;
public class MonetarioDocument extends PlainDocument {

public static final int NUMERO_DIGITOS_MAXIMO = 12;

public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {

String texto = getText(0, getLength());
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (!Character.isDigit©) {
return;
}
}

if(texto.length() < this.NUMERO_DIGITOS_MAXIMO){
super.remove(0, getLength());
texto = texto.replace(".", “”).replace(",", “”);
StringBuffer s = new StringBuffer(texto + str);

if (s.length() > 0 && s.charAt(0) == ‘0’) {
s.deleteCharAt(0);
}

if(s.length() < 3) {
if (s.length() < 1) {
s.insert(0,“000”);
}else if (s.length() < 2) {
s.insert(0,“00”);
}else{
s.insert(0,“0”);
}
}

s.insert(s.length()-2, “,”);

if(s.length() > 6) {
s.insert(s.length()-6, “.”);
}

if(s.length() > 10) {
s.insert(s.length()-10, “.”);
}

super.insertString(0, s.toString(), a);
}
}

public void remove(int offset, int length) throws BadLocationException {
super.remove(offset, length);
String texto = getText(0, getLength());
texto = texto.replace(",", “”);
texto = texto.replace(".", “”);
super.remove(0, getLength());
insertString(0, texto, null);
}

} [/code]

da mesma forma, uso ela assim:

JTextFieldDinheiro = new JTextField(8); JTextFieldDinheiro .setDocument(new MonetarioDocument());

Perguntas:
1 - É possível otimizar e unir esses códigos (principalmente a primeira classe), deixando somente numa classe melhorada? alguma dica?
2 - É possível, nesse raciocínio, fazer com que a tecla ENTER se comporte como TAB? (tenho que ver o método Character.isISOControl());
3 - É possível, quando o JTextField receber o foco, selecionar todo o seu conteúdo?

(já fiz a pergunta 2 e 3, separadamente, penso em chegar num modelo onde não precise fazer isso componente por componente)

espero ajuda!

Movi o seu tópico para a área de interface gráfica. Vamos ver se dá mais certo aqui.

1 - Deve ser possível sim. Mas talvez fosse melhor entende-los e repensar uma nova lógica. Se eu fosse fazer, criaria atributos separados para o valor, o que é exibido e para a máscara.

2 - O comportamento do TAB é uma característica da View, não do Model. Por isso, o tratamento não seria feito aqui. Há diversos tópicos no GUJ sobre enter se comportando como tab.

3 - Sim, também é possível, e também é um aspecto da view, não do model. Provavelmente envolverá tratar o evento onFocus() e usar o SelectionModel para marcar todo o conteúdo.

Uma dica para seu próximo tópico. Código demais assusta quem vai responder. Procure colocar suas dúvidas no início e anexar só trechos realmente importantes de código. Se for anexar classes inteiras, procure usar a opção de attachments. Dificilmente alguém vai ler tanto código assim.

Olá ViniGodoy, obrigado pela resposta, direcionamento e pela orientação no tópico.

Como iniciante, tenho buscado entender alguns conceitos que ainda não estão bem claros pra mim.

1 - Compreendo sua idéia de repensar uma nova lógica (e acho necessário nesse caso), porém ainda não consigo visualizar isso num código. Você teria um link, um exemplo simples, pra mim dar uma olhada?

2 - OK, entendido. Pensei que poderia fazer alguma coisa automática, tipo

Já faço o enter se comportar como tab, porém vou de componente a componente fazendo isso (é um pouco trabalhoso).

3 - Entendido. No mesmo sentido da idéia anterior, agora já sei que não posso fazer esse tratamento por lá.

1 - Seria muito difícil achar algo assim em um código só, quanto mais de maneira simples. Acho que esse é exatamente o grande trabalho que você está propondo. Por outro lado, é o que torna a sua idéia interessante, se você conseguir implementa-la.

2 -
Na verdade, basta na inicialização do seu JFrame fazer:

// Colocando enter para pular de campo HashSet conj = new HashSet(this.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)); conj.add(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_ENTER, 0)); this.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, conj);

Mais informações: http://www.guj.com.br/posts/list/47289.java#258583

Olá ViniGodoy,

1 - Vou tentar implementar essa classe, extendendo PlainDocument para controlar JTextField. Se tiver algum resultado produtivo coloco aqui no fórum.

2 - Parece que já fiz alguns testes com esse código que você me passou. Se não me engano ele faz o enter se comportar como tab em qualquer componente do JFrame, inclusive em botões.
Por esse motivo desisti dele, pois queria um controle (enter por tab) somente em JTextFields. Fica estranho (no mínimo) pressionar enter num botão e ‘saltar’ para o próximo componente.

Vou experimentar esse código que achei na net:

for(int i = 0; i < c.getComponentCount(); i++){ Component comp = c.getComponent(i); if(comp.getClass().getName() == "javax.swing.JTextField"){ ((JTextField)(comp)).addKeyListener( new KeyAdapter(){ public void keyPressed(KeyEvent evt){ int key = evt.getKeyCode(); if(key == KeyEvent.VK_ENTER) ((JTextField)(evt.getSource())).transferFocus(); } } ); }

Vou ver o tópico que me passou como referência.
Obrigado pela ajuda.

Não use para isso o KeyPressed. Se necessário, é possível definir esse hashset para cada JTextField.

O problema dos eventos é que eles já estão sendo processados no momento em que ocorrem. O keystroke é processado antes do primeiro evento ser gerado. Isso gera um comportamento mais consistente.

ViniGodoy,

Quanto ao hashset para cada componente, implementei e realmente deu certo, obrigado.

Agora vou me concentrar no objeto principal desse tópico, que é (tentar) extender PlainDocument para controlar os JTextFields.

Por que quando um jtextfield vinculado a uma jtable esses codigos de validaçao nao funciona??
ja testei um monte e nao da de jeito algum… alguem sabe me falar pq??
nao entedo pq em um jtextfield sem vinculo funciona normalmente.