Introducción a componentes Swing H. Tejeda Mayo 2016 Índice 1. Introducción 1 2. Clase JFrame 2 3. Clase JLabel 6 4. Manejador de diseño 9 5. Extensión de la clase JFrame 11 6. Clases de entrada 13 7. Programación de manejo de eventos 17 8. Receptores de eventos 21 9. Clases para selección 23 1. Introducción Las aplicaciones son más amigables al usuario cuando estas contienen componentes interfaz de usuario o UI (User Interface). Los componentes UI son botones, campos de texto, y otros con los cuales el usuario puede interactuar. Los creadores de Java han empacado una cantidad de componentes preescritos en el paquete Swing. Los componentes Swing son elementos UI tales como cuadros de diálogo y botones; se reconocen porque sus nombres inician con J. Nota. Los componentes Swing fueron nombrados por un estilo musical que fue popular allá en 1940. El nombre implica que los componentes tienen estilo y dinamismo. Las clases Swing son parte de un 1 conjunto más general de capacidades de programación UI que son llamadas las clases de fundación Java o JFC (Java Foundation Classes). JFC incluye las clases componentes Swing y algunas clases del paquete java.awt. Nota. En las primeras versiones de Java, los componentes tenı́an nombres simples, tales como Frame y Button. Los componentes no tenı́an una apariencia consistente cuando se usaban con diferentes navegadores y sistemas operativos. Cuando los programadores de Java diseñaron clases nuevas y mejoradas necesitaron nuevos nombres para las clases, ası́ que usaron una J precediendo cada nuevo nombre de clase. Por lo tanto, los componentes Swing tienen nombres como JFrame, JButton, JScrollbar, JOptionPane, etc. Los componentes UI son también llamados controles o widgets (reproductores). Cada componente Swing es un descendiente de un JComponent, el cual a su vez hereda desde la clase java.awt.Container. Para usar los componentes UI Swing y sus métodos se debe insertar la sentencia import javax.swing.* al inicio del programa, de esta forma se importa el paquete. La x en el nombre del paquete representa la extensión de las especificaciones del lenguaje Java original. Nota. Casi todos los componentes Swing se dice que son componentes peso ligero porque están escritos completamente en Java y no descansan en el código del sistema operativo local. Esto significa que los componentes no son “sobrecargados” teniendo que interactuar con el sistema operativo. Algunos componentes Swing, como JFrame, se conocen como componentes peso pesado porque requieren interactuar con el sistema operativo local. Un componente ligero reusa la ventana nativa de su ancestro más cercano peso pesado; un componente peso pesado tiene su propia ventana nativa opaca. Los únicos componentes peso pesado usados en Swing son JFrame, JDialog, JWindow, JApplet, awt.Component, awt.Container, y JComponent. Cuando se usan componentes Swing estos son puestos, generalmente, en contenedores. Un contenedor es un tipo de componente que guarda otros componentes para que este grupo pueda ser tratado como una sola entidad. Los contenedores están definidos en la clase Container. Frecuentemente, un contenedor toma la forma de una ventana que puede ser arrastrada, redimensionada, minimizada, restaurada, y cerrada. La clase Component es una hija de la clase Object, y la clase Container es una hija de la clase Component. Por lo tanto, cada objeto Container “es un” Component, y cada objeto Component “es un” Object. La clase Window es una hija de Container. Los programadores de Java usan poco los objetos Window porque la subclase Frame de Window y su subclase JFrame componente Swing, permiten crear objetos más útiles. Los objetos Window no tienen barra de tı́tulo, ni bordes. 2. Clase JFrame Se crea un JFrame para poner otros objetos dentro para ser mostrados. Enseguida se muestra el árbol de herencia de la clase JFrame para mostrar la relación con sus ancestros. java.lang.Object 2 | +--java.awt.Component | +---java.awt.Container | +---java.awt.Window | +---java.awt.Frame | +---javax.swing.JFrame La clase JFrame tiene cuatro constructores: JFrame() construye un nuevo marco que inicialmente es invisible y no tiene tı́tulo. JFrame(String tı́tulo) crea un nuevo JFrame inicialmente invisible con el tı́tulo indicado. JFrame(GraphicsConfiguration gc) crea un JFrame en el GraphicsConfiguration de un dispositivo de pantalla sin tı́tulo. JFrame(String tı́tulo, GraphicsConfiguration gc) crea un JFrame con el tı́tulo especificado y el GraphicsConfiguration dado de una pantalla. Se construye un JFrame como cualquier otro objeto, usando el nombre de la clase, un identificador, el operador de asignación, el operador new, y una llamada al constructor. Las siguientes dos sentencias construyen dos JFrame: uno con el tı́tulo “Hola” y otro sin tı́tulo. JFrame primerMarco = new JFrame("Hola"); JFrame segundoMarco = new JFrame(); Ya teniendo los objetos JFrame creados, se pueden usar algunos de los métodos útiles dados en el cuadro 1. Suponiendo que se ha declarado un JFrame llamado primerMarco, se pueden usar las siguientes sentencias para poner el tamaño del objeto primerMarco a 250 pı́xeles horizontalmente por 100 verticalmente y pone el tı́tulo del JFrame para mostrar un argumento String. Los pı́xeles son los elementos de imagen, o pequeños puntos de luz, que hacen la imagen en el monitor de la computadora. primerMarco.setSize(250, 100); primerMarco.setTitle("Mi marco"); Cuando se pone el tamaño de un JFrame, no se tiene el área completa disponible para usarla porque parte del área es usada por la barra de tı́tulo y los bordes del JFrame. La aplicación JFrame1, código 1, muestra una aplicación que crea un JFrame pequeño y vacı́o. 3 Método void setTitle(String) void setSize(int, int) void setSize(Dimension) String getTitle() void setResizable(boolean) boolean isResizable() void setVisible(boolean) void setBounds(int, int, int, int) Descripción Pone el tı́tulo del JFrame usando el argumento String. Fija el tamaño de un JFrame en pı́xeles con el ancho y alto como argumento. Fija el tamaño de un JFrame usando un objeto Dimension; el constructor Dimension(int, int) crea un objeto que representa un ancho y una altura. Regresa el tı́tulo de un JFrame. Pone el JFrame para que sea redimensionable pasando true al método, con false es no redimensionable. Regresa true o false para indicar si el JFrame es redimensionable. Hace un JFrame visible usando el argumento booleano true e invisible usando el argumento false. Sobreescribe el comportamiento por defecto para que el JFrame sea posicionado en la esquina superior izquierda en la pantalla del escritorio de la computadora; los primeros dos argumentos son las posiciones horizontal y vertical de la esquina superior izquierda del JFrame en el escritorio, y los dos argumentos finales fijan el ancho y la altura. Cuadro 1: Métodos útiles heredados por la clase JFrame. 4 1 2 3 4 5 6 7 8 import j a v a x . swing . ∗ ; public c l a s s JFrame1 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { JFrame unMarco = new JFrame ( ” Primer Marco” ) ; unMarco . s e t S i z e ( 2 5 0 , 1 0 0 ) ; unMarco . s e t V i s i b l e ( true ) ; } } Código 1: Aplicación JFrame1. La aplicación JFrame1, código 1, crea un JFrame. Este se parece a marcos que se han visto cuando se usan programas UI diferentes. La razón para usar objetos marco similares es porque los usuarios ya están familiarizados con el ambiente de marcos. Cuando los usuarios ven marcos esperan ver una barra de tı́tulo en la cima con información de texto. También esperan ver los botones de minimizar, maximizar o restaurar, y cerrar en alguna de la esquinas superiores del marco. Muchos usuarios suponen que pueden cambiar el tamaño del marco arrastrando sus bordes o reposicionar la ventana en la pantalla arrastrando la barra de titulo de la ventana a una nueva localidad. En la aplicación del código 1, las tres sentencias en el método main() son importantes. Después de instancia unMarco, se necesita hacer setVisible(true) para poder ver el JFrame, y también poner su tamaño ya que de otra forma solo la barra de tı́tulo del JFrame es visible porque el tamaño de este es 0 x 0 por defecto. Una razón por la cual el JFrame es invisible es porque este se podrı́a construir en segundo plano mientras otras acciones están ocurriendo y quizás se desearı́a hacer visible más tarde. Algunos programadores usan el método show() en vez del método setVisible(). Cuando un usuario cierra un JFrame pulsando en el botón cerrar en alguna de la esquinas superiores, el comportamiento por defecto para el JFrame es ocultarse y para la aplicación continuar ejecutándose. Esto tiene sentido cuando hay otras tareas por completar para el programa después de que el marco principal fue cerrado—por ejemplo, mostrar marcos adicionales, cerrar archivos abiertos de datos, o imprimir un reporte de actividad. Cuando un JFrame sirve como una aplicación de Swing se quiere que el programa termine cuando el usuario pulsa el botón cerrar. Para cambiar el comportamiento se puede llamar al método setDefaultCloseOperation() de JFrame con alguno de los siguientes cuatro valores como argumento: JFrame.EXIT ON CLOSE termina el programa cuando el JFrame es cerrado. WindowConstants.DISPOSE ON CLOSE cierra el marco, dispone el objeto JFrame, y mantiene en ejecución la aplicación. WindowConstants.DO NOTHING ON CLOSE mantiene el JFrame abierto y continúa ejecutándose. En otras palabras, deshabilita el botón cerrar. WindowConstants.HIDE ON CLOSE cierra el JFrame y continúa ejecutándose; este es el comportamiento por defecto. Cuando se ejecuta una aplicación en cual se ha olvidado salir cuando el JFrame es cerrado, se puede terminar el programa tecleando CTRL+C. 5 Personalización de la apariencia de un JFrame La apariencia de un JFrame es dada por el sistema operativo en el cual el programa esté ejecutándose. El diseño de los elementos que controlan el marco se parecen y se comportan como lo hace cualquier otra aplicación gráfica. Los botones y el ı́cono, en el caso de que se muestre, son conocidos como decoraciones ventana; por defecto las decoraciones ventana son proporcionadas por el sistema operativo. Se puede pedir que el mirar y sentir de Java dé las decoraciones para un marco. Un mirar y sentir es la apariencia y comportamiento por defecto de cualquier interfaz de usuario. Opcionalmente, se puede poner el mirar y sentir de un JFrame usando el método setDefaultLookAndFeelDecorated(). La aplicación JFrame2, código 2, hace una llamada a este método en la lı́nea 4. 1 2 3 4 5 6 7 8 9 import j a v a x . swing . ∗ ; public c l a s s JFrame2 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { JFrame . s e t D e f a u l t L o o k A n d F e e l D e c o r a t e d ( true ) ; JFrame unMarco = new JFrame ( ” Segundo Marco” ) ; unMarco . s e t S i z e ( 2 5 0 , 1 0 0 ) ; unMarco . s e t V i s i b l e ( true ) ; } } Código 2: La clase JFrame2. Puede ser que al usar el método setDefaultLookAndFeelDecorated() el sistema operativo impida modificar su apariencia, como en Mac OS X. Actividad 1. Escribir una aplicación gráfica que muestre en el tı́tulo de la ventana su nombre y tenga un tamaño cuadrado. Además la aplicación debe terminar cuando se cierre el marco. 3. Clase JLabel Uno de los componentes que se podrı́a poner en un JFrame es un JLabel. JLabel es una clase Swing incorporada que tiene texto que se quiere mostrar. La jerarquı́a de herencia de la clase JLabel se muestra enseguida: java.lang.Object | +--java.awt.Component | +---java.awt.Container | +---javax.swing.JComponent | +---javax.swing.JLabel 6 Los constructores para la clase JLabel incluyen los siguientes: JLabel() crea una instancia JLabel sin imagen con una cadena vacı́a para el tı́tulo. JLabel(Icon imagen) crea una instancia JLabel con la imagen especificada. JLabel(Icon imagen, int alineaciónHorizontal) crea una instancia JLabel con la imagen especificada y la alineación horizontal. JLabel(String texto) crea una instancia JLabel con el texto especificado. JLabel(String texto, Icon imagen, int alineaciónHorizontal) crea una instancia JLabel con el texto, la imagen, y la alineación horizontal especificados. JLabel(String texto, int alineaciónHorizontal) crea una instancia JLabel con el texto y la alineación horizontal indicados. Se puede crear un JLabel llamado saludo que tenga las palabras “Buen dı́a” con la siguiente sentencia: JLabel saludo = new JLabel("Buen dı́a"); Para agregar el objeto saludo al objeto JFrame llamado unMarco usando el método add() como sigue: unMarco.add(saludo); La aplicación JFrame3, código 3, muestra la creación de JFrame al que enseguida se le pone su tamaño y la operación de cierre. Luego un JLabel es creado y agregado al JFrame. 1 2 3 4 5 6 7 8 9 10 11 12 13 import j a v a x . swing . ∗ ; public c l a s s JFrame3 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { f i n a l int ANCHO MARCO = 2 5 0 ; f i n a l int ALTO MARCO = 1 0 0 ; JFrame unMarco = new JFrame ( ” T e r c e r Marco” ) ; unMarco . s e t S i z e (ANCHO MARCO, ALTO MARCO) ; unMarco . s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT ON CLOSE ) ; JLabel s a l u d o = new JLabel ( ”Buen dı́a ” ) ; unMarco . add ( s a l u d o ) ; unMarco . s e t V i s i b l e ( true ) ; } } Código 3: La clase JFrame3. La contraparte del método add() es el método remove(). La siguiente sentencia quita saludo de unMarco: 7 unMarco.remove(saludo); Si se agrega, o quita un componente de un contenedor después de que este se hizo visible, se deberı́a también llamar los métodos invalidate(), validate() y repaint() para ver los resultados de las acciones. Cada uno realiza funciones ligeramente diferentes pero las tres juntas garantizan que el resultado de cambios en el diseño tomarán efecto. Los métodos invalidate() y validate() son parte de la clase Container, y repaint() es parte de la clase Component. Nota. Si se agrega o quita un componente en un objeto JFrame durante la construcción, no se tiene que llamar al método repaint() si después se modifica el componente, como por ejemplo, cambiando el texto. Sólo se necesita llamar a repaint() si se agrega o quita un componente después de la construcción. Se puede cambiar el texto en un JLabel usando el método setText() de la clase Component con el objeto JLabel y pasándole un String. El siguiente código cambia el valor mostrado en el JLabel saludo: saludo.setText("Suerte"); Se puede recuperar el texto en un JLabel, o cualquier otro Component, usando el método getText(), el cual regresa el String guardado actualmente. Cambiar la fuente de un JLabel Java proporciona una clase Font para crear un objeto que guarde el tipo de fuente, y la información del estilo y del tamaño de la fuente. El método setFont() requiere un argumento objeto Font. Para construir un objeto Font, se necesitan tres argumentos: tipo de fuente, estilo, y tamaño en puntos. El argumento tipo de fuente para el constructor Font es un String representando una fuente. Las fuentes comunes tienen nombres como Arial, Century, Monospaced, y Times New Roman. El argumento tipo de fuente es solo una petición; el sistema operativo en el cual el programa se ejecuta podrı́a no tener acceso, y si es necesario, este lo sustituye con una fuente por defecto. El argumento estilo aplica un atributo al texto mostrado y es uno de tres valores: Font.PLAIN, Font.BOLD, o Font.ITALIC. El argumento tamaño en puntos es un entero que representa aproximadamente 1/72 de una pulgada. El texto impreso es generalmente de 12 puntos; una cabecera podrı́a ser de 30 puntos. Nota. En impresión, el tamaño de punto define una medida entre lı́neas de texto en un documento de texto con interlineado simple. Java adopta la convención de que un punto en una pantalla es equivalente a una unidad en las coordenadas del usuario. 8 Para dar a un objeto JLabel una nueva fuente, se puede crear un objeto Font, como en el siguiente: Font fuenteCabecera = new Font("Monospaced", Font.BOLD, 36); Luego se usa el método setFont() para asignar el objeto Font al objeto JLabel con una sentencia como la siguiente: saludo.setFont(fuenteCabecera); La aplicación JFrame4, código 4, muestra en las lı́neas 2, 3, 7, 8, y 12 los cambios que se hicieron a JFrame3 para usar otro tipo de fuente con diferente tamaño y apariencia. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import j a v a x . swing . ∗ ; import j a v a . awt . ∗ ; public c l a s s JFrame4 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { f i n a l int ANCHO MARCO = 2 5 0 ; f i n a l int ALTO MARCO = 1 0 0 ; Font f u e n t e = new Font ( ” A r i a l ” , Font .BOLD, 3 6 ) ; JFrame unMarco = new JFrame ( ” Cuarta Ventana ” ) ; unMarco . s e t S i z e (ANCHO MARCO, ALTO MARCO) ; unMarco . s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT ON CLOSE ) ; JLabel s a l u d o = new JLabel ( ”Buen dı́a ” ) ; saludo . setFont ( fuente ) ; unMarco . add ( s a l u d o ) ; unMarco . s e t V i s i b l e ( true ) ; } } Código 4: La clase JFrame4. No es obligatorio proporcionar un identificador para un objeto Font. Se pudo omitir la lı́nea 7 en el código 4 y poner la fuente en saludo con la siguiente sentencia, la cual usa un objeto Font anónimo: saludo.setFont(new Font("Arial", Font.BOLD, 36)); Después de crear un objeto Font, se puede crear un nuevo objeto con un tipo y tamaño diferente usando el método deriveFont() con los argumentos apropiados. Por ejemplo, las siguientes dos sentencias crean el objeto fuenteCabecera y el fuenteCuerpoTexto que está basado en el primer objeto: Font fuenteCabecera = new Font("Arial", Font.BOLD, 36); Font fuenteCuerpoTexto = fuenteCabecera.deriveFont(Font.PLAIN, 14); 4. Manejador de diseño Cuando se quiere agregar múltiples componentes a un JFrame u otro contenedor, usualmente se requiere proporcionar instrucciones para la colocación de los componentes. En la aplicación JFrame5, 9 código 5, dos JLabel son creados y agregados a un JFrame en las sentencias de las lı́neas 11—12. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import j a v a x . swing . ∗ ; public c l a s s JFrame5 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { f i n a l int ANCHO MARCO = 2 5 0 ; f i n a l int ALTO MARCO = 1 0 0 ; JFrame unMarco = new JFrame ( ” Quinta Ventana ” ) ; unMarco . s e t S i z e (ANCHO MARCO, ALTO MARCO) ; unMarco . s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT ON CLOSE ) ; JLabel s a l u d o = new JLabel ( ” Hola ” ) ; JLabel s a l u d o 2 = new JLabel ( ”¿Quién e r e s ? ” ) ; unMarco . add ( s a l u d o ) ; unMarco . add ( s a l u d o 2 ) ; unMarco . s e t V i s i b l e ( true ) ; } } Código 5: La clase JFrame5. Cuando se ejecuta la aplicación JFrame5 sólo la última JLabel que fue agregada es visible, a pesar de que se agregaron dos etiquetas al marco. La segunda JLabel fue puesta encima de la primera, tapándola completamente. Si se continúan agregando más JLabel en el programa, sólo la última agregada al JFrame será visible. Para colocar múltiples componentes en posiciones especificadas en un contenedor de tal forma que no se oculten entre ellas, se debe usar explı́citamente un manejador de diseño, una clase que controla el posicionamiento de componentes. El comportamiento por defecto de un JFrame es dado por un manejador llamado BorderLayout. Un manejador BorderLayout divide un contenedor en regiones. Cuando no se indica una región en la cual un componente se coloca, este se coloca siempre en el centro, y si estaba otro componente, lo tapa. Al agregar componentes usando el manejador FlowLayout, estos son colocados en un renglón, y cuando el renglón se llena, los componentes automáticamente se ponen en el siguiente renglón. Tres constantes están definidas en la clase FlowLayout para indicar como los componentes son posicionados en cada renglón de su contenedor. Estas son FlowLayout.LEFT, FlowLayout.RIGHT, y FlowLayout.CENTER. Para crear un manejador de diseño llamado flujo que posicione los componentes a la derecha, se usa la siguiente sentencia: FlowLayout flujo = new FlowLayout(FlowLayout.RIGHT); En caso de no indicar como los componentes son distribuidos, por defecto estos son centrados en cada renglón. Una vez que el manejador de diseño ha sido creado entonces puede ser puesto al objeto JFrame haciendo lo siguiente: unMarco.setLayout(flujo); Se puede compactar el código usando un objeto FlowLayout anónimo con: 10 unMarco.setLayout(new FlowLayout(FlowLayout.RIGHT)); La aplicación JFrame6, código 6, ha puesto el manejador de diseño del JFrame para que componentes múltiples sean visibles. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import j a v a x . swing . ∗ ; import j a v a . awt . ∗ ; public c l a s s JFrame6 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { f i n a l int ANCHO MARCO = 2 5 0 ; f i n a l int ALTO MARCO = 1 0 0 ; JFrame unMarco = new JFrame ( ” Sexta Ventana ” ) ; unMarco . s e t S i z e (ANCHO MARCO, ALTO MARCO) ; unMarco . s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT ON CLOSE ) ; JLabel s a l u d o = new JLabel ( ” Hola ” ) ; JLabel s a l u d o 2 = new JLabel ( ”¿Quién e r e s ? ” ) ; unMarco . s e t L a y o u t (new FlowLayout ( FlowLayout . RIGHT ) ) ; unMarco . add ( s a l u d o ) ; unMarco . add ( s a l u d o 2 ) ; unMarco . s e t V i s i b l e ( true ) ; } } Código 6: La clase JFrame6. Al ejecutar la aplicación JFrame6 se deben ver los dos JLabel, uno al lado del otro porque se usa un FlowLayout. Si hubiera más JLabel u otros componentes, estos serı́an colocados continuamente lado por lado a través del JFrame hasta que no haya más espacio. 5. Extensión de la clase JFrame Se puede instanciar un objeto JFrame simple dentro del método main() de una aplicación o en cualquier otro método de cualquier clase que sea escrita. Alternativamente, se puede crear su propia clase que descienda de la clase JFrame. La ventaja de crear una clase hija desde JFrame es la posibilidad de poder poner las propiedades del JFrame dentro del constructor de objetos; luego, cuando se crea su objeto hijo JFrame, este es dotado automáticamente con las caracterı́sticas que se hayan especificado, tales como el tamaño, el tı́tulo, y la operación de cierre por defecto. Para crear una clase hija se usa la palabra reservada extends en la cabecera de la clase, seguido por el nombre de la clase padre. Para llamar el constructor de la clase padre se usa la palabra reservada super(), y deberá ser la primera sentencia en el constructor de la clase hija. La clase JMiMarco, código 7, extiende a JFrame. En el constructor de JMiMarco, el constructor super() JFrame es llamado; este acepta un argumento String para usarlo como el tı́tulo del JFrame. El constructor de JMiMarco también fija el tamaño, la operación de cierre por defecto y la visibilidad para cada JMiMarco. Cada uno de los métodos—setSize(), setDefaultCloseOperation(), y setVisible—aparecen en el constructor sin un objeto, porque el objeto es el JMiMarco actual siendo construido. 11 1 2 3 4 5 6 7 8 9 10 11 import j a v a x . swing . ∗ ; public c l a s s JMiMarco extends JFrame { f i n a l int ANCHO = 2 0 0 ; f i n a l int ALTO = 1 2 0 ; public JMiMarco ( ) { super ( ”Mi marco ” ) ; s e t S i z e (ANCHO, ALTO) ; s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT ON CLOSE ) ; s e t V i s i b l e ( true ) ; } } Código 7: La clase JMiMarco. Cuando se ejecuta la aplicación CrearDosObjetosMiMarco, código 8, los dos objetos JMiMarco son mostrados con el segundo encima del primero. Para ver el primer marco se debe arrastrar el segundo marco. 1 2 3 4 5 6 public c l a s s CrearDosObjetosMiMarco { public s t a t i c void main ( S t r i n g [ ] a r g s ) { JMiMarco miMarco = new JMiMarco ( ) ; JMiMarco miSegundoMarco = new JMiMarco ( ) ; } } Código 8: La aplicación CrearDosObjetosMiMarco. Nota. Se podrı́a usar el método setBounds() con uno de los objetos JMiMarco para no tener que arrastrar uno de los objetos JMiMarco para ver el otro. La clase Objeto incluye el método setLocation() que se puede usar con un JFrame. Para usar este método, se deben dar valores para las posiciones horizontal y vertical como argumentos del método. Nota. Se termina la aplicación cuando se pulsa en el botón cerrar de alguno de los dos objetos JMiMarco. Cada objeto tiene la misma operación de cierre por defecto porque cada uno usa el mismo constructor que indica esa operación. Para permitir que sólo un JMiMarco controle la terminación del programa, se podrı́a usar el método setDefaultCloseOperation() con alguno de los objetos en la aplicación para cambiar su comportamiento de cierre, quizás usando DISPOSE ON CLOSE para descartar uno de los marcos pero manteniendo la aplicación en ejecución. Cuando se extiende un JFrame para crear una clase nueva personalizada, se debe recordar tomar decisiones con los atributos que se quieren fijar dentro de la clase y los que se quieren dejar a las aplicaciones que usarán la clase. Por ejemplo, se puede poner la sentencia setVisible() dentro del constructor de la clase hija JFrame, o se puede permitir a la aplicación usar el método setVisible() precedido por el nombre de un objeto instanciado seguido de un punto. Cualquiera trabaja, pero si no se hace ninguna, el marco no será visible. Nota. Algunos programadores ponen un método main() dentro de una clase como JMiMarco. Luego la clase da la opción para ser usada para instanciar objetos, como en la aplicación CrearDosObjetosMiMarco, o para ser usada ejecutándose como un programa que crea un objeto. 12 6. Clases de entrada Un JFrame también contiene otras caracterı́sticas de ventana, tales como JTextField, JButton, e información sobre herramientas (tool tips). Clase JTextField Un JTextField es un componente en el cual un usuario puede teclear una lı́nea de texto. El texto comprende cualquier carácter que se pueda meter del teclado o aplicación, incluyendo números y signos de puntuación. La siguiente figura muestra la jerarquı́a de herencia de la clase JTextField. java.lang.Object | +--java.awt.Component | +---java.awt.Container | +---javax.swing.JComponent | +---javax.swing.text.JTextComponent | +---javax.swing.JTextField En un JTextField un usuario teclea una lı́nea de texto y luego presiona la tecla Intro o pulsa un botón con el ratón para meter los datos. Se puede construir un objeto JTextField usando uno de los varios constructores: JTextField() construye un nuevo JTextField. JTextField(int columnas) construye un nuevo JTextField vacı́o con la cantidad indicada de columnas. JTextField(String texto) construye un nuevo JTextField inicializado con el texto especificado. JTextField(String texto, int columnas) construye un nuevo JTextField inicializado con el texto dado y la cantidad de columnas indicada. Por ejemplo, para tener un JTextField que tenga suficiente espacio para que un usuario puede meter 10 caracteres, se puede codificar lo siguiente: JTextField respuesta = new JTextField(10); Para agregar el JTextField llamado respuesta a un JFrame llamado marco, se indica con: 13 marco.add(respuesta); La cantidad de caracteres que un JTextField puede mostrar depende de la fuente usada y los caracteres tecleados. En la mayorı́a de las fuentes, m es más ancha que i, ası́ un JTextField de tamaño 10 usando la fuente Arial puede mostrar 24 caracteres i, pero sólo 8 caracteres m. Se debe intentar anticipar cuanto caracteres los usuarios podrı́an ingresar cuando se crea un JTextField. El usuario puede meter más caracteres que aquellos que son mostrados, pero los caracteres extra se desplazan fuera de la vista. Puede ser desconcertante intentar meter datos en un campo que no es lo suficientemente grande, ası́ que es mejor sobreestimar que subestimar el tamaño de un campo de texto. Otros métodos están disponibles para ser usados con objetos JTextField. El método setText() permite cambiar el texto en un JTextField, u otro Componet, que ha sido creado, como se muestra enseguida: respuesta.setText("Gracias"); Después de que el usuario ingreso texto en un JTextField, se puede limpiar con una sentencia como la siguiente, la cual asigna una cadena vacı́a al texto: respuesta.setText(""); El método getText() permite recuperar el String de texto en un JTextField, u otro Component, como en: String entradaUsuario = respuesta.getText(); Cuando un JTextField tiene la capacidad de aceptar pulsaciones de teclas, el JTextField es editable. Un JTextField por defecto es editable. Si no se quierer que el usuario pueda ingresar datos en un JTextField, se puede mandar un valor booleano al método setEditable() para cambiar el estado editable de un JTextField. Por ejemplo, si se quiere dar al usuario una cantidad limitada de oportunidades de contestar una pregunta correctamente, se pueden contar los intentos de entrada de datos y entonces prevenir al usuario de reemplazar o editar los caracteres en el JTextField usando una sentencia como: if (intentos > LIMITE) respuesta.setEditable(false ); Clase JButton Un JButton es un Component en el cual el usuario puede pulsar con el ratón para hacer una selección. Hay cinco constructores JButton y son: 14 JButton() crea un botón sin texto. JButton(Icon ı́cono) crea un botón con un ı́cono de tipo Icon o ImageIcon. JButton(String texto) crea un botón con texto. JButton(String texto, Icon icon) crea un botón con el texto inicial y un ı́cono del tipo Icon o ImageIcon. JButton(Action a) crea un botón en el cual sus propiedades son tomadas de la Action proporcionada. La jerarquı́a de herencia de la clase JButton es la siguiente: java.lang.Object | +--java.awt.Component | +---java.awt.Container | +---javax.swing.JComponent | +---javax.swing.AbstractButton | +---javax.swing.JButton Para crear un JButton con la etiqueta “Pulsar cuando esté listo”, se puede escribir lo siguiente: JButton botonListo = new JButton("Pulsar cuando esté listo"); Se puede agregar un JButton a un JFrame usando el método add(). Se puede cambiar la etiqueta de un JButton con el método setText(), como en: botonListo.setText("¡No me presiones nuevamente!"); Se puede recuperar el texto de un JButton y asignarlo a un objeto String con el método getText(), usando: String queEstaEnJButton = botonListo.getText(); La clase JFrameConMuchosComponentes, código 9, extiende JFrame y guarda varios componentes. Como los componentes, dos JLabel, un JTextField y un JButton son agregados al marco, estos son puestos de izquierda a derecha en renglones horizontales en la superficie del JFrame. 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import j a v a x . swing . ∗ ; import j a v a . awt . ∗ ; public c l a s s JFrameConMuchosComponentes extends JFrame { f i n a l int ANCHO = 3 5 0 ; f i n a l int ALTO = 1 5 0 ; public JFrameConMuchosComponentes ( ) { super ( ” Mostrar muchos componentes ” ) ; s e t S i z e (ANCHO, ALTO) ; s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT ON CLOSE ) ; JLabel c a b e c e r a = new JLabel ( ” Este marco t i e n e v a r i o s componentes ” ) ; c a b e c e r a . s e t F o n t (new Font ( ” A r i a l ” , Font .BOLD, 1 6 ) ) ; JLabel mensajeNombre = new JLabel ( ” I n g r e s a r su nombre : ” ) ; J T e x t F i e l d campoNombre = new J T e x t F i e l d ( 1 2 ) ; JButton boton = new JButton ( ” P u l s a r para c o n t i n u a r ” ) ; s e t L a y o u t (new FlowLayout ( ) ) ; add ( c a b e c e r a ) ; add ( mensajeNombre ) ; add ( campoNombre ) ; add ( boton ) ; } } Código 9: La clase JFrameConMuchosComponentes. La aplicación DemoComponentes, código 10, instancia un objeto del tipo JFrameConMuchosComponentes. 1 2 3 4 5 6 7 public c l a s s DemoComponentes { public s t a t i c void main ( S t r i n g [ ] a r g s ) { JFrameConMuchosComponentes marco = new JFrameConMuchosComponentes ( ) ; marco . s e t V i s i b l e ( true ) ; } } Código 10: Aplicación DemoComponentes. Al ejecutar la aplicación DemoComponentes, el JFrame contiene todos los componentes que fueron agregados en el constructor del marco. El usuario puede minimizar o restaurar el marca y puede modificar su tamaño arrastrando los bordes del marco. El usuario puede teclea en el JTextField y pulsar el JButton. Cuando el botón es pulsado, este parece que fue presionado al igual que otros botones usados en diferentes aplicaciones. Sin embargo, cuando el usuario teclea o pulsa el botón, no ocurren acciones resultantes porque no se ha sido escrito todavı́a código para manejar es eventos iniciados por usuario. Uso de información sobre herramientas Información sobre herramientas (tool tips) son ventanas emergentes que pueden ayudar al usuario a entender el propósito de los componentes en una aplicación; la información aparece cuando un usuario flota el apuntador del ratón encima del componente. Se define el texto que será mostrado en una sugerencia usando el método setToolTipText() y pasándole un String apropiado. En la clase JFrameConMuchosComponentes, código 9, se puede agregar una sugerencia al componente 16 boton usando la siguiente sentencia en el constructor JFrame: boton.setToolTipText("Pulsa este botón"); Actividad 2. Crear una aplicación Swing que muestre un JFrame que tenga un JLabel, un JTextField, y un JButton. El diseño de esta aplicación deberá incluir una clase que extienda a la clase JFrame, la cual tendrá los componentes como campos de esta clase con la correspondiente asignación de los objetos instanciados; en el constructor de la clase hija configurar el marco y agregar los componentes. Luego escribir una aplicación que cree un objeto de la clase hija donde se deberá fijar el tamaño de la ventana en 350 de ancho y 100 de alto usando constantes. 7. Programación de manejo de eventos Un evento ocurre cuando un usuario realiza una acción en un componente, tal como pulsar el ratón en un objeto JButton. En un programa de manejo de eventos, el usuario podrı́a iniciar cualquier cantidad de eventos en cualquier orden. Si se usa un programa procesador de textos, se tienen docenas de opciones disponibles en cualquier momento. Se puede teclear texto, seleccionar el texto con el ratón, pulsar un botón para cambiar el texto a negritas, o a itálicas, escoger un elemento del menú, etc. Con cada documento creado se escogen opciones en el orden que parece más apropiado en el momento. El programa procesador de textos deberá estar listo para responder cualquier evento que haya sido iniciado. Dentro de un programa de manejo de eventos, un componente en el cual un evento es generado es la fuente del evento. Un botón que un usuario puede pulsar es un caso de una fuente; un campo de texto donde un usuario puede ingresar texto es otra fuente. Un objeto que está interesado en un evento es un receptor (listener ). No todos los objetos oyen por todos los posibles eventos—como en algunos programas en los cuales se pulsa en diferentes áreas de la pantalla y no sucede nada. Si se quiere que un objeto sea un receptor para un evento, se debe registrar el objeto como un receptor de la fuente. Un objeto fuente componente Java, como un botón, mantiene una lista de receptores registrados y notifica a todos ellos cuando cualquier evento ocurre. Un JFrame podrı́a querer ser notificado de cualquier pulsación en su superficie. Cuando un receptor “recibe las noticias”, un método de manejo de evento contenido en el objeto receptor responde al evento. Nota. Un objeto fuente y un objeto receptor puede ser el mismo objeto. Por ejemplo, se podrı́a programar un JButton para cambiar su propia etiqueta cuando un usuario lo pulse. Para responder a eventos del usuario dentro de alguna clase creada, se debe hacer lo siguiente: Preparar la clase para aceptar mensajes de eventos. Indicar a la clase para que espere eventos que pasarán. Indicar a la clase como responder a los eventos. 17 Preparar la clase para aceptar mensajes de eventos Se prepara la clase para aceptar eventos de pulsación del botón importando el paquete java.awt.event en el programa y agregando la frase implements ActionListener a la cabecera de la clase. En el paquete java.awt.event se incluyen clases evento como ActionEvent, ComponentEvent, y TextEvent. ActionListener es una interfaz—un tipo de clase con un conjunto de especificaciones para métodos que se pueden usar. Implementar ActionListener da especificaciones estándar de métodos de eventos que permiten al receptor trabajar con ActionEvent, el cual es el tipo de evento que ocurre cuando se pulsa un botón. Indicar a la clase para que espere eventos que pasarán Se le dice a una clase que espere eventos ActionEvent con el método addActionListener(). Si se ha declarado un JButton llamado unBoton, y se quiere realizar una acción cuando el usuario pulse unBoton, unBoton es la fuente de un mensaje, y se puede considerar a la clase como un destino al cual se manda este. La referencia this significa “este objeto actual”, ası́ el código unBoton.addActionListener(this); hace que cualquier mensaje ActionEvent (pulsar botón) que venga de unBoton sea enviado a “este objeto actual”. Nota. No todos los eventos son ActionEvent con un método addActionListener(). Por ejemplo, KeyListener tiene un método addKeyListener, y FocusListener tiene un método addFocusListener. Indicar a la clase como responder a los eventos La interfaz ActionListener contiene la especificación del método actionPerformed(ActionEvent e). Cuando una clase, tal como un JFrame, fue registrada como un receptor para un Component, tal como un JButton, y un usuario pulsa el JButton, el método actionPerformed() se ejecuta. Se implementa el método actionPerformed() usando la siguiente cabecera, donde e representa cualquier nombre que se quiera para el evento que inició la notificación al ActionListener, el cual es el JFrame: public void actionPerformed(ActionEvent e) El cuerpo del método contiene cualquier sentencia que se quiera ejecutar cuando la acción ocurre. Se podrı́a querer realizar un cálculo matemático, construir nuevos objetos, generar salida, o ejecutar cualquier otra operación. En la clase JVentanaHola, código 11, un JFrame contiene un JLabel que pide al usuario un nombre, un JTextField en el cual el usuario puede teclear una respuesta, un JButton para pulsar, y una segunda JLabel que muestra el nombre ingresado por el usuario. El método actionPerformed() se ejecuta cuando el usuario pulsa el botón JButton; dentro del método, la cadena que un usuario ha tecleado en el JTextField es recuperada y guardada en la variable nombre. El nombre es luego usado como de un String que altera la segunda JLabel del JFrame. 18 1 2 3 4 j a v a x . swing . ∗ ; j a v a . awt . ∗ ; j a v a . awt . e v e n t . ∗ ; c l a s s JVentanaHola extends JFrame implements A c t i o n L i s t e n e r { JLabel p r e g u n t a = new JLabel ( ”¿Cuá l e s su nombre ? ” ) ; Font fuenteGrande = new Font ( ” A r i a l ” , Font .BOLD, 1 6 ) ; J T e x t F i e l d r e s p u e s t a = new J T e x t F i e l d ( 1 0 ) ; JButton unBoton = new JButton ( ” Pulsa aquı́” ) ; JLabel s a l u d o = new JLabel ( ” ” ) ; f i n a l int ANCHO = 2 7 5 ; f i n a l int ALTURA = 2 2 5 ; public JVentanaHola ( ) { super ( ” Ventana Hola ” ) ; s e t S i z e (ANCHO, ALTURA) ; p r e g u n t a . s e t F o n t ( fuenteGrande ) ; s a l u d o . s e t F o n t ( fuenteGrande ) ; s e t L a y o u t (new FlowLayout ( ) ) ; add ( p r e g u n t a ) ; add ( r e s p u e s t a ) ; add ( unBoton ) ; add ( s a l u d o ) ; s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT ON CLOSE ) ; unBoton . a d d A c t i o n L i s t e n e r ( t h i s ) ; } public void a c t i o n P e r f o r m e d ( ActionEvent e ) { S t r i n g nombre = r e s p u e s t a . getText ( ) ; S t r i n g mensaje = ” Hola ” + nombre ; s a l u d o . s e t T e x t ( mensaje ) ; } import import import public 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 } Código 11: La clase JVentanaHola. La aplicación JDemoHola, código 12, instancia un objeto JVentanaHola y lo hace visible. 1 2 3 4 5 6 public c l a s s JDemoHola { public s t a t i c void main ( S t r i n g [ ] a r g s ) { JVentanaHola ventana = new JVentanaHola ( ) ; ventana . s e t V i s i b l e ( true ) ; } } Código 12: Aplicación JDemoHola. Cuando se ejecuta la aplicación JDemoHola, la cual instancia un objeto JVentanaHola, después de que el usuario ingresa un nombre, el programa lo saluda con el nombre después de que el usuario pulsa el botón. Cuando más de un componente es agregado y registrado a un JFrame, podrı́a ser necesario determinar cual componente fue usado para iniciar un evento. En la clase JVentanaHola, código jventanahola, se podrı́a querer que el usuario pueda ver el mensaje después de pulsar el botón o de presionar la tecla Intro en el JTextField. En ese caso, se podrı́a designar a ambos como fuentes 19 de mensaje usando el método addActionListener() con cada uno, como sigue: unBoton.addActionListener(this); respuesta.addActionListener(this); Estas dos sentencias hacen que el JFrame (this) el receptor de cualquier objeto. El JFrame tiene un sólo método actionPerformed(), ası́ que este es el método que se ejecuta cuando el botón unBoton mande un mensaje o el campo de texto respuesta. Si se quiere que acciones diferentes ocurran dependiendo del generador del evento, se debe determinar la fuente del evento. Dentro del método actionPerformed(), se puede usar el método getSource() del objeto enviado para determinar cual componente generó el evento. Por ejemplo, dentro de un método con la cabecera public void actionPerformed(ActionEvent e), e es un ActionEvent. ActionEvent y otras clases evento son parte del paquete java.awt.event y son subclases de la clase EventObject. Para determinar cual objeto generó el ActionEvent, se usa la siguiente sentencia: Object fuente = e.getSource(); Por ejemplo, si un JFrame contiene dos JButton llamados opcion1 y opcion2, se puede usar la estructura de decisión en el siguiente método para tomar diferentes acciones dependiendo del botón pulsado. Si una fuente de evento es un JButton, JTextField, u otro Component, este puede ser asignado a un Object porque todos los componentes descienden de Object. public void actionPerformed(ActionEvent e) { Object fuente = e.getSource(); if (fuente == opcion1) // ejecutar estas sentencias cuando el usuario pulse opcion1 else // ejecutar estas sentencias cuando el usuario pulse opcion2 } Alternativamente, se puede también usar la palabra reservada instanceof para determinar la fuente del evento. instanceof es usada cuando es necesario conocer sólo el tipo del componente, en vez del componente que disparó el evento. Por ejemplo, si se quiere tomar alguna acción cuando un usuario ingresa datos en cualquier JTextField, pero no cuando un evento es generado por un tipo diferente de Component, se usa el formato de método mostrado enseguida: void actionPerformed(ActionEvent e) { Object fuente = e.getSource(); if (fuente instanceof JTextField) { // ejecutar las sentencias cuando cualquier // JTextField genera el evento, pero no cuando // un JButton u otro Component lo hace. } } 20 Método setEnabled() Cuando se usan aplicaciones hay ocasiones en las cuales un componente se deshabilita o deja de ser usable. Por ejemplo, un JButton podrı́a oscurecerse y deja de responder cuando el programador no quiera que se tenga acceso a este. Los componentes están habilitados por defecto, pero se puede usar el método setEnabled() para hacer un componente disponible o no pasando true o false a este, respectivamente. Actividad 3. Agregar funcionalidad al JButton y al JTextField de la actividad 6. Al pulsar el botón o presionar la tecla Intro se deberá cambiar el texto de la etiqueta para que muestre un mensaje de agradecimiento y para que el botón muestre la cadena “Hecho”. Actividad 4. Repetir la actividad anterior pero ahora sólo se modificará el texto de la etiqueta para que indique quien fue la fuente del evento, si el botón o el campo de texto. 8. Receptores de eventos Varios tipos de receptores existen en Java, y cada uno de estos puede manejar un tipo de evento especı́fico. Una clase puede implementar tantos receptores como ocupe—por ejemplo, una clase podrı́a requerir responder a la pulsación de un botón del ratón y a un teclazo, ası́ que se deberán implementar las interfaces ActionListener y KeyListener. El cuadro 2 lista algunos receptores de eventos y los tipos de eventos para los cuales son usados. Receptor ActionListener AdjustmentListener Tipos de eventos Eventos de acción Eventos de ajuste ChangeListener FocusListener Eventos de cambio Eventos del foco del teclado ItemListener Eventos de elementos KeyListener MouseListener MouseMotionListener Eventos Eventos Eventos ratón Eventos WindowListener del teclado del ratón de movimiento del Ejemplo Pulsar botón Mover barra de desplazamiento Reposicionar deslizador Gana o pierde el foco un campo de texto Cambiar estado de casilla de verificación Ingresar texto Pulsar botón del ratón Mover ratón de la ventana Cerrar ventana Cuadro 2: Lista de algunos receptores de eventos Un evento ocurre cada vez que un usuario teclea o pulsa un botón del ratón. Cualquier objeto puede ser notificado de un evento siempre y cuando este implemente la interfaz apropiada y sea registrado como un receptor de eventos en la fuente de eventos apropiada. Previamente, en la sección 6, se ha mostrado como establecer una relación entre un JButton y un JFrame que lo contiene usando el método addActionListener(). De igual forma, se pueden crear relaciones entre otros componentes Swing y las clases que reaccionan a las manipulaciones de usuarios de ellos. En el cuadro 3, cada componente listado en la columna izquierda está asociado con un método de la columna derecha. 21 Por ejemplo, cuando se quiere que un JCheckBox responda a las pulsaciones del usuario, se puede usar el método addItemListener() para registrar el JCheckBox como el tipo de objeto que puede crear un evento ItemEvent. El argumento que se pone dentro de los paréntesis de la llamda al método addItemListener() es el objeto que deberı́a responder al evento—quizás un JFrame que contiene el JCheckBox generador del evento. El formato es: laFuenteDelEvento.addListenerMetodo(laClaseQueDeberáResponder); Componente(s) Método(s) registradoresreceptores asociados addActionListener() JButton, JCheckBox, JComboBox, JTextField, y JRadioButton. JScrollBar Todos los componentes Swing. addAdjustmentListener() addFocusListener(), addKeyListener(), addMouseListener(), y addMouseMotionListener() addItemListener() JButton, JCheckBox, JComboBox, y JRadioButton. Todos los componentes JWindow y JFrame. JSlider y JCheckBox. addWindowListener() addChangeListener() Cuadro 3: Algunos componentes Swing y su métodos registradores-receptores asociados Nota. Cualquier fuente de eventos puede tener múltipes receptores registrados en este.Es decir, una sola instancia de JCheckBox podrı́a generar eventos ItemEvent y FocusEvent, y una sola instancia de la clase JFrame podrı́a responder a los ActionEvent generados por el JButton y a los ItemEvents generados por el JCheckBox. La clase del objeto que responde a un evento debe contener un método que acepte el objeto evento creado por la acción del usuario. Un método que se ejecuta porque es llamado automáticamente cuando un evento apropiado ocurre es un manejador de evento. Es decir, cuando se registra un componente, tal como un JFrame, para ser un receptor para eventos generados por otro componente, como un JCheckBox, se debe escribir un método manejador de evento. No se puede escoger un nombre propio para los manejadores de evento—identificadores de métodos especı́ficos reaccionan a tipos especı́ficos de eventos. En el cuadro 4 se listan algunos de los métodos que reaccionan a eventos. Receptor ActionListener AdjustmentListener FocusListener ItemListener Método actionPerformed(ActionEvent) adjustmentValueChanged(AdjustmentEvent) focusGained(FocusEvent) y focusLost(FocusEvent) itemStateChanged(ItemEvent) Cuadro 4: Métodos seleccionados que responden a eventos. Las siguientes tareas se deben realizar cuando se declara una clase que maneja un evento: 22 La clase que maneja un evento deberá implementar una interfaz receptora o extender una clase que implemente una interfaz receptora. Por ejemplo, si un JFrame llamado MiMarco necesita responder a pulsaciones del usuario en un JCheckBox, se podrı́a escribir la siguiente cabecera de clase: public class MiMarco extends JFrame implements ItemListener Si después se declara una clase que extienda MiMarco, no se necesita incluir implements ItemListener en su cabecera. La nueva clase hereda la implementación. Se debe registrar cada instancia de la clase manejadora de eventos como un receptor para uno o más componentes. Por ejemplo, si MiMarco contiene un JCheckBox llamado miCheckBox, entonces dentro de la clase MiMarco se podrı́a codificar: miCheckBox.addItemListener(this); La referencia this es a la clase en la cual miCheckBox está declarado—en este caso, MiMarco. Se debe escribir un método manejador de eventos con un identificador apropiado, como se muestra en el cuadro 4, que acepte el evento generado y reaccione a este. 9. Clases para selección Otros componentes permiten al usuario hacer selecciones en un ambiente interfaz de usuario, como JCheckBox, ButtonGroup, y JComboBox. Clase JCheckBox Un JCheckBox es una casilla de verificación que consiste de una etiqueta puesta a un lado de un cuadro; se puede pulsar el cuadro para mostrar una palomita o quitarla. Se usa un JCheckBox para permitir al usuario prender o apagar una opción. La aplicación DemoCheckBox, código 13, muestra el uso de cuatro JCheckBox. 23 1 2 3 4 j a v a . awt . ∗ ; j a v a x . swing . ∗ ; j a v a . awt . e v e n t . ∗ ; c l a s s DemoCheckBox extends JFrame implements I t e m L i s t e n e r { JLabel e t i q u e t a = new JLabel ( ”¿Qué d e s e a s tomar ? ” ) ; JCheckBox c a f e = new JCheckBox ( ” Café” , f a l s e ) ; JCheckBox c o l a = new JCheckBox ( ” Cola ” , f a l s e ) ; JCheckBox l e c h e = new JCheckBox ( ” Leche ” , f a l s e ) ; JCheckBox agua = new JCheckBox ( ”Agua” , f a l s e ) ; JLabel e s t a d o = new JLabel ( ” ” ) ; public DemoCheckBox ( ) { super ( ” Demostración CheckBox” ) ; s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT ON CLOSE ) ; s e t L a y o u t (new FlowLayout ( ) ) ; e t i q u e t a . s e t F o n t (new Font ( ” A r i a l ” , Font . ITALIC , 2 2 ) ) ; c a f e . addItemListener ( this ) ; cola . addItemListener ( this ) ; leche . addItemListener ( this ) ; agua . a d d I t e m L i s t e n e r ( t h i s ) ; add ( e t i q u e t a ) ; add ( c a f e ) ; add ( c o l a ) ; add ( l e c h e ) ; add ( agua ) ; add ( e s t a d o ) ; } public void itemStateChanged ( ItemEvent e ) { Object f u e n t e = e . g e t I t e m ( ) ; JCheckBox cb = ( JCheckBox ) f u e n t e ; int s e l e c c i o n = e . g e t S t a t e C h a n g e ( ) ; i f ( s e l e c c i o n == ItemEvent .SELECTED) e s t a d o . s e t T e x t ( ”¡Se ha s e l e c c i o n a d o ”+cb . getText ()+ ” ! ” ) ; else e s t a d o . s e t T e x t ( ”¡Se ha q u i t a d o l a s e l e c c i ón ”+cb . getText ()+ ” ! ” ) ; } public s t a t i c void main ( S t r i n g [ ] arguments ) { f i n a l int FRAME WIDTH = 3 5 0 ; f i n a l int FRAME HEIGHT = 1 2 0 ; DemoCheckBox ventana = new DemoCheckBox ( ) ; ventana . s e t S i z e (FRAME WIDTH, FRAME HEIGHT ) ; ventana . s e t V i s i b l e ( true ) ; } import import import public 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 } Código 13: Aplicación DemoCheckBox. La jerarquı́a de herencia de la clase JCheckBox se muestra enseguida. Los métodos más frecuentemente usados aparacen en el cuadro 5. java.lang.Object | +--java.awt.Component 24 | +---java.awt.Container | +---javax.swing.JComponent | +---javax.swing.AbstractButton | +---javax.swing.JToggleButton | +---javax.swing.JCheckBox Método void setText(String) String getText() void setSelected(boolean) boolean isSelected() Propósito pone el texto para el JCheckBox. regresa el texto JCheckBox. pone el estado del JCheckBox a true para seleccionado o false para no seleccionado. obtiene el estado actual (marcado o desmarcado) del JCheckBox. Cuadro 5: Métodos JCheckBox usados frecuentemente. Varios constructores pueden ser usados con JCheckBox. Cuando se construye un JCheckBox, se puede escoger si se le asigna una etiqueta; si el JCheckBox aparece seleccionado, por defecto un JCheckBox no está seleccionado. Las siguientes sentencias crean cuatro objetos JCheckBox: Sin etiqueta y sin selección. JCheckBox casilla1 = new JCheckBox(); Con etiqueta y sin selección. JCheckBox casilla2 = new JCheckBox("Marcar aquı́"); Con etiqueta y sin selección. JCheckBox casilla3 = new JCheckBox("Marcar aquı́", false ); Con etiqueta y seleccionado. JCheckBox casilla4 = new JCheckBox("Marcar aquı́", true ); Si no se quiere inicializar un JCheckBox con una etiqueta y se quiere asignar después, o si se quiere cambiar una etiqueta existente, se puede usar el método setText(), como sigue: casilla1.setText("Marcar esta casilla ahora); 25 Se puede poner el estado de un JCheckBox con el método setSelected(); por ejemplo, se puede usar la siguiente sentencia para asegurar que casilla1 no esté seleccionada: casilla1.setSelected(false ); El método isSelected() es más útil en expresiones booleanas, como en el siguiente ejemplo, la cual agrega uno a una variable contadorVotos si casilla2 está actualmente marcada. if (casilla2.isSelected()) ++contadorVotos; Cuando el estado de un JCheckBox cambia de marcado a no marcado, o viceversa, un evento ItemEvent es generado, y el método itemStateChanged() es ejecutado. Se puede usar el método getItem() para determinar cual objeto generó el evento y método getStateChange() para determinar si el evento fue una selección o no. El método getStateChange() devuelve un entero que es igual a una de las dos constantes de clase—ItemEvent.SELECTED o ItemEvent.DESELECTED. En el siguiente extracto de código el método itemStateChanged() llama al método getItem(), el cual regresa el objeto llamado fuente. Luego, el valor de fuente es probado con una sentencia if para determinar si este es equivalente a un objeto JCheckBox llamado casilla. Si las dos referencias son al mismo objeto, el código determina si la casilla fue seleccionada o no, y en cada caso las acciones apropiadas son hechas. public void itemStateChanged(ItemEvent e) { Object fuente = e.getItem(); if (fuente == casilla) { int seleccion = e.getStateChange(): if (seleccion == ItemEvent.SELECTED) // sentencias que se ejecutan cuando la casilla está seleccionada else // sentencias que se ejecutan cuando la casilla NO está seleccionada } else { // sentencias que se ejecutan cuando la fuente del evento es otro // componente diferente a casilla } } Clase ButtonGroup Cuando se quieren opciones mutuamente exclusivas, es decir, se quiere que el usuario sólo pueda seleccionar una opción de varias, se debe crear un ButtonGroup para agrupar varios componentes, tales como los JCheckBox. Cuando se agrupan objetos JCheckBox y el usuario selecciona cualquiera de las casillas, el resto de las casillas queda sin selección. La clase ButtonGroup desciende directamente de la clase Object y también es parte del paquete javax.swing. 26 Nota. Un grupo de JCheckBox en el cual un usuario puede seleccionar uno a la vez actúa como un conjunto de botones de radio, los cuales se pueden crear usando la clase JRadioButton. La clase JRadioButton es similar a la clase JCheckBox, y se debe preferir su uso cuando se tiene una lista de opciones de usuario mutuamente excluyente. Para crear un ButtonGroup en un JFrame y luego agregar un JCheckBox, se deben realizar los siguientes cuatro pasos: 1. Crear un ButtonGroup, tal como: ButtonGroup unGrupo = new ButtonGroup(); 2. Crear un JCheckBox JCheckBox unaCasilla = new JCheckBox(); 3. Agregar unaCasilla a unGrupo unGrupo.add(unaCasilla); 4. Agregar unaCasilla al JFrame add(unaCasilla); Se puede crear un ButtonGroup y luego crear los objetos individuales JCheckBox, o también invirtiendo el orden. Si se crea un ButtonGroup pero se olvida agregar cualquier objeto JCheckBox a este, entonces los JCheckBox actúan como casillas individuales no exclusivas. Un usuario puede marcar uno de los JCheckBox de un grupo pulsando con el ratón en este, o con código puede seleccionar un JCheckBox dentro de un ButtonGroup con una sentencia como la siguiente: unGrupo.setSelected(unaCasilla); Solo un JCheckBox puede ser seleccionado dentro de un grupo. Si se asigna el estado selected a un JCheckBox dentro de un grupo, cualquier asignación previa es dejada sin marca. Se puede determinar cual, si hay, de los JCheckBox en un ButtonGroup está seleccionado con el método isSelected(). No se puede “limpiar la casilla” para todos los elementos que son miembros de un ButtonGroup. Se podrı́a hacer que todos los JCheckBox en un ButtonGroup inicialmente se muestren sin selección agregando un JCheckBox que no sea visible, usando en este el método setVisible(). Luego, se podrı́a usar el método setSelected() para marcar el JCheckBox no visible, y el resto se muestre sin marca. 27 Clase JComboBox Un JComboBox es un componente que combina dos caracterı́sticas: una área de visualización mostrando una opción por defecto y un cuadro de lista que contiene opciones adicionales alternas. El área de visualización contiene un botón que el usuario puede pulsar o un campo editable en el cual el usuario puede teclear. Cuando un JComboBox se muestra, la opción por defecto es mostrada. Cuando el usuario pulsa el JComboBox, una lista de elementos alternativos cae; si el usuario selecciona alguno, este reemplaza el elemento del cuadro mostrado. Los usuario esperan ver las opciones de un JComboBox en orden alfabético. Otras formas razonables son poner las opciones en algún otro orden lógico, como “pequeño”, “mediano”, y “grande”, o poniendo a los más frecuentemente seleccionados primero. La jerarquı́a de herencia de la clase JComboBox se muestra a continuación. java.lang.Object | +--java.awt.Component | +---java.awt.Container | +---javax.swing.JComponent | +---javax.swing.JComboBox Se puede construir un JComboBox usando un constructor sin argumentos y luego agregando elementos, por ejemplo, String a la lista con el método addItem(). Las siguientes sentencias crean un JComboBox llamado opcionPrincipal que contiene tres opciones de las cuales un usuario puede escoger: JComboBox<String> opcionPrincipal = new JComboBox<String>(); opcionPrincipal.addItem("Inglés"); opcionPrincipal.addItem("Matemáticas"); opcionPrincipal.addItem("Sociologı́a"); En la declaración del JComboBox del ejemplo previo, se usa <String> seguido del nombre de la clase. Por defecto, un JComboBox espera elementos que los elementos agregados sean del tipo Object. Usando String encerrado entre paréntesis angulares se notifica al compilador que los elementos esperados en el JComboBox son String y se permite que el compilador revise por errores si elementos inválidos son agregados. Cuando no se indica un tipo de dato para un JComboBox, el programa compila, pero un mensaje de advertencia es marcado con cada llamada al método addItem(). Se dice que la clase JComboBox usa genéricos. La programación con genéricos es una caracterı́stica de los lenguajes modernos que permiten que tipos de datos múltiples sean usados de forma segura con métodos. Otra forma de construir un JComboBox es usando un arreglo de Object como argumento para el constructor; los elementos en el arreglo se convierten en la lista de elementos dentro del JComboBox. El siguiente código crea el mismo JComboBox opcionPrincipal como en el código precedente: 28 String[] arregloPrincipal = {"Inglés","Matemáticas","Sociologı́a"}; JComboBox opcionPrincipal = new JComboBox(arregloPrincipal); El cuadro 6 lista algunos métodos que se pueden usar con un objeto JComboBox. Por ejemplo, se puede usar el método setSelectedItem() o setSelectedIndex() para escoger uno de los elementos en un JComboBox para que sea el elemento seleccionado inicialmente. Se puede usar el método getSelectedItem() o getSelectedIndex() para saber cual elemento está actualmente seleccionado. Método void addItem(Object) void removeItem(Object) void removeAllItems() Object getItemAt(int) int getItemCount() int getMaximumRowCount() int getSelectedIndex() Object getSelectedItem() Object[] getSelectedObjects() void setEditable(boolean) void setMaximumRowCount(int) void setSelectedIndex(int) void setSelectedItem(Object) Propósito Agrega un elemento a la lista. Quita un elemento de la lista. Quita todos los elementos de la lista. Regresa el elemento de la lista en la posición indicada por el ı́ndice entero. Devuelve la cantidad de elementos en la lista. Regresa la cantidad máxima de elementos que el cuadro lista puede desplegar sin una barra de desplazamiento. Da la posición del elemento seleccionado actualmente. Da el elemento seleccionado actualmente. Devuelve un arreglo conteniendo los Object seleccionados. Pone el campo para que sea editable o no. Pone la cantidad de renglones en el cuadro de lista que pueden ser mostrados a la vez. Pone el ı́ndice en la posición indicada por el argumento. Pone el elemento seleccionado en el área de visualización. Cuadro 6: Métodos JComboBox usados frecuentemente. Se puede tratar una lista de elementos en un objeto JComboBox como un arreglo; el primer elemento está en la posición cero, el segundo en la posición uno, etc. Es adecuado usar el método getSelectedIndex() para determinar la posición en la lista del elemento actualmente seleccionado; luego se puede usar el ı́ndice para acceder la información correspondiente guardada en un arreglo paralelo. Por ejemplo, si un JComboBox llamado opcionesHistoria ha sido llenado con una lista de eventos históricos, tales como “Declaración de Independencia”, “Batalla de Puebla”, y “Expropiación Petrolera” se puede codificar lo siguiente para recuperar la opción del usuario: int posicionSeleccion = opcionesHistoria.getSelectedIndex(); La variable posicionSeleccion guarda la posición del elemento seleccionado, y se puede usar la variable para acceder un arreglo de fechas para poder mostrar la fecha que corresponde al evento. Por ejemplo, si se declara lo siguiente, entondces fechas[posicionSeleccion] tiene el año para el evento histórico seleccionado: 29 int fechas = {1810,1862,1938}; Nota. Un JComboBox no tiene que guardar los elementos declarados como String; puede guardar un arreglo de Object y mostrar los resultados del método toString() usados con esos objetos. Es decir, en vez de usar arreglos paralelos para guardar eventos históricos y fechas, se podrı́a designar una clase EventoHistórico que encapsule String para el evento e int para la fecha. Además de un JComboBox para el cual el usuario pulse sobre elementos presentados en una lista, se puede crear un JComboBox en el cual el usuario pueda teclear texto. Para hacer esto, se usa el método setEditable(). Una desventaja de usar un JComboBox es que el texto que el usuario ingresa debe ser exactamente igual a un elemento en el cuadro de lista. Si el usuario introduce incorrectamente la selección o el uso de mayúsculas/minúsculas, no devuelve un valor válido la llamada del método getSelectedIndex(). Se puede usar una sentencia if para probar el valor regresado de getSelectedIndex(); si este es negativo, la selección no aparea ningún elemento en el JComboBox, y se puede generar un mensaje de error apropiado. Actividad 5. Crea una aplicación interactiva para un hotel incluyendo JCheckBox para las diferentes selecciones. El precio base para un cuarto es $2000, y un huésped puede escoger de varias opciones. Reservar un cuarto para un fin de semana agrega $1000 al precio, incluir desayuno agrega $200, e incluir un viaje en lancha agrega $750. Un huésped puede seleccionar ninguna, alguna u todos estos servicios. Cada vez que el usuario cambia la opciones del paquete, el precio es recalculado. 30