Prof. Dr. Eric Jeltsch F. Capitulo 8. Programación en Java2 Cap 8: Swing Swing En el Cap. 7 se dieron los pasos preliminares para entender la filosofía que estaba detrás de los eventos, con la tarea especifica de cerrar la ventana para cuando el usuario pulse, el icono respectivo. La idea es ahora estructurar la forma de cómo trabajar con Swing respecto al diseño de una interfaz de usuario. Recordemos que todos los elementos de Swing son parte del paquete javax.swing, y como tal se debe utilizar el encabezado import javax.swing.*;. La forma de utilizar un componente Swing no difiere en nada de cómo se hizo con un objeto de cualquier otra clase. Es decir, se crea el componente invocando al constructor de su clase, luego se establece que deberá realizar, para finalmente añadirlo a un contenedor. Por ejemplo, se desea crear un botón de pulsación, luego se establece que la tecla aceleradora es la C, además de una descripción abreviada. Esto se visualiza así. //crear un botón MiBoton = new JButton(“Pulse aquí”); //Pulsar Alt+C será equivalente a pulsar el botón. MiBoton.setMnemonic(KeyEvent.VK_C); //asignarle una descripción MiBoton.setTollTipText(“botón de pulsación.”); Los componentes más comunes son las llamadas: etiquetas, botones, campo de texto de una línea o de varias, casillas de verificación, botones de opción, listas, barras de desplazamiento, cuadros de diálogo estándar y otros. Para relacionarlo con el cap. 7, digamos que el siguiente segmento de programa, asocia al componente de la clase JButton, un manejador de eventos de acción. De esta forma el botón podrá responder al evento “click”, mediante el método actionPerformed. ActionListener al=new ActionListener(){ //este método se ejecuta para cuando se haga click sobre el boton. public void actionPerformed(ActionEvent e){ //proceso a ejecutar. } }; boton.addActionListener(al); Ahora si hubiera más de un componente asociado con un escuchador de eventos, tendremos que utilizar las capacidades proporcionadas por el objeto ActionEvent que se pasa como argumento. Por ejemplo: public void actionPerformed (ActionEvent e) { } Object obj = e.getSource(); // objeto que produjo el evento e. if (obj == botón) //por ejemplo. mostrarMensaje(); else if (... ) // 1 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing Otra técnica que puede ser útil es proceder en función de la clase del componente. Por ejemplo: public void actionPerformed (ActionEvent e) { } Object obj = e.getSource(); // objeto que produjo el evento e. if (obj instanceof JButton) //por ejemplo. mostrarMensaje(e); else if (obj instanceof JTextField) //hacer otra cosa. Como Ud. habrá observado en Cap.7 aparece en Tabla Resumen de trato a los Eventos la columna Eventos generados por, el concepto de Component y Container , estos se refieren a que un Component es un elemento de la Interface de usuario, tales como Button, Text field, o scrollbar, mientras que Container es un área de la pantalla o componente que puede contener componentes, tales como windows o un panel. En general digamos que todos los componentes Swing son subclases de la clase JComponent. Como Ud. podrá recordar, a la clase estrucFrame, luego de ralizada su ejecución se obtuvo una simple ventana con su título, esto es porque no tenía ningún componente, como Button o algún otro que se pudiera haber “incrustado”. Los contenedores son componentes Swing utilizados para ubicar otros componentes. En general, se tiene una jerarquía de contenedores, ellos son contenedores de nivel intermedio, de nivel superior y controles. La siguiente figura nos muestra algo al respecto, en donde se tiene un marco, con un panel Marco(Frame) Panel 2 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing Marco(Frame) Panel Botón Etiqueta En general, para agregar a un marco los componentes que hemos denominado controles, como son la etiqueta y el botón, hay que utilizar un contenedor intermedio. La figura anterior muestra que realmente existe una jerarquía de contenedores que nos ayudará a colocar adecuadamente los controles. La raíz de esa jerarquía es el contenedor de nivel superior definido por el marco de la ventana. Analizando esa figura se observa: Un marco (objeto de la clase JFrame). Se corresponde con la ventana marco principal. Es el contenedor de nivel superior. Los contenedores correspondientes a JDialog y JApplet son también de nivel superior. Cada contenedor de nivel superior tiene de forma predeterminada un panel raíz (en la figura no se pinta) al que se puede acceder por medio del método getContentPane( ). Se trata de un panel de contenido que permite ubicar otros componentes (paneles y controles). Por ejemplo, la siguiente sentencia añade el objeto panel al panel raíz predeterminado: getContentPane().add(panel, BorderLayout.CENTER); Un panel (objeto de la clase JPanel). Se corresponde con la vista o área de trabajo de la ventana marco principal. Es el contenedor de nivel intermedio llamado panel de contenido. Su propósito es simplificar la colocación de controles. Un panel de contenido puede incluir a su vez otros paneles de contenido. La siguiente sentencia es típica en el afán de crear un panel de este tipo: JPanel panel = new JPanel (); Una etiqueta (objeto de la clase JLabel) y un botón (objeto de la clase JButton). Esta clase de componentes es lo que genéricamente llamamos controles. Cada uno de ellos realiza una operación específica cara al usuario. Anteriormente, en este mismo capítulo, ya vimos cómo crearlos. Para añadirlos a un panel, utilizaremos el método add. Por ejemplo: 3 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing panel.add(botón) ; Para organizar los controles que se añaden a un contenedor, Java proporciona los llamados administradores de diseño. Un administrador de diseño está pensado para mostrar varios componentes a la vez en un orden preestablecido. De forma predeterminada cada contenedor tiene asignado un administrador de diseño. Swing proporciona seis administradores de diseño, que los veremos en el transcurso del semestre, ellos son: • • • • • • FlowLayout. Diseño en flujo. Coloca los componentes en el contenedor de izquierda a derecha (igual que se coloca el texto en un párrafo). Es el administrador de diseño asignado de forma predeterminada a los contenedores de nivel intermedio. GridBagLayout. Diseño tipo rejilla. Coloca los componentes en el contenedor en filas y columnas. A diferencia de GridLayout, que explicaremos a continuación, permite que un componente pueda ocupar más de una columna. BorderLayout. Diseño con límites. Divide un contenedor en cinco secciones denominadas: norte, sur, este, oeste y centro. Es el administrador de diseño que tienen asignado de forma predeterminada los contenedores de nivel superior. Los componentes se colocarán en una sección u otra según decidamos. CardLayout. Diseño por paneles. Este diseñador permite colocar en el contenedor grupos diferentes de componentes en instantes diferentes de la ejecución (similar a los paneles con pestañas). GridLayout. Diseño por rejilla. Coloca los componentes en el contenedor en filas y columnas. BoxLayout. Diseño en caja. Coloca los componentes en el contenedor en una única fila o columna, ajustándose al espacio que haya. Cuando queramos asignar un determinado administrador de diseño a un contenedor diferente de los predeterminados, primero hay que crearlo y después asignárselo invocando a su método setLayout. Por ejemplo, la siguiente sentencia asigna al contenedor panel un administrador de diseño de tipo GridLayout con 0 filas y 1 columna (los controles se colocarán en columna). panel.setLayout(new GridLayout(0,1)); Como agregar componentes a un marco de Swing?. Un JFrame está subdividido en varios paneles diferentes, el panel principal con el que trabaja es el panel de contenido, que representa toda el área de un marco al que se pueden agregar componentes. Para agregar componentes debemos hacer lo siguiente: • • • Crear un objeto JPanel(que es la versión Swing de un panel). Agregar todos los componentes(que pueden ser contenedores) al JPanel a través del correspondiente método add(componente). Convertir este JPanel en el panel de contenido con el método setContentPane(Contenedor). El objeto JPanel debe ser el único argumento. 4 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing El siguiente ejemplo usa la estructura antes descrita, agregando a la aplicación un botón rotulado al panel de contenido del marco. El botón se creará desde la clase JButton, que es la versión Swing de un botón seleccionable. Ejemplo import java.awt.GridLayout; import java.awt.event.*; import javax.swing.*; public class Swinger extends JFrame { public Swinger() { super("Swinger"); String nota = "Interactue conmigo."; JButton miButton = new JButton(nota); JPanel pane = new JPanel(); pane.add(miButton); setContentPane(pane); } //este segmento es casi estandar, para cerrar la aplicación. public static void main(String[] args) { JFrame frame = new Swinger(); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; frame.addWindowListener(l); frame.pack(); frame.setVisible(true); } } Tal vez lo único novedoso en este programa es el segmento, String nota = "Interactue conmigo."; JButton miButton = new JButton(nota); /* creándose un objeto JButton usando string como rótulo. */ JPanel pane = new JPanel(); pane.add(miButton); /* */ se crea un objeto JPanel, al cual se le agrega este botón. setContentPane(pane); La ejecución se visualiza así, 5 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing Como ya se dió cuenta los botones de Swing forman parte del cuerpo de la clase JButton. Los cuales pueden tener una etiqueta o rótulo de texto, como AWT, una etiqueta de icono o una combinación de ambos. En el caso de icono, basta considerar en el programa base Swinger.java, en donde reemplaza lo “bold” por esto ImageIcon miIcon= new ImageIcon(“hola.gif”); JButton miButton = new JButton(miIcon); JPanel pane = new JPanel(); pane.add(miButton); SetContentPane(pane) No olvidar que en Java se tiene una clase Toolkit que posee un método llamado getScreenSize() que retorna el tamaño del “screen” como un objeto Dimension, en el programa llamado d, con variables instanciadas d.height y d.width, con el fin de centrar la aplicación. Notar que el botón “interactua conmigo”, no gatilla ningún evento, es decir, NO se le ha asociado ningún evento que pueda realizar para cuando el usuario pulse el botón. Veamos que esto puede cambiar. Hasta el momento, podría esperarse, el de continuar dando ejemplos con las distintas herramientas etiquetas, botones, campo de texto de una línea o de varias, casillas de verificación, botones de opción, listas, barras de desplazamiento, cuadros de diálogo estándar y otros., pero lo que deseo es, lograr la comunión entre los eventos, vistos en Cap. 7 y los componentes y controles, que de alguna forma se han mencionado y ejemplificado con JButton, aquí en Cap. 8. No obstante al final de esta sección daremos más ejemplos que resumen muchas de las componentes aquí mencionadas. Se sabe que dentro del sistema de manejo de eventos de Java2, si una clase desea responder a un evento de usuario debe implementar la interfaces que maneja los eventos, estas interfaces se llaman escuchadores de eventos o listener. Existe una lista de ellos dada en Cap. 7. Por ejemplo, ActionListener() es un evento que se genera cuando un usuario realiza una acción sobre un componente, por ejemplo, pulsar sobre un botón. Existen varias otras, y la idea es aplicarla en algunas aplicaciones concretas. El paquete java.awt.event contiene todos los escuchadores de eventos básicos, para utilizarlos basta declarar import java.awt.event.*; Además, es posible declarar eventos que puedan manejar eventos de acción y de texto, lo que se logra al considerar, public class Suspenso extends Jframe implements ActionListener, TextListener { //... } 6 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing • Componentes Listener Al convertir una clase en un escuchador de eventos, establece un tipo específico de evento para ser escuchado por esa clase, aunque esto último puede que jamás suceda si no agrega al componente un escuchador de evento coincidente. A lo que ese escuchador de evento generará los eventos cada vez que se use el componente. Por eso después de crear un componente, para asociarle un “listener” deberá llamar a alguno de los siguientes métodos del componente. Por ejemplo, addActionListener() para los componentes de JButton, JCheckBox, JComboBox, JTextField y JRadioButton. La siguiente declaración por ejemplo crea un objeto JButton y le asocia un escuchador de evento de acción: JButton miButton = new JButton (“miBoton”); MiButton.addActionListener(this); Note que todos los métodos agregados toman un argumento: A saber, el objeto escuchador de evento de esa clase. Por medio de this Ud. indica que la clase actual es el escuchador de evento. Métodos Manejadores de Eventos Cuando Ud. asocia una interfaz a una clase, la clase debe manejar todos los eventos contenidos en la interfaz. En el caso de los escuchadores de eventos, cuando tiene lugar el evento de usuario correspondiente, el sistema de ventanas llama a cada método en forma automática. La interface ActionListener tiene un solo método. Todas las clases que ActionListener implementa deben tener un método con una estructura como la siguiente: public void actionPerformed(ActionEvent evt) { //...maneje el evento en este sector } El siguiente ejemplo instrumentaliza al botón de manera que cada vez que se pulse el botón respectivo, se cambiará el titulo del frame por el nombre del botón pulsado. Ejemplo import java.awt.event.*; import javax.swing.*; import java.awt.*; public class tuBoton extends Jframe implements ActionListener { JButton b1 = new JButton("Eric"); JButton b2 = new JButton("Jeltsch"); public tuBoton() { super("Super Botón"); //escuchadores de eventos de acción a los botones b1.addActionListener(this); b2.addActionListener(this); JPanel pane = new JPanel(); pane.add(b1); pane.add(b2); 7 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing setContentPane(pane); } public static void main(String[] args) { JFrame frame = new tuBoton(); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; frame.addWindowListener(l); frame.pack(); frame.setVisible(true); } } //la instrumentalización del evento asociado al boton. public void actionPerformed(ActionEvent evt) { Object fuente = evt.getSource(); if (fuente == b1) setTitle("Eric"); else if (fuente == b2) setTitle("Jeltsch"); repaint(); } En la ejecución Ud. podrá apreciar que cada vez que pulse algunos de los botones, se le asociará el nombre del mismo al nombre del frame. Tras de pulsar, Eric Tras de pulsar, Jeltsch El método getSource() del objeto evt se usa para determinar la fuente del evento, el que procede a llamar a repaint() para dibujar nuevamente el marco. Otro método que sirve es getActionCommand() en el objeto ActionEvent. Si en el ejemplo anterior, reemplaza setTitle("Eric"); por System.exit(0); notara que al pulsar “Eric” la aplicación se cierra. Si en el ejemplo anterior, reemplaza la siguiente declaración JOptionPane.showMessageDialog(null,"Ud.presiono:"+ evt.getActionCommand()); por setTitle("Eric"); obtendrá el siguiente mensaje 8 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing Otra aplicación asociada a pintar el panel de algún color de su preferencia, luego de pulsado el boton correspondiente, se puede apreciar en el ejemplo siguiente. import java.awt.*; import java.awt.event.*; import javax.swing.*; class ButtonPanel extends JPanel implements ActionListener { public ButtonPanel() { amaButton = new JButton("Amarillo"); azulButton = new JButton("Azul"); rojoButton = new JButton("Rojo"); add(amaButton); add(azulButton); add(rojoButton); } amaButton.addActionListener(this); azulButton.addActionListener(this); rojoButton.addActionListener(this); public void actionPerformed(ActionEvent evt) { Object source = evt.getSource(); Color color = getBackground(); if (source == amaButton) color = Color.yellow; else if (source == azulButton) color = Color.blue; else if (source == rojoButton) color = Color.red; setBackground(color); repaint();//en general, no es necesario. } } private JButton amaButton; private JButton azulButton; private JButton rojoButton; class ButtonFrame extends JFrame { public ButtonFrame() { setTitle("Y mas Botones..."); setSize(300, 200); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } ); } } Container contentPane = getContentPane(); contentPane.add(new ButtonPanel()); 9 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing public class ButtonDemo { public static void main(String[] args) { JFrame frame = new ButtonFrame(); frame.show(); } } Como ya hemos adelantado, con los métodos existen una serie de eventos entre ellos: Eventos de Acción, Eventos de Ajuste, Eventos de Enfoque, Eventos de Elemento, Eventos de Tecla, Eventos de Ratón, Eventos de Movimiento de Ratón, Eventos de Ventana, los cuales no son excluyentes, es decir existen Eventos que pueden ser de Acción como de Elemento al mismo tiempo. Veremos a continuación algunos de ellos. Eventos de Acción: Estos eventos ocurren cuando un usuario termina una acción por medio de uno de los siguientes componentes(JButton, JCheckBox, JComboBox, JTextField, JRadioButton). Para poder manejar estos eventos, se tiene que implementar la interfaz ActionListener, que por lo general la hemos declarado en ya sea de una clase TextFieldTrato o CheckBoxTrato, que están asociados a como tratar los eventos. Además se debe llamar al método addActionListener() de cada componente que genere un evento de acción, a no ser que quiera ignorar los eventos de acción de ese componente. • JTextField y JPasswordField Ejemplo. import java.awt.*; import java.awt.event.*; import javax.swing.*; public class TextFieldDemo extends JFrame { private JTextField text1, text2, text3; private JPasswordField password; public TextFieldDemo() { super( "Demo de JTextField y JPasswordField" ); Container c = getContentPane(); c.setLayout( new FlowLayout() ); // construcción de textfield de tamaño fijo text1 = new JTextField( 10 ); c.add( text1 ); // construcción de textfield con texto fijo text2 = new JTextField( "Ingrese el texto" ); c.add( text2 ); // construcción de textfield con texto fijo de 20 //elementos visibles y ningún evento a la vista. text3 = new JTextField( "text field no editable", 20 ); text3.setEditable( false ); c.add( text3 ); // construcción de textfield texto fijo password = new JPasswordField( "Texto Oculto" ); 10 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing c.add( password ); TextFieldTrato tra = new TextFieldTrato(); text1.addActionListener( tra ); text2.addActionListener( tra ); text3.addActionListener( tra ); password.addActionListener( tra ); } setSize( 325, 100 ); show(); public static void main( String args[] ) { TextFieldDemo app = new TextFieldDemo(); } app.addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e ) { System.exit( 0 ); } } ); // clase interna para tratar los eventos private class TextFieldTrato implements ActionListener { public void actionPerformed( ActionEvent e ) { String s = ""; if ( e.getSource() == text1 ) s = "text1: " + e.getActionCommand(); else if ( e.getSource() == text2 ) s = "text2: " + e.getActionCommand(); else if ( e.getSource() == text3 ) s = "text3: " + e.getActionCommand(); else if ( e.getSource() == password ) { JPasswordField pwd = (JPasswordField) e.getSource(); s = "password: " + new String( pwd.getPassword() ); } } • } } JOptionPane.showMessageDialog( null, s ); JCheckBox Ejemplo. 11 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing import java.awt.*; import java.awt.event.*; import javax.swing.*; public class CheckBoxDemo extends JFrame { private JTextField t; private JCheckBox bold, italic; public CheckBoxDemo() { super( "JCheckBox Demo" ); Container c = getContentPane(); c.setLayout(new FlowLayout()); t = new JTextField( "Mire el cambio de estilo", 20 ); t.setFont( new Font( "TimesRoman", Font.PLAIN, 14 ) ); c.add( t ); // crea objeto checkbox bold = new JCheckBox( "Bold" ); c.add( bold ); italic = new JCheckBox( "Italic" ); c.add( italic ); CheckBoxTrato tra = new CheckBoxTrato(); bold.addItemListener( tra ); italic.addItemListener( tra ); setSize( 275, 100 ); show(); } public static void main( String args[] ) { CheckBoxDemo app = new CheckBoxDemo(); app.addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e ) { System.exit( 0 ); } } ); } private class CheckBoxTrato implements ItemListener { private int valBold = Font.PLAIN; private int valItalic = Font.PLAIN; public void itemStateChanged( ItemEvent e ) { if ( e.getSource() == bold ) if ( e.getStateChange() == ItemEvent.SELECTED ) valBold = Font.BOLD; else valBold = Font.PLAIN; } } } if ( e.getSource() == italic ) if ( e.getStateChange() == ItemEvent.SELECTED ) valItalic = Font.ITALIC; else valItalic = Font.PLAIN; t.setFont(new Font( "TimesRoman", valBold + valItalic, 14 ) ); t.repaint(); 12 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing Este ejemplo también se considera también como Evento de Elemento, pues este tipo de eventos suceden cuando se selecciona o no un elemento en cualquiera de los siguientes componentes JButton, JCheckBox, JComboBox, JTextField, JRadioButton. La diferencia radica en que para poder manejar estos eventos se tiene que implementar la interfaz ItemListener desde una clase, tal como se hizo en el ejemplo recién visto. La interfaz ItemListener tiene un solo método llamado itemStateChanged() y para determinar el elemento en el que ocurrió el evento se debe llamar al método getItem() en el objeto ItemEvent, también puede usar el método getStateChange(). Eventos de Ajuste Los eventos de ajuste se dan cuando se mueve un componente JScrollBar por medio de las flechas de la barra, el cuadro de desplazamiento o haciando click en cualquier parte de la barra. Para manejar estos eventos, una clase debe implementar la interfaz AdjustmentListener(), que posee adjustmentValueChanged(EventodeAjuste), como método y que toma la forma: public void adjustmentValueChanged(AdjustmentEvent e) { //.. } En este contexto se usa el método getValue() en el objeto AdjustEvent(), que devuelve un entero que representa el valor de la barra de desplazamiento. Ejemplo import java.awt.event.*; import javax.swing.*; import java.awt.*; public class buenAjuste extends JFrame implements AdjustmentListener { BorderLayout borde= new BorderLayout(); JTextField valor = new JTextField(); JScrollBar bar = new JScrollBar(SwingConstants.HORIZONTAL, 50, 10, 0, 100); public buenAjuste() { super("Buen Ajuste"); bar.addAdjustmentListener(this); valor.setHorizontalAlignment(SwingConstants.CENTER); valor.setEditable(false); JPanel pane = new JPanel(); pane.setLayout(borde); pane.add(valor, "South"); pane.add(bar, "Center"); 13 Ingeniería en Computación , Universidad de La Serena Prof. Dr. Eric Jeltsch F. Programación en Java2 Cap 8: Swing setContentPane(pane); } public static void main(String[] args) { JFrame frame = new buenAjuste(); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; frame.addWindowListener(l); } } frame.pack(); frame.setVisible(true); public void adjustmentValueChanged(AdjustmentEvent evt){ Object fuente = evt.getSource(); if (fuente == bar) { int nuevoValor = bar.getValue(); valor.setText("" + nuevoValor); } repaint(); } En setText() las comillas vacías se denomina cadena “null”, y están concatenadas al entero nuevoValor para convertir el argumento en una cadena. Como podrá recordar, si concatena una cadena con un tipo diferente, Java siempre manejará el resultado como cadena, la cadena null es un método abreviado para cuando se quiera desplegar algo que todavía no es una cadena. 14 Ingeniería en Computación , Universidad de La Serena