Home   |   Archivio   |   Chi siamo   |   Contatti
Luca Vetti Tagliati
Swing modello MVC
Questo sconosciuto
di
Luca Vetti Tagliati
Come realizzare un oggetto dedicato all'acquisizione dei campi data. Metodo setMaxLength() della classe JTextField: cronaca di una funzionalità inspiegabilmente mancante.

L'obiettivo del presente articolo è illustrare le tecniche che permettono di risolvere il problema del controllo interattivo dell'input utente estendendo opportunamente le funzionalità della classe JTextField (API Swing). Si tratta di un problema "atavico", ma tuttora ricorrente, presente pressoché in ogni applicativo provvisto di un'interfaccia utente non di puro servizio.

Introduzione

La questione del controllo interattivo potrebbe essere considerata piuttosto "decrepita"; lo stesso autore ci si è imbattuto per la prima volta oltre un decennio orsono, realizzando un apposito modulo in linguaggio C (i tempi d'oro del Turbo C 2.0 della Borland). Da allora molte cose sono cambiate, ma il problema sembrerebbe essere di attualità, sebbene si ripresenti in forme diverse: chissà che ne direbbe Gianbattista Vico!

È possibile misurare l'incidenza del problema mediante l'analisi dell'ingente quantitativo di quesiti a riguardo presenti nel sito www.javasoft.com.

Il modo migliore per risolvere il caso, e qui il concetto della riusabilità trova la sua esaltazione, è quello di progettare una classe (meglio un bean), più generale possibile che, opportunamente parametrizzata, sia in grado di controllare e gestire tutte le diverse casistiche di campi dati (solo testo, numerici, monetari, data, ...).

Il dominio del metodo di controllo dell'input è quindi costituito dall'insieme delle stringhe di caratteri (inserite per mezzo della funzione di "Paste") aventi come caso più frequente, il sottoinsieme delle stringhe di lunghezza unitaria (analisi del singolo carattere premuto). In sostanza si tratta di realizzare un'estensione della classe JTextField che svolga il compito di "filtro-interattivo". La classe così ottenuta, una volta ricevuta l'attenzione ("focus") dovrebbe occuparsi di monitorare tutto ciò che le viene fornito dalla tastiera (compresi i risultati dei comandi "Copy & Paste") al fine di accettare i dati validi e di scartare quelli non previsti dal tipo di campo. Il criterio di selezione (quali caratteri considerare validi) dovrebbe essere fornito per mezzo di un'apposita stringa di "mascheramento".

Per esempio, se si volesse demandare alla classe il compito di acquisire i valori di un campo monetario, la si dovrebbe parametrizzare al fine di farle accettare i soli caratteri numerici, eventualmente corredati da opportuni separatori.

Per poter affrontare il problema è necessario però possedere una certa dimestichezza con il famossissimo modello "M. V. C." (ampiamente illustrato nei numeri precedenti di MokaByte), o meglio, della versione denominata "model-delegate", su cui si basa l'architettura Swing.

Da notare che, volenti o nolenti, è necessario affrontare il problema, dal momento che la classe JTextField, di per sé, non dispone di alcun metodo per limitare il numero di caratteri impostabili dall'utente.

Poiché l'esercizio, come si vedrà, non è banalissimo, ci si chiede come mai non sia stato realizzato un opportuno metodo (setMaxLength()) direttamente dai progettisti delle Swing. Per il lettore meno attento si vuole sottolineare che si tratta di un'interrogazione retorica.

Il problema potrebbe sembrerebbe abbastanza banale, il timore è tuttora grande, se non fosse per il fatto che anche il relativo esempio fornito nella sezione "Tips & Tricks" del sito Sun presenti un'imprecisione abbastanza rilevante: si è "semplicemente" trascurata l'architettura MVC (o meglio la relativa versione Swing)! Ci si rende conto che l'articolo voleva essere solo un esempio, ma, proprio per il suo carattere didattico e per la sua funzione di riferimento (faro per milioni di programmatori), forse sarebbe stato più opportuno porvi maggiore attenzione. L'articolo "incriminato", attualmente, è stato rimpiazzato, ne sono rimaste tracce nell'archivio utilizzato per le ricerche (provare ad eseguire un reperimento specificando il titolo "Constructing a Masked JTextField") e, casualmente (touché), l'autore del presente articolo (come si vedrà) ne conserva una copia in formato digitale.

AWT e SWING: Architetture differenti

Lungi dal voler proporre un'ennesima (inflazionatissima) dissertazione sulla variante al modello MVC proposta nelle API Swing, per la quale esistono articoli a iosa (consultare numeri precedenti di MokaByte), in questo paragrafo, si vuole semplicemente illustrare come sia possibile realizzare un bean per l'acquisizione di un campo data, senza violare l'architettura MVC.

Si tenga presente che l'esempio fornito è ben più complesso del caso generale: non basta eseguire un controllo sintattico (quali caratteri accettare e quali rifiutare), bensì è necessario eseguire verifiche semantiche (per esempio controllo dell'anno bisestile, verifica di date inesistenti, ecc.).

Da notare che poiché l'architettura interna delle Swing, con grande giovamento dei programmatori Java, è stata completamente ristudiata, rispetto a quella malconcia e più volte rimaneggiata proposta dall'AWT, l'approccio utilizzato in precedenza per estendere i "controlli" grafici va completamente ripensato.

Se si disponeva di qualche classe già realizzata per l'ambiente AWT, si tenga presente che è tutto da rifare. Non si tratta semplicemente di recuperare (leggere "Copy&Paste") alcune parti e scriverne altre, è necessaria una vera e propria riprogettazione: in questo caso, tutto ciò che è stato realizzato per il modello AWT è destinato al Garbage Collector (famoso concetto della riusabilità).

In base alla precedente architettura (AWT) era sufficiente definire una nuova classe, denominata (con grande impiego di risorse celebrali) "MaskedField", ereditante la "TextField"

public class MaskedField extends TextField

ed andare a sovrascrivere ("override") il metodo handleEvent, al fine di gestire, uno per uno, i tasti premuti dall'utente.

Tali tasti, in funzione di un'opportuna stringa di mascheramento, potevano essere ignorati (valore di ritorno "false") o accettati (valore di ritorno "true").

public boolean handleEvent(Event oEvnt) {

Tipicamente all'interno di tale metodo si inseriva un apposito costrutto "switch" al fine di poter gestire gli eventi della tastiera desiderati, ed il gioco era pressoché fatto.

switch (oEvnt.id) {
  case Event.KEY_PRESS:
    ...
    break;
}

Con l'introduzione delle API Swing tale approccio non è più utilizzabile, sia perché non è possibile evitare che un tasto venga rifiutato, sia (ancor più importante) perché si contravverrebbe (almeno in linea di principio) l'architettura MVC.

Se si accede alle "FAQ Swing", si legge che il consiglio (o meglio l'ordine) fornito è quello di agire (override) sui metodi remove() e insertString() appartenenti alla classe PlainDocument da associare alla classe che estende la JTextField() (come avviene del resto per ogni altro componente dell'"API Swing").

protected class NewDocument extends PlainDocument {
  public void remove(int iOffs, int iLen) throws BadLocationException {
    ...
  }

  public void insertString(int iOffs, String sTxt, AttributeSet oAttSet)
      throws BadLocationException {
    ...
  }
}

Per associare una classe estensione di una PlainDocument, è necessario inserire, all'interno della classe che specializza la JTextField, il seguente frammento di codice:

protected Document createDefaultModel() {
  return new NewDocument();
}

Da qui si dovrebbe intuire, tra l'altro, perché si parla di modello per delegazione.

Fin qui sembrerebbe tutto logico e lineare. Poiché si vuole specializzare il comportamento di un "oggetto" grafico e non il suo "look" è necessario estendere opportunamente la classe PlainDocument. La nuova tecnica risulta per molti versi semplificare il lavoro. In primo luogo, per capire se l'utente intenda cancellare o inserire una sotto-stringa, non è necessario andare a "leggere" tutti i caratteri premuti da tastiera ed intercettare la pressione del tasto "CANC" o "BACK SPACE": è sufficiente estendere il relativo metodo; il quale, attraverso i parametri, fornisce gli indici del testo da eliminare.

Il lavoro tedioso spicca quando, invece, si deve gestire l'inserimento di un'intera stringa precedentemente memorizzata ("Copy&Paste"). La questione è così delicata che taluni programmatori, spaventati dal problema, ahimè, si riducono ad inibirne la funzionalità. Purtroppo oggigiorno non è infrequente il caso di molti programmatori che, trovandosi di fronte ad un algoritmo da realizzare, la cui complessità supera un "epsilon" piccolo a piacere, invece di affrontare il problema, decidano semplicemente di evitarlo. VB docet!

Prima di proseguire, si analizzi l'esempio fornito nell'articolo "pubblicato" nel sito "JavaSoft".

L'obiettivo era realizzare un componente, denominato IPField, dedicato all'acquisizione di indirizzi IP di quattro byte; quartetto di sotto-stringhe numeriche di tre caratteri, separate dal carattere punto, secondo il formato visualizzato in figura 1.

Figura 1 - "Look" del controllo "IP field"
Figura 1 - "Look" del controllo "IP field"

La soluzione proposta è rappresentata, fedelmente, dal codice riportato di seguito.

import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;

La classe seguente serve allo scopo di realizzare un "frame" in cui visualizzare (e provare) l'"IPfield".

public class IPAddressDemo extends JFrame {
  public IPAddressDemo() {
    super("IP Address text field demo");

    JPanel panel = new JPanel();
    getContentPane().add(panel);

    IPField ipf = new IPField();
    ipf.setFont(new Font("Monospaced", Font.BOLD, 14));
    panel.add(ipf);

    setSize(250, 70);
    setVisible(true);
  }

  public static void main(String[] args) {
    new IPAddressDemo();
  }
}

Classe estendente la JTextField.

class IPField extends JTextField {
  public IPField() {
    super();
    setText("   .   .   .   ");
    KeyAdapter ka = new KeyAdapter() {
      public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
          int pos = getCaretPosition();
          if (pos > 0) {
            char[] text = getText().toCharArray();
            text[pos - 1] = ' ';
            text[3] = '.';
            text[7] = '.';
            text[11] = '.';
            switch (pos) {
              case 2:  text[1] = text[2];
                       text[2] = ' '; break;
              case 6:  text[5] = text[6];
                       text[6] = ' '; break;
              case 10: text[9] = text[10];
                       text[10] = ' '; break;
              default: break;
            }
            setText(new String(text));
            setCaretPosition(pos - 1);
            e.consume();
          }
        } else if (e.getKeyCode() == KeyEvent.VK_DELETE) {
          int pos = getCaretPosition();
          if (pos < 16) {
            char[] text = getText().toCharArray();
            text[pos] = ' ';
            text[3] = '.';
            text[7] = '.';
            text[11] = '.';
            switch (pos) {
              case 0: text[0] = text[1];
              case 1: text[1] = text[2];
              case 2: text[2] = ' '; break;
              case 3: break;
              case 4: text[4] = text[5];
              case 5: text[5] = text[6];
              case 6: text[6] = ' '; break;
              case 7: break;
              case 8: text[8] = text[9];
              case 9: text[9] = text[10];
              case 10: text[10] = ' '; break;
              case 11: break;
              case 12: text[12] = text[13];
              case 13: text[13] = text[14];
              case 14: text[14] = ' '; break;
              case 15: break;
              default: break;
            }
            setText(new String(text));
            setCaretPosition(pos);
            e.consume();
          }
        }
      }
    };
    addKeyListener(ka);
  }
}

L'autore usa questo esempio per mostrare il limite della soluzione JavaSoft: il controllo è realizzato agendo direttamente sul componente visivo e sugli eventi di tastiera, mentre in Swing il punto corretto di estensione è il modello documentale. Da qui nasce la necessità di spostare la logica nel Document associato al campo.

Verso un campo data corretto

L'idea è realizzare un componente dedicato all'acquisizione di date che non si limiti al filtro sintattico, ma che applichi controlli semantici coerenti con il dominio del problema: lunghezza massima, separatori, giorni validi per ciascun mese, anni bisestili e così via.

In questa prospettiva la classe derivata da JTextField deve limitarsi a delegare il comportamento al documento sottostante, mentre la logica di validazione e di gestione dell'inserimento deve essere incapsulata nel PlainDocument specializzato. In questo modo il componente resta coerente con il modello MVC di Swing e può essere riutilizzato senza dipendere da un particolare look and feel.

Si tenga presente che l'esempio fornito è ben più complesso del caso generale: non basta eseguire un controllo sintattico (quali caratteri accettare e quali rifiutare), bensì è necessario eseguire verifiche semantiche (per esempio controllo dell'anno bisestile, verifica di date inesistenti, ecc.).

Il componente proposto non solo esegue un controllo sintattico dell'input (evita che si inseriscano caratteri in posizioni errate), ma esegue anche controlli semantici: evita che vengano impostate date inesistenti come 31/11/1999, 29/02/2100 (ebbene il 2100 non è un anno bisestile) ecc.

La classe, così come progettata, prevede la visualizzazione di un carattere sottolineato al posto dei spazi vuoti e, come divisore di campi, uno slash; nulla vieta di variarli o di realizzare opportuni metodi (setSepChar() e setEmptyChar()) per consentire all'utente di selezionarli a proprio piacimento.

L'aspetto del widgets (catturato nell'atto fatidico dell'impostazione della data della catastrofe mondiale) è illustrato in figura 2.

Figura 2 - Look del controllo per l'acquisizione dei campi data
Figura 2 - Look del controllo per l'acquisizione dei campi data

È ormai chiaro che la parte di codice di interesse è quella relativa alla classe che estende la PlainDocument, che, visto l'argomento, è stata "battezzata" DateDocument.

protected class DateDocument extends PlainDocument {

  /** Array con i giorni di cui è composto ciascun mese*/
  private final int aiDaysOfMonths[] =
    {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

Il metodo remove (o meglio la relativa sovrascrittura) è abbastanza semplice, le uniche cose da tenere in considerazione sono:

1. viene invocato la prima volta all'atto dell'istanziazione della classe; senza che sussista una reale necessità di eliminazione caratteri (lo stesso dicasi per il metodo insertString());

2. è necessario ricalcolare la posizione futura del cursore, dopo l'eliminazione, ogniqualvolta si verifichi che l'utente abbia eliminato un solo carattere e la posizione di default risulti coincidente a quello di uno slash: è inutile richiedere all'utente di inserire un carattere già presente! (effetto salto).

public void remove(int iOffs, int iLen) throws BadLocationException {
  if (iLen > 0) {  // È un'operazione di eliminazione reale?
    String sInsertedDate = super.getText(0,10); //legge la data visualizzata
    // determina la stringa corredata dalla maschera
    String sNewDate = replaceStr(sInsertedDate, iOffs, getSignChar(iOffs,iLen));
    super.remove(0,10); //..elimina tutto il testo visualizzato
    super.insertString(0,sNewDate,null); //..Inserisce quello risultante

    // Determina la nuova posizione
    if (iLen == 1) {     // Si è eliminato un singolo carattere?

      if ((iOffs == 3) || (iOffs == 6))  // Ci si trova su uno slash?
        iOffs--;  //..arretra la posizione del cursore
    }

    setCaretPosition(iOffs);   // posizione il cursore
  }
}

Si noti che si sono invocati metodi remove() e InsertString della classe padre al fine di evitare chiamate ricorsive che, tra l'altro, genererebbero un "dead lock".

Il metodo insertString() è decisamente più complesso di quello precedente, e svolge i seguenti di compiti:

1. Filtra la stringa che si vuole inserire (elimina i caratteri non numerici);

2. Esegue il controllo semantico dei caratteri numerici. Qualora individui un dato errato (per esempio 31/11), lo rimpiazza con un carattere sottolineato (3_/11) e quindi vi si posiziona con il cursore;

3. Calcola la nuova posizione del cursore.

public void insertString(int iOffs, String sTxt, AttributeSet oAttSet)
    throws BadLocationException {

  // Si tratta di un reale nuovo inserimento?
  if ((sTxt != null) && (sTxt.length() > 0)) {

    String sTxtInserted = this.getText(0,10);  // preleva la stringa video

    //--------------------------------------------------
    //Verifica se c'è spazio per l'inserimento di un altro carattere

    if (iOffs > iDEF_LENG_INT+1) {  //Si sta cercando di andare oltre la fine?

      int iFirstEmpty = sTxtInserted.indexOf(getMaskChar());

      if (iFirstEmpty > -1) //È presente almeno uno spazio vuoto?
        setCaretPosition(iFirstEmpty); //Si, posiziona il cursore sul primo.

      beep();

      return;
    }

    // -----------------------------------------------------------------
    // Elimina gli eventuali caratteri errati presenti nella stringa

    int iIntStrLen = sTxt.length();  // lunghezza della stringa da inserire

    sTxt = getOnlyDigit(sTxt);      // Elimina eventuali caratteri non validi

    if (iIntStrLen != sTxt.length()) { // Eliminati caratteri non validi?
      beep();
    }

    // --------------------------------------------------------------
    // Verifica se l'utente stia tentando di inserire troppi caratteri

    iIntStrLen = getOnlyDigit(sTxtInserted).length();

    iIntStrLen += sTxt.length(); //Lunghezza della data inclusa la nuova stringa

    // troppi caratteri?
    if ((iOffs - getSlashNumber(iOffs))+sTxt.length() > iDEF_LENG_INT) {
      beep();
      // Elimina quelli in eccedenza
      sTxt = sTxt.substring(0, iDEF_LENG_INT + getSlashNumber(iOffs) - iOffs);
    }

    // --------------------------------------------------------------------
    // Aggiunge la stringa da inserire

    if (sTxt.length() > 0) {

      //Ciò che si vuole inserire "sovrascrive" i caratteri slash?
      if ( (iOffs < 2) && (sTxt.length()+iOffs > 2)) {

        sTxt = insertSubstr(sTxt, (2-iOffs), getSeparChar()+"" ); }

      if(iOffs == 2) //Il nuovo inserimento è sul primo slash?
        iOffs++; //..si => si sposta in avanti

      if ((iOffs < 5) && (sTxt.length()+iOffs > 5)) { //comprende il 2 slash?
        sTxt = insertSubstr(sTxt, (5-iOffs), getSeparChar()+"");  //Riformatta la stringa con i caratteri di controllo
      }

      if(iOffs == 5) //Si vuole inserire sopra il secondo slash?
        iOffs++;   //si => lo salta.
    }

    // --------------------------------------------------------
    // Esegue i controlli semantici sulla data

    // prepara la nuova stringa da inserire
    sTxtInserted = replaceStr(sTxtInserted, iOffs, sTxt);  //aggiunge il testo

    sTxtInserted = getValidCharacters(sTxtInserted); // Verifica se la data è valida

    // --------------------------------------------------------
    // Mostra la nuova stringa

    super.remove(0,10); //Elimina la stringa visualizzata
    super.insertString(0,sTxtInserted,oAttSet);//.Inserisce quella nuova

    // ----------------------------------------------------------
    // Calcola la nuova posizione del cursore

    setCaretPosition(getNextAvailablePos(iOffs,sTxtInserted,sTxt.length()>0));

  } else {                                         //Operazione fittizia?

    super.insertString(0,getSignChar(0,10),oAttSet);//Visualizza la formattazione
  }
}

/** Determina la posizione del primo "carattere" vuoto ("_") nella stringa
  *
  * @param iCurrPos Posizione attuale del carset
  * @param sDate    Data visualizzata
  * @param boInsStr Indica se si sta eseguendo il metodo di inserimento o meno
  * @return iNewPos Posizione del primo spazio disponibile per l'inserimento
  */

private int getNextAvailablePos(int iCurrPos, String sDate, boolean boInsStr) {

  int iNewPos = sDate.indexOf(getMaskChar());

  if (iNewPos == -1) {   // Nessun spazio vutoto?

    if (boInsStr) {      // Si sta eseguendo un inserimento?

      iNewPos = 10;      // Posiziona il cursore oltre la fine della stringa

    } else {

      iNewPos = iCurrPos;

      if (iNewPos < 10) {// Non si è ancora raggiunta la fine della stringa?
        iNewPos++;       // … Si posiziona al prossimo carattere

        if ( (iNewPos == 2) || (iNewPos == 5))  // Ci si trova su uno slash?
          iNewPos++;                             // … Avanza di una posizione
      }
    }
  }

  return iNewPos;
}

/** Verifica che il carattere fornito sia compreso nei limiti specificati
  *
  * @param cToVer   Carattere da verificare
  * @param cValMin  Limite inferiore
  * @param cValMax  Limite superiore
  * @return True se il carattere fornito appartiene all'intervallo specificato
  */

private boolean verCharValidity(char cToVer, char cValMin, char cValMax) {

  boolean boRet = true;

  if (cToVer != getMaskChar()) { //Non si tratta di un carattere di formattazione?

    if ((cToVer < cValMin) || (cToVer > cValMax)) {
      boRet = false;
    }

  }

  return boRet;
}

/** Elimina i caratteri non validi presenti nella stringa fornita
  *
  * @param sText Stringa da verificare
  * @return      Stringa con i soli caratteri validi
  */

private String getValidCharacters(String sText) {

  String sValidate = "";
  boolean boErr = false;


  // -------------------------------------------- Carattere in pos. 0
  if (!verCharValidity(sText.charAt(0), '1','3')) {
    boErr = true;
    sValidate += getMaskChar();
  } else {
    sValidate += sText.charAt(0);
  }

  // -------------------------------------------- Carattere in pos. 1
  if (sText.charAt(0) == '3'){

    if(!verCharValidity(sText.charAt(1), '0','1')){
      boErr = true;
      sValidate += getMaskChar();
    } else {
      sValidate += sText.charAt(1);
    }

  } else {
    sValidate +=sText.charAt(1);
  }

  // ------------------------------------------- Carattere in pos. 2 ==> slash
  sValidate += sText.charAt(2);

  // ------------------------------------------- Carattere in pos. 3
  if (!verCharValidity(sText.charAt(3), '0','1')) {
    boErr = true;
    sValidate += getMaskChar();
  } else {
    sValidate += sText.charAt(3);
  }

  // ------------------------------------------- Carattere in pos. 4
  if (sText.charAt(3) == '1'){
    if(!verCharValidity(sText.charAt(4), '0','2')){
    boErr = true;
    sValidate += getMaskChar();
    } else {
      sValidate += sText.charAt(4);
    }
  } else {
    sValidate += sText.charAt(4);
  }

  // ------------------------------------------- Carattere in pos. 5 ==> slash
  sValidate += sText.charAt(5);

  // ------------------------------------------- Carattere in pos. 6
  sValidate += sText.charAt(6);

  // ------------------------------------------- Carattere in pos. 7
  sValidate += sText.charAt(7);

  // ------------------------------------------- Carattere in pos. 8
  sValidate += sText.charAt(8);

  // ------------------------------------------- Carattere in pos. 9
  sValidate += sText.charAt(9);

  // ---------------------------------------------------
  // Esegue verifica semantica

  String sDays = sValidate.substring(0,2);   //giorno
  String sMonth = sValidate.substring(3,5);  //mese
  String sYear = sValidate.substring(6,10);  //anno

  if ((getOnlyDigit(sDays).length() == 2) &&
      (getOnlyDigit(sMonth).length() == 2)) {

    int iMonthId = (Integer.parseInt(sMonth))-1;

    // E' lecito utilizzare il seguente controllo dal momento che
    // si è già verificata la validità dei singoli caratteri
    // Si è preferito affidare il controllo al JDK API

    // Si sta considerando il giorno 29 del mese di Febbraio?
    if ((iMonthId == 1) && (Integer.parseInt(sDays)) == 29) {

      // E' presente il campo anno?
      if ((!boErr) && (getOnlyDigit(sYear).length() == 4)) {

        int iYear = Integer.parseInt(sYear);

        GregorianCalendar oCal = new GregorianCalendar();

        if (!oCal.isLeapYear(iYear)) {
          boErr = true;
          sValidate = replaceStr(sValidate,1,getMaskChar()+"");
        }
      }
    }
  }

  if (boErr) {  // Riscontrato un errore?
    beep();
  }

  return sValidate;
}

/** Sostituisce una sottostringa in un dato oggetto stringa
  *
  * @param sSource Stringa da aggiornare
  * @param iStrPos Indice in cui iniziare la sostituzione
  * @param sToRep  Sottostringa da sostituire
  * @return        Stringa risultante
  */

private String replaceStr(String sSource, int iSrtPos, String sToRep) {

  String sBefore = sSource.substring(0,iSrtPos);// Sottostringa che precede il punto di sostituzione
  String sAfter  = sSource.substring(iSrtPos+sToRep.length());//.Sottostringa che segue il punto di sostituzione

  String sRet = sBefore+sToRep+sAfter;// Nuova stringa

  return sRet;
}

/** Inserisce una sottostringa in un dato oggetto stringa
  *
  * @param sSource Stringa da aggiornare
  * @param iStrPos Indice in cui iniziare l'inserimento
  * @param sToRep  Sottostringa da inserire
  * @return        Stringa risultante
  */

private String insertSubstr(String sSource, int iSrtPos, String sToIns) {

  String sBefore = sSource.substring(0,iSrtPos); // Sottostringa che precede il punto di inserimento
  String sAfter  = sSource.substring(iSrtPos);   //. Sottostringa che segue il punto di inserimento
  String sRet = sBefore+sToIns+sAfter; // nuova stringa

  return sRet;
}

/** It returns a string of significant characters
  * in the right positions
  *
  * @param iOffs is the starting point in the date string of the substring to be elaborate
  * @param iLen is the length of this substring
  * @return an sValidate only digit string
  */

private String getSignChar(int iOffs,int iLen){
  String sSignChar = "";

  for (int iIncr = 0; iIncr< iLen; iIncr ++){ //let's work on this string

  if (iOffs == 2 || iOffs == 5)   //are we on the separator position
    sSignChar += getSeparChar();  //let's put it
  else
    sSignChar +=  getMaskChar();  //and here let's put mask char for our date field!
    iOffs++;
  }

  return sSignChar; //the right separator and masked
                    //chars for the substrig position
}

/** Preleva dalla stringa data i soli campi numerici (elimina i caratteri di formattazione)
  *
  * @param sText   Stringa contenente il campo data
  * @return        Data filtrata dei caratteri di formattazione
  */

private String getOnlyDigit(String sText){

  int iInd = 0;  // Contatore di ciclo
  char cCurr;    // Carattere "oggetto" del singolo ciclo
  String sValidate = "";

  while (iInd < sText.length()) {
    cCurr = sText.charAt(iInd);  //preleva il carattere in posizione "iInd"
    if (Character.isDigit(cCurr))
      sValidate += cCurr ;
      iInd++; // Prossimo carattere

    }
  return sValidate;
}

/** Determina il numero di caratteri slash che precedono la posizione specificata
  *
  * @param iOffs Posizione da considerare
  * @return      Numero di slash che precedono la posizione in questione
  */

private int getSlashNumber (int iOffs){
  int iWithSlash = 0;

  if (iOffs > 2) //Passata la seconda posizione?
    iWithSlash++; //..Si consideri il primo slash.
  if (iOffs >5)  //Passata la quinta posizione?
    iWithSlash++; //.. Si consideri anche il secondo slash.
  return  iWithSlash;
}
}
}

Conclusioni

Il presente articolo è scaturito da un'esigenza pratica: realizzare un "widgets" dedicato al controllo dei dati impostati dall'utente. L'idea è scaturita a seguito delle numerose e continue richieste di delucidazione riguardanti l'argomento.

Come si è potuto osservare, la progettazione delle API Swing ha determinato, come logica conseguenza, la necessità di ristudiare i controlli grafici basati sulle API AWT, al fine di adeguarli alla nuova struttura interna: non si tratta di un "cut&Copy", bensì di una reingegnerizzazione vera e propria.

Sebbene l'esempio fornito, controllo di un campo data, risolva un sottocaso specifico, date in formato <gg>/<mm>/<aaaa>, si è convinti che si tratta di una buona base di partenza per la realizzazione di una classe più flessibile, in grado di acquisire date nei formati più vari.

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it