## Cómo usar Text Fields

Un campo de texto es un control básico que permite al usuario teclear una pequeña cantidad de texto y dispara un evento action cuando el usuario indique que la entrada de texto se ha completado (normalmente pulsando Return). Generalmente se usa la clase JTextField para proporcionar campos de texto. Si necesitamos proporcionar un password field -- un campo de texto editable que no muestra los caracteres tecleados por el usuario -- utilizaremos la clase JPasswordField. Esta sección explica estos dos campos de texto.

Si queremos un campo de texto que también proporcione un menú de cadenas desde la que elegir una, podemos considerar la utilización de un combo box editable. Si necesitamos obtener más de una línea de texto desde el usuario deberíamos utilizar una de las glases que implementan text area para propósito general.

El Applet siguiente muestra un campo de texto básico y un área de texto. El campo de texto es editable y el áera de texto no lo és. Cuando el usuario pulse Return en el campo de texto, el campo dispara un action event. El applet reacciona al evento copiando el contenido del campo de texto en el área de texto y seleccionando todo el texto del campo de texto.

> Esta es una imagen del GUI del applet. Para ejecutar el applet, pulsa sobre la imagen. El Applet aparecerá en una nueva ventana del navegador.

Puedes encontrar el programa fuente en TextDemo.java. Aquí está el código de TextDemo que crea el campo de texto del applet:

    textField = new JTextField(20);
    textField.addActionListener(this);
    ...
    contentPane.add(textField);

El argumento entero pasado al constructor de JTextField, 20 en el ejemplo, indica el número de columnas del campo, que es usada junto con la métrica proporcionada por el font actual del campo, para calcular la anchura preferida por el campo. Como varios controladores de disposición ingnoran los tamaños preferidos y como los márgenes, los bordes y otros factores afectan al tamaño del componente, toma este número como una aproximación, no un número absoluto.

Las siguientes líneas de código registran el applet como oyente de action para el campo de texto y añade el campo de texto al panel de contenidos del applet. Aquí está el método actionPerformed que meneja los eventos action del campo de texto:

    public void actionPerformed(ActionEvent evt) {
        String text = textField.getText();
        textArea.append(text + newline);
        textField.selectAll();
    }

Observa el uso del método getText de jTextField para recuperar el contenido actual del campo de texto. El texto devuelto por este método no incluye un caracter de nueva línea para la tecla Return que disparó el evento action.

Este ejemplo ilustra usando un campo de texto básico para introducir datos textuales y realizar algunas tareas cuando el campo de teto diapara un evento action. Otros programas, sin embargo, necesitan un comportamiento más avanzado. Una subclase de JTextComponent, JTextField puede ser configurada y personalizada. Un personalización común es proporcionar un campo de texto cuyos contenidos sean validados. Esta sección cubre los siguientes tópicos de los campos de texto avanzados. Para entender toda la información, necesitas haber comprendido el material presentado en Reglas Generales para el uso de Componentes.

- Crear un Text Field Validado
- Usar un oyente de Document sobre un Text Field
- Distribuir parejas de Label/Text Field
- Proporcionar un campo de Password
- El API de Text Field
- Ejemplos que usan Text Fields

#### Crear un Text Field Validado

> Muchos programas requieren que el usuario introduzca un dato textual de un cierto tipo o formato. Por ejemplo, un programa podría proporcionar un campo de texto para entrar una fecha, un número decimal, o un número de teléfono. Los contenidos de dichos campos como campos de texto deben ser validados antes de ser utilizados para cualquier propósito. Un campo de texto puede ser validado cuando se dispare el evento action o el evento keystroke.

> El dato en un campo validado-en-action se chequea cada vez que el campo dispara un evento action (cada vez que el usuario pulsa la tecla Return). Un campo validado-en-action podría, en un momento dado, contener datos no válidos. Sin embargo, el dato será validado antes de ser utilizado. Para crear un campo validado-en-action, necesitamos proporcionar un oyente action para nuestro campo e implementa su método actionPerformed de la siguiente forma:

> - Usa getText para obtener el contenido del campo de texto.
> - Evalua el valor devuelto por getText.
> - Si el valor es válido, realiza cualquier tarea de cálculo que sea requerida. Si el campo es nulo, reporta el error y retorna sin realizar ninguna tarea de cálculo.
> El dato en un campo validado-en-pulsación se chequea cada vez que el campo cambia. Un campo validado-en-pulsación nunca puede contener datos no válidos porque cada cambio (pulsación, cortar, copiar, etc.) hace que el dato no válido sea rechazado. Para crear un campo de texto validado-en-pulsación necesitamos proporcionar un documento personalizado para nuestro campo de texto. Si no estás familiarizado con los documentos, puedes ir a Trabajar con el Documento de un Componente de Texto.

> > Aviso: No use un oyente de document para validación-por-pulsación. El momento en que un oyente de documento es notificado de un cambio, es demasiado tarde, el cambio ya ha tenido lugar. Puedes los dos últimos párafos de Escuchar los Cambios en un Documento para más información

> La aplicación mostrada en la siguiente figura tiene tres campos validados-por-pulsación. El usuario introduce información en los tres primeros campos de texto. Cada vez que el usuairo teclea un caracter, el programa valida la entrada y actualiza el resultado del cuarto campo de texto.

> > Prueba esto:
> > 1. Compila y ejecuta la aplicación. El fichero fuente es TextFieldDemo.java. También necesitarás WholeNumberField.java, DecimalField.java, y FormattedDocument.java.
> > 2. Introduce la información en los campos de texto y mira los resultados.
> >    Si intentas introducir un campo no válido, el programa pitará.
> > 3. Intenta teclear algo en el cuarto campo de texto.
> >    No puedes, porque no es editable; sin embargo si se puede seleccionar el texto.
> > 4. Redimensiona la ventana.
> >    Observa como las etiquetas y los campos de texto permanecen alienados. Distribuir Parejas de Etiqueta/campo de Texto te contará más cosas sobre esta característica del programa.

> El campo Years es un ejemplar de WholeNumberField.java, que es una subclase de JTextField. Sobreescribiendo el método createDefaultModel,WholeNumberField establece una subclases Document personalizada -- un ejemplar de WholeNumberDocument -- como documento para cada WholeNumberField creado:

>     protected Document createDefaultModel() {
>         return new WholeNumberDocument();
>     }

> Aquí está la implementación de WholeNumberDocument:

>     protected class WholeNumberDocument extends PlainDocument {

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

>             char[] source = str.toCharArray();
>             char[] result = new char[source.length];
>             int j = 0;

>             for (int i = 0; i < result.length; i++) {
>     	    if (Character.isDigit(source[i]))
>     	        result[j++] = source[i];
>     	    else {
>     	        toolkit.beep();
>     	        System.err.println("insertString: " + source[i]);
>     	    }
>             }
>             super.insertString(offs, new String(result, 0, j), a);
>         }
>     }

> Esta clase sobreescribe el método insertString el cual es llamado cada vez que un string o un caracter va a ser insertado en el documento. La implementación de WholeNumberDocument de insertString evalua cada caracter a ser insertado dentro dle campo de texto. Si el carácter es un dígito, el documento permite que sea insertado. De otro modo, el método pita e imprime un mensaje de error. Por lo tanto, WholeNumberDocument permite los números en el rango 0, 1, 2, ...

> Un detalle de implementación interesante esque nuestra clase document personalizada no tiene que sobreescribir el método remove. Este método es llamado cada vez que un caracter o grupos de caracteres es eliminado del campo de texto. Como eliminar un dígito de un entero no puede producir un resultado no válido, esta clase no presta atención a las eliminaciones.

> Los otros dos campos de texto del ejemplo, así como el campo no editable Monthly Payment, son ejemplares de DecimalField.java, una subclase personalizada de JTextField. DecimalField usa un documento personalizado,FormattedDocument, que sólo permite que sena intorducidos los datos en un formato particular.

> FormattedDocument no tiene conocimiento del formato real de su contenido. En su lugar FormattedDocument relga en un formato, una ejemplar de una subclase de Format, para aceptar o rechazar el cambio propuesto. El campo de texto que usa el FormattedDocument debe especificar el formato que se debe utilizar.

> Los campos Loan Amount y Monthly Payment usan un objeto NumberFormat creado de esta forma

>     moneyFormat = NumberFormat.getNumberInstance();

> El siguiente código crea el formato dle campo de texto APR:

>     percentFormat = NumberFormat.getNumberInstance();
>     percentFormat.setMinimumFractionDigits(3);

> Como muestra el código, la misma clase (NumberFormat) puede soportar diferentes formatos. Además, Format y sus subclases son sensitivas a la localidad, por eso un campo decimal, puede hacerse para soportar formatos de otros paises y regiones. Puedes referirte a Formateando ien la sección de Internacionalización para información más detallada sobre los formatos.

> Aquí está la implementación que FormattedDocument hace de insertString:

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

>         String currentText = getText(0, getLength());
>         String beforeOffset = currentText.substring(0, offs);
>         String afterOffset = currentText.substring(offs, currentText.length());
>         String proposedResult = beforeOffset + str + afterOffset;

>         try {
>             format.parseObject(proposedResult);
>             super.insertString(offs, str, a);
>         } catch (ParseException e) {
>             Toolkit.getDefaultToolkit().beep();
>             System.err.println("insertString: could not parse: " + proposedResult);
>         }
>     }

> El método usa el formato para analizar el resultado de la inserción propuesta. Si el resultado se formatea adecuadamente, el método llamada al método insert de su superclase para hacer la inserción. Si el resultado no se formatea de la forma decuada, el ordenador pita.

> Además de sobreescribir el método insertString, FormattedDocument también sobreescribe el método remove:

>     public void remove(int offs, int len) throws BadLocationException {
>         String currentText = getText(0, getLength());
>         String beforeOffset = currentText.substring(0, offs);
>         String afterOffset = currentText.substring(len + offs, currentText.length());
>         String proposedResult = beforeOffset + afterOffset;

>         try {
>             if (proposedResult.length() != 0)
>                 format.parseObject(proposedResult);
>             super.remove(offs, len);
>         } catch (ParseException e) {
>             Toolkit.getDefaultToolkit().beep();
>             System.err.println("remove: could not parse: " + proposedResult);
>         }
>     }

> La implementación que FormattedDocument hace del método remove es similar a su implementación del método insertString. El formato analiza el resultado del cambio propuesto, y realiza la eliminación o no, dependiendo de si el resultado es válido.

> > Nota: La solución propuesta en este ejmplo no es una solución general para todos los tipos de formatos. Algunos formatos puede ser validados-por-pulsación simplemente llamando al método parseObject. Aquí tenemos un ejejmplo que te puede ayudar a entender por qué. Supongamos que tenemos un campo de texto que contiene la fecha "May 25, 1996" y queremos cambiarlo a "June 25, 1996". Deberías selección "May" y empezar a teclear "June". Tan pronto como teclearas la "J", el campo no analizaría porque "J 25, 1996" no es un dato válido, aunque si es un cambio válido. Hay un número de posibles soluciones para fechas y otros tipos de datos cuando un cambio incompleto puede crear un resultado no válido. Se puede cambiar la validación-por-pulsación para que rechace definitivamente todos los cambios no válido (teclear "X" en un campo de fecha, por ejemplo) pero permitir todos los cambios válidos posibles. O cambiar a un campo validado-en-action.

#### Usar un Oyente de Document en un Campo de Texto

> Entonces, si no podemos utilizar un oyente de document para validación de cmapos, ¿para qué podemos utilizarlo? Se usa para oir, pero no interferir, con los cambios del contenido del documento. La calculadora de pagos usa el sigueinte oyente de document para actualizar el pago mensual después de cada cambio:

>     class MyDocumentListener implements DocumentListener {
>         public void insertUpdate(DocumentEvent e) {
>             update(e);
>         }
>         public void removeUpdate(DocumentEvent e) {
>             update(e);
>         }
>         public void changedUpdate(DocumentEvent e) {
>             // we won't ever get this with a PlainDocument
>         }
>         private void update(DocumentEvent e) {
>             Document whatsup = e.getDocument();
>             if (whatsup.getProperty("name").equals("amount"))
>                 amount = amountField.getValue();
>             else if (whatsup.getProperty("name").equals("rate"))
>                 rate = rateField.getValue();
>             else if (whatsup.getProperty("name").equals("numPeriods"))
>                 numPeriods = numPeriodsField.getValue();
>             payment = computePayment(amount, rate, numPeriods);
>             paymentField.setValue(payment);
>         }
>     }

> Este es un uso apropiado para el uso de un oyente de document.

> Para información general sobre oyentes de document, puedes ir a la página Cómo Escribir un Oyente de Document.

#### Distribuir Parejas Etiqueta/Campo de Texto

> Esta sección describe cómo se an alineado las etiquetas y los campos de texto del ejemplo y requiere algún conocimiento de controladores de distribución.

> Las líneas de parejas de etiquetas y campos de texto como los encontradas en la calculadora de pagos son bastante comunes en los paneles que implementan formularios. Aquí está el código que distribuye las etiquetas y los campos de texto.

>     . . .
>     //distribuye las etiquetas sobre el panel
>     JPanel labelPane = new JPanel();
>     labelPane.setLayout(new GridLayout(0, 1));
>     labelPane.add(amountLabel);
>     labelPane.add(rateLabel);
>     labelPane.add(numPeriodsLabel);
>     labelPane.add(paymentLabel);

>     //distribuye los campos de texto sobre el panel
>     JPanel fieldPane = new JPanel();
>     fieldPane.setLayout(new GridLayout(0, 1));
>     fieldPane.add(amountField);
>     fieldPane.add(rateField);
>     fieldPane.add(numPeriodsField);
>     fieldPane.add(paymentField);

>     //Pone los paneles sobre otro panel, las etiquetas a la izquierda,
>     //los campos de texto a al derecha
>     JPanel contentPane = new JPanel();
>     contentPane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
>     contentPane.setLayout(new BorderLayout());
>     contentPane.add(labelPane, BorderLayout.CENTER);
>     contentPane.add(fieldPane, BorderLayout.EAST);

>     setContentPane(contentPane);
>     . . .

> Podrías haberte sorprendido de encontrar que las etiquetas fueran distribuidas sin referencia a los campos de texto, y de hecho, están en un panel diferente, sólo alineados correctamente con ellos. Esto es un efecto colateral de los controladores de distribución usados por el programa.

> Como muestra el diagrama, el progra usa dos controladores GridLayout, uno para la columna de etiquetas y otro para la columna de campos de texto. GridLayout garantiza que todos sus componentes sean del mismo tamaño, por eso todos los campos de texto tienen la mista altura y todas las etiquetas tienen la misma altura.

> Para conseguir que las etiquetas y los campos de texto esten alineados, el programa usa un tercer controlador, un BorderLayout. con sólo dos componentes, en la izquierda y en el centro, BorderLayout garantiza que las columnnas son de la misma altura. Y así se alinean las etiquetas y los campos de texto.

> Otra forma de conseguir alinear las etiquetas y los campos de texto es utilizar el más complejo de los controladores de distribución del AWT, el GridBagLayout.

#### Proporcionar un Campo de Password

> Swing proporciona la clase JPasswordField, una subclase de JTextField, que se usa en lugar de un campo de texto cuando el texto introducido por el usuario es una password. Por razones de seguridad, un campo de password no muestra los caracteres que teclea el usuario. En su lugar el campo muestra otro caracter, como un asterisco "*".

> El ejemplo PasswordDemo descrito en Usar la Clase SwingWorker usa un JPasswordField. El programa trae una pequeña ventana para pedirle al usuario que teclee una password:

> Aquí está el código de PasswordDemo que crea y configura el campo password:

>     JPasswordField password = new JPasswordField(10);
>     password.setEchoChar('#');

>     password.addActionListener(showSwingWorkerDialog);

> Como con los campos de texto, el argumento pasado al constructor JPasswordField indica que el campo deberá tener 10 columnas de ancho. Por defecto, un campo password muestra asteriscos "*" por cada caracter tecleado. La llamada a setEchoChar lo cambia por el signo de almohadilla "#". finalmente, el código añade una oyente de action al campo password. El método actionPerformed del oyente de actuib obtiene la password tecleada por el usuario y la verifica con este código:

>     public void actionPerformed(ActionEvent e) {
>         JPasswordField input = (JPasswordField)e.getSource();
>         char[] password = input.getPassword();
>         if (isPasswordCorrect(password))
>             JOptionPane.showMessageDialog(f, worker.get());
>         else
>             JOptionPane.showMessageDialog(f,
>     	    new JLabel("Invalid password."));
>         }

> Este método utiliza el método getPassword para obtener el contenido del campo. Esto es por lo que getPassword devuelve un array de caracteres. La información de passwords no debería ser almacenada no pasada en strings, porque no son seguras.

> Un programa que usa un campo de password tipicamente valida la password antes de completar cualquier acción que requiera la password. Este programa llama a un método personalizado, isPasswordCorrect, que compara el valor devuelto por getPassword con el valor almacenado en un array de caracteres.

#### El API de Text Field

> Las siguientes tablas listan los constructores y métodos más comunmente utilizados de JTextField. Otros métodos a los que se podría llamar están definidos en las clases JComponent y Component. Estos incluyen los métodos setForeground, setBackground, y setFont.

> Además, podrías querer llamar a algunos de los métodos definidos en la clase padre de JTextField, JTextComponent.

> El API para usar campos de texto se divide en tres categorías:

> - Seleccionar u Obtener el Contenido de un Campo de Texto
> - Ajuste Fino de la Apariencia de un Campo de Texto
> - Implementar la Funcionalidad de un Campo de Texto

> Seleccionar u Obtener el Contenido de un Campo de Texto
> Método o Constructor | Propósito
> --- | ---
> JTextField() JTextField(String) JTextField(String, int) JTextField(int) JTextField(Document, String, int) | Crea un ejemplar de JTextField, inicializando su contenido al texto especificado. El argumento int seleccionar el número de columnas. Esto se utiliza para calvlar la anchura preferida del componente y podría no ser el número de columnas realmente mostradas.
> void setText(String) String getText() | Seleccion u obtiene el texto mostrado por el campo de texto.

> Ajuste Fino de la Apariencia de un Campo de Texto
> Método o Constructor | Propósito
> --- | ---
> void setEditable(boolean) boolean isEditable() | Selecciona u obtiene si el usuario puede editar el texto del campo del texto.
> void setForeground(Color) Color getForeground() | Selecciona u obtiene el color del texto en el campo de texto.
> void setBackground(Color); Color getBackground() | Selecciona u obtiene el color del fondo del campo de texto
> void setFont(Font); Font getFont() | Selecciona u obtiene la fuente utilizada por el campo de texto.
> void setColumns(int); int getColumns() | Selecciona u obtiene el número de columnas mostradas por el campo de texto.
> int getColumnWidth() | Obtiene la anchura de las columnas del campo de texto. Este valor es establecido implicitamente por la fuente usada.
> void setHorizontalAlignment(int); int getHorizontalAlignment() | Selecciona u obtiene cómo se alinea el texto horizontalmente dentro de su área. S epuede utilizar JTextField.LEFT, JTextField.CENTER, y JTextField.LEFT como argumentos.

> Implementar la Funcionalidad del Campo de Texto
> Método o Constructor | Propósito
> --- | ---
> void addActionListener(ActionListener) void removeActionListener(ActionListener) | Añade o elimina un oyente de action.
> Document createDefaultModel() | Sobreescribe este método para proporcionar un documento personalizado.

#### Ejemplos que usan Campos de Texto

> Esta tabla muestra los ejemplos que usan JTextField y dónde están descritos esos esjemplos:

> Ejemplo | Dónde se Describe | Notas
> --- | --- | ---
> TextDemo.java | Esta página | Usa un text field con un oyent ede action.
> TextFieldDemo.java | Esta página | Implementa dos campos validados-por-pulsación.
> PasswordDemo.java | Esta página y Usar la Clase SwingWorker | Usa un campo de password.
> CustomDialog.java | Cómo crear Dialogs | Incluye un campo de texto cuyo valor es chequeado.

Ozito