Professional Documents
Culture Documents
1 de 24
entrar texto. Tambin muestra lo que hemos entrado. Volveremos ms tarde sobre este punto. Resumiendo un poco las caractersticas de este componente podemos decir que: Distingue entre el valor del campo y el texto que lo representa Permite especificar formatos fijos de entrada de datos, por ejemplo, mediante mscaras Sabe aprovechar el resto de especificaciones de formato disponibles en Java para nmeros, fechas, horas, etc. Permite decidir si se admiten caracteres incorrectos en la entrada o no. Permite distinguir entre modalidad de edicin y modalidad de visualizacin. Permite establecer dos modalidades de escritura: insercin y sobrescritura. Permite que decidamos qu hacer con el foco si lo que el usuario ha entrado no es correcto, al beneficiarse de las nuevas capacidades del JDK 1.4 para la comprobacin de valores en Swing. Bueno, creo que las posibilidades que nos ofrece JFormattedTextField son esperanzadoras e intentar mostrar hasta qu punto. Para ello, he pensado dividir el artculo en tres partes diferenciadas con objetivos distintos. La primera, muestra los usos ms habituales de JFormattedTextField y comenta cmo usar la clase javax.swing.InputVerifier para controlar el foco en base al valor entrado en el campo. El objetivo, en esta primera parte, es conseguir que el lector pueda utilizar fcilmente JFormattedTextField en sus aplicaciones. La segunda discute ms a fondo la arquitectura y el funcionamiento de JFormattedTextField, y tiene como objetivo preparar al lector para poder extender las posibilidades de JFormattedTextField, extendindolo y creando nuevos componentes especializados. En la tercera, presento algunos widgets derivados de JFormattedTextField, y desarrollados especficamente para este artculo, que tienen un doble objetivo: mostrar al lector una posible va de expansin del componente que nos ocupa y proporcionarle unos componentes listos para ser usados en sus aplicaciones. Tambin proporciono algunas aplicaciones que ejemplifican tanto el uso de estos widgets como de algunos aspectos de JFormattedTexField y de las clases derivadas de JFormattedTextField.AbstractFormatter.
Qu es JFormattedTextField?
JFormattedTextField es un componente derivado de JTextField que, como ste, sirve para entrar y mostrar datos. Una de las caractersticas principales de JFormattedTextField es la de permitir dar formato a los datos, tanto en el momento de entrarlos como en el de visualizarlos. Para ello, y a diferencia de JTextField, JFormattedTextField distingue entre el valor almacenado (una subclase de Object accesible mediante el mtodo getValue()) y el texto que muestra (una java.lang.String accesible mediante getText()). En el siguiente apartado, veremos cmo podemos especificar los distintos formatos.
2 de 24
Especificacin de formato
El componente JFormattedTextField nos permite especificar el formato de diversas maneras:
Pero no slo nos presenta la fecha. Nos permite editarla de una manera sencilla y sin error posible. Si colocamos, por ejemplo, el cursor sobre el mes y pulsamos la flecha hacia arriba, el mes cambia y pasa a ser sept. Si pulsamos la flecha hacia abajo, el mes ser jul. El mismo comportamiento se da para el da y el ao. Adems, el comportamiento es inteligente. Supongamos que la fecha sea 28 de febrero de 2002 y que aumentemos el da. La nueva fecha sera 1 de marzo de 2002. Lgicamente, si el ao fuera el 2000 (bisiesto) la fecha propuesta sera el 29 de febrero de 2000.
Fijmonos que las partes escribibles se representan con el carcter de subrayado que hemos especificado con setPlaceholderCharacter(). La siguiente tabla resume los caracteres utilizables en una mscara:
Carcter
# ? A * U L H ' Un nmero Una letra
Descripcin
Una letra o un nmero Cualquier cosa Una letra que ser pasada a mayscula Una letra que ser pasada a minscula Un dgito hexadecimal (A-F, a-f, 0-9) Carcter de escape para otro carcter de mscara
usado por el campo. Ejemplificaremos algunos de ellos. Ms arriba, hemos mostrado cmo especificar un formato de fecha simplemente pasando una al constructor del campo. El resultado es vistoso, pero cualquier persona que entre datos nos dir que es poco prctico. Sera ms interesante algo del estilo de dd/mm/aa. El siguiente cdigo nos muestra cmo hacerlo:2
JFormattedTextField efFecha = new JFormattedTextField(new SimpleDateFormat(dd/MM/yy));
El resultado obtenido sera: 19/08/02. El comportamiento de las flechas sera el ya descrito. Si lo que pretendemos es entrar un nmero con un mximo de dos decimales:
JFormattedTextField efNum = new JformattedTextField(new DecimalFormat(#,###.00));
Disponemos de tres formateadores especiales derivados todos ellos de la clase javax.swing.JFormattedTextField.AbstractFormatter: MaskFormatter Utilizado para mscaras y derivado directamente de AbstractFormatter. NumberFormatter Utilizado para nmeros y derivado de una subclase de AbstractFormatter: InternationalFormatter. DateFormatter Utilizado para fechas y horas y derivado, como el anterior, de InternationalFormatter.
Insertar o sobrescribir
Algo de sumo inters es poder especificar si insertamos o sobrescribimos caracteres. Lo ideal sera que se pudiera decidir pulsando la tecla <Ins>, pero esto no es inmediato. El siguiente ejemplo nos indica cmo permitir la sobrescritura:4
2 Los componentes DateField, DecimalField, DecimalFieldScrollable, IntegerField, MoneyField y PercentField, comentadas ms abajo, ilustran la asignacin de diversos formatos y se incluyen en el cdigo fuente de este artculo. 3 La aplicacin de ejemplo IntegerFieldTest, comentada ms abajo, nos permite comprobar el funcionamiento de setAllowsInvalid(boolean). 4 El componente DefaultJFormattedTextField, comentado ms abajo, implementa los mtodos de insercin y sobrescritura y se incluye en el cdigo fuente de este artculo.
4 de 24
Editamos en formato ingls (usamos el punto como separador decimal) y sin separadores de millares. Visualizamos lo que hemos entrado en el formato de moneda y numrico de nuestro pas. En este caso, el euro como smbolo de moneda, el punto como separador de millares y la coma como separador decimal, pero si el programa se ejecutara en Inglaterra, veran el smbolo de la Libra, la coma sera el separador de millares y el punto, el separador decimal.
5 Los componentes DecimalField, DecimalFieldScrollable, IntegerField, PercentField y MoneyField, comentadas ms abajo e incluidas en el cdigo fuente de este artculo, ilustran el uso de formatos distintos para la edicin y la visualizacin.
5 de 24
Si observamos el cdigo, veremos que opto por admitir caracteres incorrectos en edicin. El motivo es que NumberFormatter define el comportamiento de las teclas ms (+) y menos (-) haciendo que sean las responsables del cambio de signo. No escriben el signo, sino que lo cambian. Por ello, y para hacer que la escritura se parezca ms a la que solemos utilizar, he decidido permitir el uso de caracteres incorrectos en el ejemplo.
La mscara obligar al usuario a entrar ocho dgitos y una letra que ser pasada a maysculas. Adems, mediante el mtodo setPlaceholderCharacter(), asignamos un carcter de subrayado para que sirva de pauta al usuario, indicndole las posiciones editables del campo. El carcter U que vemos en la mscara obligar al usuario a escribir la letra del DNI y pasar dicha letra a maysculas. La mscara se encargar, pues, de que el usuario escriba dgitos y letras donde corresponda, pero el valor entrado no ser entregado al campo directamente hasta que pulsemos la tecla Intro. Al
6 El componente DNIField, cuyo cdigo fuente se incluye, es un ejemplo completo de campo destinado a la entrada de DNIs.
6 de 24
cambiar de foco, el MaskFormatter no entrega el valor. Hay que decirle explcitamente que si lo editado es vlido, pase el valor al campo. Para ello, utilizaremos el mtodo setCommitsOnValidEdit(boolean).
maskDNI.setCommitsOnValidEdit(true);
Si comentamos esta lnea, veremos que al entrar un DNI incorrecto nos deja cambiar el foco debido a que el valor no se ha entregado al campo para que determine si debe permitir el cambio de foco o no. Finalmente, creamos el campo:
JFormattedTextField efDNI = new JformattedTextField(maskDNI);
En este momento, ya hemos dotado a nuestro campo de un cierto control para entrar DNIs:
Nos fuerza a escribir los nmeros y la letra en los lugares que corresponde Pasa automticamente la letra final de control a maysculas
Sin embargo, la mscara no nos proporciona todo el control que necesitamos. Si la persona que entra los datos se equivoca en la letra de control, el error queda registrado. Necesitamos, pues, impedir que la persona que entra los datos entre un DNI errneo (aunque, y de eso se encarga la mscara, bien formado). La versin 1.3 del JDK incorpora un nuevo mtodo a la clase javax.swing.JComponent: setInputVerifier(InputVerifier v). Este mtodo nos permite asignar a un JFormattedTextField un algoritmo de control del contenido entrado. Este algoritmo de control se hallar embebido en una subclase de InputVerifier. La clase InputVerifier es abstracta y obliga a sus subclases a implementar el mtodo public boolean verify(JComponent input). Este mtodo devuelve true, si la comprobacin es correcta, o false, si no lo es. Veamos ahora la clase derivada de InputVerifier que se encarga de verificar si el DNI entrado es correcto y permite al campo decidir si autoriza, o no, el cambio de foco. Disponemos de la clase CIF_NIF, con el mtodo esttico boolean isNIFOK(String DNI) que nos devuelve true o false en funcin del DNI pasado como parmetro.7 Creamos, por ejemplo, la clase ValidateDNI que extiende InputVerifier:
class ValidateDNI extends InputVerifier { /** * Sobrescribimos el mtodo del padre para realizar la * comprobacin del DNI entrado. */ public boolean verify(JComponent input) { if (input instanceof JFormattedTextField) { Object o = ((JFormattedTextField)input).getValue(); if (o == null) return true; String value = o.toString(); return CIF_NIF.isNIFOK(value);
} return false;
El mtodo verify() se encarga de llamar al mtodo CIG_NIF.isNIFOK() que contiene el algoritmo de verificacin de DNIs. Si este mtodo da el DNI por bueno, el usuario podr cambiar el
7 La clase CIF_NIF, comentada ms abajo, se incluye en el cdigo fuente de este artculo.
7 de 24
Observaciones generales
JFormattedTextField siempre almacena como valor un objeto (una subclase de Object). Este valor, sin embargo, debe ser representado como una tira de caracteres (una String) ya que JFormattedTextField no es sino una subclase de JTextField, quien, como su propio nombre indica, es un campo de texto. Esto no es problema para JTextField, ya que siempre almacena objetos de tipo String, pero para JFormattedTextField no es tan evidente. Alguien tiene que transformar este objeto en una String para que pueda ser representado. Volviendo al nombre de nuestro componente, JFormattedTextField, observamos que contiene el adjetivo formatted (formateado, con formato). Este es un detalle importante. La transformacin del objeto almacenado a String comporta un proceso de aplicacin de formato. Resumiendo, JFormattedTextField : 1. Toma el valor que le asignamos, 2. crea una tira de caracteres convenientemente formateada segn algn criterio y 3. la muestra en el campo Pero hemos comentado ms arriba que el valor no slo se asigna y se ve, sino que se edita. Es decir, que hay un formato de edicin y que quien controla este formato se encarga de decidir, por ejemplo, si en tal posicin podemos escribir un nmero o una letra o si podemos escribir o no en una posicin concreta.
El formato
Los responsables del formato, tanto del de edicin como del de visualizacin, son los formateadores. Un formateador es, en realidad, una clase derivada de JFormattedTextField.AbstractFormatter y cumple diversas funciones. En modo de edicin, decide qu se puede escribir y dnde y cmo y cundo pasa el valor editado al
8 El componente de ejemplo DNIField, descrito ms abajo, se incluye en el cdigo fuente de este artculo.
8 de 24
campo. Por ejemplo, si usamos un formato numrico, y no permitimos la insercin de caracteres incorrectos, no podremos teclear ninguna letra. En el campo de DNI que he puesto de ejemplo, nunca podremos escribir sobre el guin de separacin de la letra de control, a pesar de que MaskFormatter use el modo de sobrescritura por defecto. En algunos casos, el formateador tambin define el comportamiento del teclado. Por ejemplo, DateFormatter permite el incremento o decremento de los distintos campos de una fecha (da, mes, etc.) mediante las flechas del teclado. En modo de visualizacin, decide cmo se muestra el valor almacenado en el campo. As, en la aplicacin de ejemplo MoneyFieldTest, podremos comprobar que el valor almacenado como BigDecimal en un campo MoneyField, en modo de visualizacin, se muestra como un nmero y un carcter de moneda. Un formateador transforma un valor (esto es, un Object) a una tira de caracteres usando el mtodo valueToString(Object) y una tira de caracteres a un valor (esto es, un Object) usando el mtodo stringToValue(String). Estos son los mtodos que le permiten almacenar lo editado como una subclase de Object y mostrar este valor como una tira de caracteres en el campo. La siguiente figura muestra la jerarqua de clases de los distintos formateadores:
El ms sencillo es DefaultFormatter que se utiliza para formatear objetos arbitrarios. El mtodo valueToString(), simplemente, devuelve el resultado del mtodo toString() del valor almacenado. Y para almacenar una tira como valor, usa un constructor de la clase del objeto que tenga como parmetro una tira de caracteres. InternationalFormatter es una subclase de DefaultFormatter que usa java.text.Format para pasar de String a Object y viceversa. Por defecto, slo admite caracteres correctos (setAllowsInvalid(false)), por lo que no es conveniente modificar esta propiedad si no queremos tener problemas. 9 de 24
Tambin se encarga de ajustar la posicin del cursor, situndolo sobre aquellas posiciones en las que se puede escribir. NumberFormatter es una subclase de InternationalFormatter diseada especialmente para la entrada de nmeros. Entre otras cosas, establece el comportamiento de la tecla <menos> de manera que, estemos donde estemos del campo, convierte el nmero entrado en negativo.9 La tecla <ms> pasa el nmero a positivo. Esto es, desaparece el signo menos. DateFormatter es una subclase de InternationalFormatter diseada especialmente para la entrada de fechas. Como hemos comentado ms arriba, define el comportamiento de las flechas del teclado para aumentar o disminuir das, meses, aos, etc. La clase MaskFormatter extiende DefaultFormatter y est pensada especialmente para la edicin de tiras de caracteres con formatos especficos. Como hemos visto ms arriba, se basa en una mscara que indica qu caracteres se pueden escribir en una posicin determinada.
setAllowsInvalid(boolean): Nos permite decidir si aceptamos caracteres incorrectos o no. setCommitsOnValidEdit(boolean): Nos permite, en modo de edicin, decidir cundo se libra el valor de lo que estamos escribiendo al campo. Si usamos true como parmetro, cada vez que escribamos algo se validar y, si es correcto, se asignar como valor del campo. setOverwriteMode(boolean): Nos permite decidir si la modalidad de edicin es sobrescritura (true) o insercin (false). setMaximum(Comparable): Para establecer el valor mximo admisible por el campo. setMinimum(Comparable): Para establecer el valor mnimo admisible por el campo.11 getFields(int offset): Para determinar qu campo (entero, decimal, signo, etc.) se corresponde con una posicin determinada.12
9 Curiosamente, si no hemos entrado ningn texto o valor y no permitimos la entrada de caracteres incorrectos, pulsar la tecla <menos> no sirve de nada. 10 En los componentes que he desarrollado de ejemplo, he considerado necesario facilitar el acceso a alguno de los mtodos de personalizacin de formateadores desde el propio componente. As, por ejemplo, los componentes ponen a disposicin del programador mtodos como setOverwriteMode(boolean) o setAllowInvalidCharacters(boolean). 11 Los mtodos setMaximum(Comparable) y setMinimum(Comparable) se han implementado en los componentes de ejemplo DefaultNumberField, IntegerField, DecimalField. Esto permite establecer rangos de valores aceptables para instancias de dichos componentes y de sus subclases PercentField y MoneyField. 12 Vase el mtodo sum(int sign) del componente de ejemplo DefaultNumberField.
10 de 24
A falta de pruebas intensivas, observo que si, habiendo definido un rango de valores aceptable para un InternationalFormater, asignamos un valor fuera del rango definido mediante el mtodo setValue(object), no se tiene en cuenta el rango y el valor se asigna sin problemas al campo.13 MaskFormatter nos obsequia con algunos mtodos realmente tiles:
setInvalidCharacters(String): Nos permite especificar una lista de caracteres que no sern aceptados por el campo. Supongamos, por ejemplo, que tenemos un campo con una mscara para entrar cdigos de producto. La mscara podra ser parecida a sta: U###. Es decir, un carcter alfabtico que ser pasado a maysculas, seguido de tres dgitos. Es de esperar que el usuario no entre una letra acentuada en la primera posicin de la mscara, pero los usuarios son muy listos y seguro que ms de uno lo intentar. Si recordamos la sintaxis para la especificacin de mscaras, observaremos que no hay ninguna manera de especificar que no admitiremos caracteres acentuados. Por lo tanto, el usuario astuto puede entrar una y fastidiarnos la aplicacin. La manera de impedirlo es, pues, usando el mtodo setInvalidCharacters() al que se le pasar como parmetro una String con todos los caracteres acentuados (en maysculas). setValidCharacters(String): Nos permite especificar la lista de caracteres que sern aceptados. Se trata del mtodo complementario del anterior. Siguiendo con el mismo ejemplo, podramos especificar que los caracteres aceptables son ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789. setPlaceHolder(String) y setPlaceHolderChracter(char): permiten, como hemos visto ms arriba, especificar los caracteres que no se mostrarn en las posiciones escribibles. En el ejemplo del campo de DNI, el carcter utilizado era el de subrayado. setValueContainsLitteralCharacters(boolean): Nos permite decidir si los valores entrados, con setValue(), o recuperados, con getValue(), contienen tambin los literales incluidos en la mscara (true) o no (false). As, si nuestra mscara para DNI es ##.###.###-U y hemos especificado true, getValue(), nos devolver 12.345.678-A. Si, por el contrario, especificamos false, nos devolver 12345678A. De igual manera, si hemos especificado true, el parmetro para setValue() tendr que ser 12.345.678-A y si hemos especificado false, 12345678A.
Asignacin de formatos
La nica manera que tenemos de asignar formatos a un JFormattedTextField es mediante una subclase de JformattedTextField.AbstractFormatterFactory. Normalmente, pues, usaremos la nica existente: DefaultFormatterFactory. DefaultFormatterFactory permite especificar cuatro AbstractFormatters para tres situaciones distintas: 1. Formato por defecto: Se usa si no hay otro definido para una situacin concreta, lo cual implica que se usar tanto como formato de edicin como de visualizacin y como nulo, si
13 Esto es debido a las diferencias de comportamiento entre setValue() y commitEdit() que se comentan ms abajo. Este comportamiento ha sido corregido en los componentes numricos de ejemplo. Si se intenta asignar un valor fuera de rango, se lanza una IllegalArgumentException.
11 de 24
slo especificamos ste. 2. Formato de edicin: Se usa cuando el campo tiene el foco y sirve para facilitar la edicin 3. Formato de visualizacin: Se usa cuando el campo no tiene el foco y se utiliza para mostrar el valor 4. Formato nulo: Se usa cuando el campo tiene un valor nulo (null)
Los procesos
En el apartado Observaciones generales, he presentado un breve esquema de los principales procesos que realiza JFormattedTextField para almacenar un valor y mostrarlo. Vamos ahora a entrar con un poco ms de detalle en estos procesos.
12 de 24
Valor JFormattedTextField.REVERT
Descripcin
Revierte lo editado al ltimo valor almacenado en el campo. Es decir, al valor devuelto por el mtodo getValue(). Si no hay ningn valor almacenado previamente o no forzamos un setValue(), por ejemplo, pulsando <intro>, el contenido de la edicin actual se perder. Entrega el valor actual al perder el foco. Si el valor editado no es considerado un valor legal por el AbstractFormatter (esto es, se lanza una ParseException), entonces el valor editado no cambiar pero no se asignar como valor. Resumiendo, se comporta como COMMIT, pero si el valor editado no es correcto, no limpia el campo.
JFormattedTextField.COMMIT
JFormattedTextField.COMMIT_OR_REVERT Es similar a COMMIT, pero si el valor que escribimos no es legal (esto es, el AbstractFormatter lanza una ParseException), se comporta como REVERT y limpia el contenido del campo. JFormattedTextField.PERSIST No hace nada, no obtiene un nuevo AbstractFormatter y no actualiza el valor. Sea correcto o incorrecto lo que entremos, mantiene el valor correcto anterior pero no limpia el campo.
13 de 24
crear una DefaultFormatterFactory basada en un DateFormatter, si es una instancia de NumberFormat, usar un NumberFormatter, si es una Date, usar DateFormatter, si es un Format, usar InternationalFormatter, si se trata de un Number, usar un NumberFormatter como formateador por defecto y para visualizacin y un NumberFormatter especial que usa un DecimalFormat con el patrn #.# para la edicin. 3. Asigna el valor. Observamos una diferencia notable entre commitEdit() y setValue(). El primero usa el mtodo stringToValue() para obtener el valor a pasar como parmetro de setValue(), mientras que ste asigna el valor directamente. Esto es, commitEdit() puede recibir una ParseException como resultado de invocar stringToValue() si el formateador detecta un error de formato. Hemos de hablar, pues, de un comportamiento asimtrico entre commitEdit() y setValue() que no suele tener consecuencias. Sin embargo, hay algn caso en que esta asimetra comporta algn que otro problema. Veamos un ejemplo: Nosotros construimos un campo para entrada de DNIs con un MaskFormatter, tal y como hemos visto ms arriba. Pero ahora decidimos que el valor del campo no debe contener literales y lo especificamos usando el mtodo valueContainsLitteralCharacters(false). Si asignamos como valor, por ejemplo, 12.345.678-Z, usando setValue(), no se produce ningn error ya que no interviene para nada el formateador. Es ms, getValue() devuelve 12.345.678-Z en vez de 12345678Z.15
15 Esta asimetra ha sido corregida para el componente de ejemplo DNIField. El mtodo setValue() llama siempre al formateador para comprobar errores de formato.
14 de 24
APLICACIONES DE EJEMPLO
Para ilustrar los conceptos tratados en este artculo, he desarrollado una serie de aplicaciones. Dichas aplicaciones se incluyen con el cdigo fuente correspondiente para que el lector pueda estudiarlas y modificarlas a su conveniencia. He procurado que los ejemplos no sean abstractos sino que sean prcticos y usables para cualquier desarrollador. He clasificado los ejemplos en tres categoras: 1. Componentes auxiliares: se usan en las aplicaciones de demostracin y tienen poco que ver con JFormattedTextField. A pesar de ello, creo que algunas de ellas pueden ser bastante interesantes para los desarrolladores. 2. Componentes derivados de JFormattedTextField: son un conjunto de subclases de JFormattedTextField que, a mi entender, cumplen un doble objetivo. Por una parte ilustran la mayor parte de conceptos relacionados con JFormattedTextField y con los distintos formateadores y por otra, constituyen un conjunto de componentes (en ingls, los llamaran widgets) especializados en distintas tareas (entrada de nmeros, porcentajes, fechas y DNIs) listos para ser utilizados por cualquier desarrollador. La estrategia seguida para el desarrollo de los componentes ha sido doble. Por una parte, he aadido funcionalidades que no estn directamente relacionadas con JFormattedTextField, como la aritmtica de fechas, la asignacin de escala a un valor decimal o la autocomplecin de un DNI y, por otro, he hecho emerger, a nivel de componente, propiedades del formateador, como la asignacin dinmica del formato de representacin de fechas o la especificacin de rango para los campos numricos. 3. Aplicaciones de demostracin: se trata de pequeas aplicaciones que ilustran tanto el funcionamiento de los distintos componentes descritos en el punto anterior, como el de algunos aspectos de JFormattedTextField y de los formateadores. A continuacin, paso a describir brevemente cada una de ellas.
Componentes auxiliares
CIF_NIF
Es una clase que proporciona una serie de mtodos estticos para la verificacin de CIFs y NIFs espaoles. Contiene una amplia documentacin sobre las fuentes en las que me he basado para escribirla y la casustica que se trata. Destacara las siguientes funcionalidades:
Determina si una tira se corresponde con un NIF o con un CIF Determina si un NIF o un CIF son correctos Dado un NIF sin letra de control, calcula y devuelve dicha letra Trata NIEs (NIFs para extranjeros)
BoundJSpinner
Es una subclase de JSpinner que tiene la propiedad value bound. Esto es, cada vez que el valor 15 de 24
de BoundJSpinner cambia, se genera un PropertyChangeEvent. Se usa en las demostraciones de algunos de los componentes.
ButtonGroupJPanel
Es una subclase de JPanel que facilita el uso de JRadioButons desde un editor visual. Si queremos un comportamiento normal de mutua exclusin de JRadioButons (esto es, que cuando se pulse en uno el que estaba seleccionado deje de estarlo), es necesario aadir todos los JRadioButons a un ButtonGroup. ButtonGroupPanel, se encarga de ello por nosotros. ButtonGroupPanel est basado en un ejemplo de Scott Stanchfield (http://www.javadude.com) y se usa en las demostraciones de algunos componentes.
OverwriteCaret
Es una subclase de DefaultCaret que dibuja un cursor horizontal. Se utiliza para indicar que estamos en modalidad de sobrescritura.
Pair
Un simple bean no visual que mantiene una pareja de tipo clave/descripcin. Se usa en los JCombobox de algunas de las demostraciones de los componentes.
EnhancedJFormattedTextField
Es una interface que establece los mtodos, y por ende las funcionalidades, generales del conjunto de componentes.
DefaultJFormattedTextField
Es una subclase abstracta de JFormattedTextField que implementa la interface EnhancedJFormattedTextField y que contiene cdigo para funcionalidades comunes al resto de los componentes. Destaco las siguientes:
Gestin y creacin de la AbstractFormatterFactory usada por los distintos subcomponentes. Posibilidad de establecer la modalidad de escritura y mostrar un cursor diferente para cada modalidad. La asignacin de dicha funcionalidad a la tecla <ins>, que intercambia las dos modalidades de escritura.
16 A pesar de haber realizado una infinitud de pruebas y de disear y ejecutar pruebas unitarias, puede que los componentes no se comporten como debieran. Los proporciono a guisa de ejemplos y no me responsabilizo de los efectos colaterales que se deriven de su uso en produccin.
16 de 24
Implementa el mtodo clear() que permite borrar el contenido de un campo. Hace accesibles, a nivel de componente, el uso de los mtodos setCommitsOnValidEdit(boolean) y getCommitsOnValidEdit() de DefaultFormatter. Implementa, a nivel de componente, la poltica de aceptacin de caracteres incorrectos del formateador. Implementa, a nivel de componente, la poltica de establecimiento y gestin de rangos de valores aceptables. Implementa el mtodo isEmpty() que nos indica si el campo est vaco..
DefaultNumberField
Es una subclase abstracta de DefaultJFormattedTextField que contiene cdigo para las funcionalidades comunes de los campos numricos (DecimalField, IntegerField, MoneyField y PercentField). Por ejemplo, define el comportamiento del teclado para que las flechas sirvan para incrementar o decrementar el valor almacenado en el campo o permite establecer un rango de valores para los componentes numricos.
DecimalField
Extiende DefaultNumberField adaptndolo a la presentacin y edicin de nmeros decimales. Destacar las siguientes funcionalidades:
Distingue el modo de presentacin, en el que muestra separadores de millares y un carcter de separacin decimal acorde con el Locale, del de edicin, en el que se facilita el uso del teclado numrico Permite establecer diversas polticas de redondeo Permite determinar la escala Los valores entrados se almacenan siempre como BigDecimals Se le puede asignar cualquier valor derivado de Number, pero tambin valores de tipos nativos (int, long, byte, short, float, double, etc.). Internamente, se almacenan como BigDecimals
IntegerField
Es una subclase de DefaultNumberField que facilita la edicin y presentacin de nmeros enteros. Se le puede asignar cualquier valor derivado de Number (si el valor tiene decimales, slo se toma la parte entera) pero tambin valores de tipos nativos (int, long, byte, short, float, double, etc.). Internamente, se almacenan como BigInteger.
MoneyField
Extiende DecimalField asignando como formato de visualizacin el de moneda.
PercentField
Extiende DecimalField asignando como formato de visualizacin el de porcentaje. 17 de 24
DNIField
Es una subclase de DefaultJFormattedTextField que facilita la entrada y validacin de DNIs espaoles mediante una mscara de entrada. Funcionalidades destacables:
Permite determinar si el DNI entrado es correcto (boolean DNIField.isOK()) Permite la activacin y desactivacin del proceso de verificacin. Si est activada la verificacin y el valor entrado no es correcto, se deshabilita el cambio de foco. Permite completar el DNI con la letra de control correcta pulsando <Ctrl-Espacio> o, tambin, cuando el campo pierde el foco. Evita la entrada de letras de control no admisibles para DNIs.
DateField
Es una subclase de DefaultFormattedTextField que facilita la entrada, visualizacin y manejo de fechas y horas. Destaco las siguientes funcionalidades:
Admite valores asignables de tipo Date y Calendar Permite cambiar dinmicamente el formato de visualizacin (el de edicin es el mismo que el de visualizacin) especificando patrones con sintaxis de SimpleDateFormat Implementa una aritmtica simple de fechas. Permite aadir o quitar das, semanas, meses, aos, horas, minutos o segundos a la fecha almacenada como valor en el campo de manera sencilla (p.e. addMonths(3), aadira tres meses, addWeeks(-3) restara tres semanas a la fecha).
StringField
Extiende DefaultFormattedTextField para facilitar la escritura de texto. Si bien Swing nos proporciona ya un campo de texto, JTextField, considero que no es suficiente para cubir algunas de las necesidades ms importantes de un campo de este estilo. As, por ejemplo, JTextField slo nos proporciona un mtodo de escritura, la insercin, y no nos permite determinar la longitud mxima del texto a escribir. Este segundo aspecto es importante si tenemos ligado el texto a alguna columna de una tabla en una base de datos. Si usamos JTextField, no podemos asegurar que el texto entrado por el usuario tenga una longitud inferior o igual a la definida para la columna de la tabla, por lo que nos veremos obligados ha controlar este hecho por programa. StringField nos permite delegar en la interficie dicho control al permitirnos determinar la longitud mxima admisible para el texto. StringField nos permite, tambin, establecer una poltica de recorte para la asignacin de valor. Si la activamos, usando el mtodo setStripOn(boolean), al intentar asignar un valor con una longitud superior a la permitida, ste ser recortado convenientemente antes de ser asignado. Si no la tenemos activada, consecuentemente, lanzar una IllegalArgumentException al intentar asignar un valor con una longitud superior a la permitida.
18 de 24
Aplicaciones de demostracin
Siempre se ha dicho que una imagen (en nuestro caso, una aplicacin visual) vale ms que mil palabras. Es por este motivo que he creado una serie de aplicaciones de escritorio que pretenden ejemplificar las funcionalidades de cada uno de los componentes comentados en el apartado anterior. As, pues, el lector dispone de un ejemplo de uso para cada uno de los componentes derivados de JFormattedTextField:
DecimalFieldTest Ilustra las posibilidades de DecimalField IntegerFieldTest Ilustra las posibilidades de IntegerField y las distintas polticas de comportamiento de JFormattedTextField con la prdida de foco. Tambin muestra las posibilidades de uso de setAllowsInvalid(). Permite, a su vez, verificar las asignaciones de valor cuando hay prdida de foco en funcin de la poltica definida. MoneyFieldTest Ilustra las posibilidades de MoneyField PercentFieldTest Ilustra las posibilidades de PercentField DNIFieldTest Ilustra las posibilidades de DNIField DateFieldTest Ilustra las posibilidades de DateField StringFieldTest Ilustra las posibilidades de StringField
Estas aplicaciones se pueden ejecutar por separado o bien a travs de la clase Pruebas que nos permite decidir qu aplicacin ejecutar. Los ejemplos de uso son, creo, bastante intuitivos, sin embargo, hay algunos trucos poco evidentes:
En la aplicacin DNIFieldTest, no hace falta escribir siempre la letra del DNI, si se han entrado todos los nmeros del DNI, basta con pulsar Ctrl-Espacio y la letra correcta aparecer por arte de magia. Si lo que sucede es que la letra entrada es incorrecta, tambin se puede recurrir a Ctrl-Espacio para que se cambie por la correcta. En la aplicacin DateFieldTest, despus de especificar un nuevo patrn, podemos activarlo pulsando el botn OK o bien pulsando intro en el campo de patrn. Tambin es importante tener en cuenta que en el campo Aadir, podemos especificar cantidades negativas para que reste. En los ejemplos IntegerFieldTest y DNIFieldTest, la combinacin de teclas <ctrl-v>, cuando el foco est en el campo, abren un dilogo que muestra el valor del campo.
19 de 24
Arquitectura de clases
La siguiente figura nos muestra el diagrama de clases de los componentes de ejemplo derivados de JformattedTextField. Tngase en cuenta que el diagrama no incluye todos los mtodos.
20 de 24
21 de 24
EPLOGO
Es notable el aparente cambio de estrategia de Sun proporcionando en la versin 1.4 de Java dos nuevos componentes Swing que intentan cubrir vacos importantes en lo que al desarrollo de interficies grficas se refiere. Lamentablemente, la versin 5.0 no incluye ningn componente nuevo ni mejora los anteriores. JFormattedTextField nos permite desarrollar aplicaciones ms profesionales y mejora la imagen de Swing. Tenemos mscaras, campos de fecha con un comportamiento razonable, podemos usar diversos formateadores para personalizar nuestros campos, etc. Sin embargo, durante el tiempo que me ha llevado construir este artculo, he encontrado algunos obstculos que me han dificultado la labor de escribir tanto el texto del artculo como los componentes de ejemplo. He encontrado, y es una opinin personal, problemas de ortogonalidad, algunos de los cuales ya he expuesto, como la diferencia de comportamiento de commitEdit() y setValue() que, combinados con la admisin o no de literales en el valor del MaskFormatter, me han dado algn que otro quebradero de cabeza. En el mismo orden de cosas estara el establecimiento de rangos de InternationalFormatter. Siguiendo con la ortogonalidad, me pregunto dnde est el model de JformattedTextField? Ciertamente, sigue siendo el mismo que el de su padre, JTextField, un PlainDocument. Sin embargo, las relaciones entre vista/controlador (delegate) y modelo, ni son tan claras como en JTextField ni se cuentan en parte alguna. Me he encontrado tambin con problemas de visibilidad (scope) cuando he intentado extender, por ejemplo, DefaultFormatter. Hay mtodos importantes de DefaultFormatter y de JTextComponent que slo estn visibles a nivel de package y que dificultan extender tanto DefaultFormatter como AbstractFormatter. Es realmente complejo extender los formateadores que nos vienen dados. En el proceso de creacin de este artculo, me propuse reproducir un componente que haba desarrollado hace tiempo como una extensin de JTextField y que ofreca una funcionalidad sencilla pero prctica: determinar el nmero mximo de caracteres que poda aceptar un campo de entrada. Ante la dificultad de extender DefaultFormatter, decid ir directamente a PlainDocument y atacar el modelo como hice anteriormente. Bien, no acab de funcionar. El mtodo insertString() de AbstractDocument no se invoca al insertar una tira (por teclado o desde el clipboard) como en JTextField, sino cuando el campo cambia de foco. La falta de tiempo y las dificultades han hecho que abandonara esta lnea. Finalmente, y para la versin 1.1 de este artculo, he desarrollado StringField, pero recurriendo al control de la propiedad value, ya que las otras vas, a mi entender ms coherentes, han resultado imposibles de seguir (posiblemente por mis limitaciones personales). A pesar de los pesares, creo que JFormattedTextField es un componente importante que debe formar parte, de manera habitual, en nuestras aplicaciones. Deseo que el lector pueda, con la ayuda de este artculo, sortear mejor que yo las dificultades de creacin de componentes derivados de JFormattedTextField y que este artculo contribuya a hacer un mejor y mayor uso de Swing en sus aplicaciones de escritorio.
22 de 24
QU HE USADO?
He usado Eclipse 3.0.1 (http://www.eclipse.org) para el desarrollo, la generacin de Javadocs y las pruebas unitarias con JUnit 3.8.1 (http://www.junit.org). Para el desarrollo de las interficies de usuario en Swing, he usado el Visual Editor de Eclipse (http://www.eclipse.org/vep/) en su versin 1.0.2.1RC2. Para la generacin del diagrama de clases de los componentes, he usado la ltima versin del plugin de Eclipse Omondo EclipseUML (http://www.omondo.com/index.html). Este artculo ha sido escrito con OpenOffice 2.0 beta (http://www.openoffice.org/) y ste tambin se ha usado para la generacin del PDF.
QU HE LEDO?
Realmente, hay poca literatura que haga referencia a JformattedTextField. Yo slo he encontrado un par de tutoriales que cubren los aspectos ms bsicos. El primero, siempre es una referencia, es el captulo How to Use Formatted Text Fields (http://java.sun.com/docs/books/tutorial/uiswing/components/formattedtextfield.html) del tutorial oficial de Java. Es correcto, pero creo que insuficiente si quieres trabajar a fondo con las posibilidades de JformattedTextField. Expone con claridad algunos conceptos bsicos. El segundo, es el artculo de John Zukowski Swing's new JFormattedTextField component (http://www-106.ibm.com/developerworks/java/library/j-mer0625/) de junio de 2002 dentro de la interesante serie de artculos sobre novedades de la versin 1.4 de Java Magic with Merlin que el autor ha publicado en developerWorks. Bien escrito pero muy bsico. Como he comentado ms arriba, para entender el funcionamiento de JFormattedTextField, he tenido que leer mucha API y bastante cdigo fuente.
El archivo LEEME.TXT que explica, como aqu, qu contiene el directorio y cmo se usa. Este artculo en formato PDF. El archivo jftf.jar que contiene los componentes de ejemplo, las aplicaciones de demostracin y los tests unitarios. El subdirectorio src que contiene el cdigo fuente. El subdirectorio bin con las classes compiladas. El subdirectorio doc que contiene los javadoc de todas las clases.
o bien
java -jar jftf.jar
desde el directorio [...]\JFTF creado al descomprimir el archivo ZIP del artculo. Si el lector est usando un entorno Windows, puede, simplemente, hacer doble clic sobre el archivo jftf.jar.
24 de 24