Manejo de Eventos
Curso Java Experto
Clase 3
Repaso
Manejo de Excepciones
Los usuarios esperan que los programas se comporten de forma sensata cuando se producen errores. Si no es posible terminar una operación por causa de error el programa debería:
- volver a un estado seguro y permitir al usuario que ejecute otras órdenes o bien
- permitir al usuario guardar su trabajo y salir ordenadamente del programa.
Tipos de problemas que deberíamos considerar
- Errores debidos a entradas de usuario
- Errores de dispositivos
- Limitaciones físicas
- Errores en el código
Clasificación de Excepciones

La jerarquía se divide en dos ramas: Error y Exception
- Error: describe errores internos y el agotamiento de recursos dentro del sistema de ejecución de java. Nosotros no debemos lanzar objetos de este tipo.
- Exception: se divide en dos ramas
- RuntimeException: se producen porque hemos cometido un error de programación.
- Otras: se producen porque a nuestro programa, que es correcto, le ha ocurrido algo malo, como un error de E/S.
Regla general: "Si es una RuntimeException, es culpa tuya".
Ejemplos de causas de RuntimeException:
- Refundiciones incorrectas
- Accesos a índices matriciales fuera de límites.
- Accesos a punteros null.
Ejemplos de causas de excepciones que no heredan de RuntimeException:
- Intentar leer más alla de un archivo.
- Intentar abrir una URL mal formada.
Excepciones comprobadas y no comprobadas
- Excepciones no comprobadas: todas las que derivan de la clase Error o de la clase RuntimeException.
- Excepciones comprobadas: todas las demás.
El compilador verifica que nosotros proporcionemos manejadores de excepciones para todas las excepciones comprobadas.
Cuáles excepciones debemos manejar?
- Cualquier método que lance una excepción comprobada es una trampa en potencia. Si no hay ningún manejador que capture la excepción, el hilo de ejecución actual llegará a su fin.
- No es necesario anunciar los errores internos de java, esto es las excepciones que se deriven de Error.
- No se deben anunciar las excepciones sin comprobación que se deriven de RuntimeException. Estos errores están totalmente bajo nuestro control. Deberíamos dedicar tiempo suficiente a corregirlos y no lanzar una excepción.
Resumen
- Los métodos deben declarar todas las excepciones comprobadas. Si nuestro método no declara todas las excepciones comprobadas, el compilador emitirá un mensaje de error.
- Las excepciones no comprobadas están fuera de nuestro control o bien son el resultado de situaciones que no deberíamos haber permitido desde un principio.
Captura de excepciones
Para capturar una excepción, se configura un bloque del tipo try/catch. La forma más sencilla del bloque try es la siguiente:
try
{
...
}
catch (TipoDeExcepcion e)
{
// manejador para este tipo de excepcion
}Si cualquier parte del código situado dentro del bloque try lanza una excepción de la clase especificada en el catch entonces:
- El programa salta el resto del código que haya en el bloque try.
- El programa ejecuta el código del manejador situado dentro de la clase catch.
Si no hay parte del código situado dentro del bloque try que lance una excepción, entonces el programa salta la cláusula catch.
Si alguna parte del código lanza una excepción de tipo distinto a la mencionada en el catch este método finaliza inmediatamente.
public void read(String nombrefichero)
{
try
{
InputStream in = new FileInputStream(nombrefichero);
int b:
while ((b = in.read()) !+ -1)
{
//procesar entrada
}
}
catch(IOException e)
{
e.printStackTrace();
}
}Ejemplo
De que otras opciones disponemos?
Con frecuencia, la mejor opción consiste en no hacer nada y limitarse a pasar la excepción a quien hiciera la llamada. Si se produce este error que se ocupe quien hizo la llamada al método.
public void read(String nombrefichero) throws IOException
{
InputStream in = new FileInputStream(nombrefichero);
int b:
while ((b = in.read()) !+ -1)
{
//procesar entrada
}
}Regla general
Debemos capturar las excepciones que debemos manejar y notificar la existencia de aquellas que no sepamos manejar.
Cuando se notifica la existencia de una excepción es preciso añadir el throws.
Consejos para el uso de excepciones
1- El manejo de excepciones no debe sustituir validaciones.
if(!s.empty()) s.pop();try
{
s.pop();
}
catch(EmptyStackException e)
{
}Las excepciones solo deben utilizarse para situaciones excepcionales.
2- Las excepciones no deben tener un manejo detallado hasta el extremo.
OutputStream out;
Stack s;
for(i=0; i<100;i++)
{
try
{
n=s.pop();
}
catch (EmptyStackException s)
{
//la pila esta vacía
}
try
{
out.writeInt(n);
}
catch(IOException e)
{
//hay un problema al escribir el archivo
}
}OutputStream out;
Stack s;
try
{
for(i=0; i<100;i++)
{
n=s.pop();
out.writeInt(n);
}
}
catch (EmptyStackException s)
{
//la pila esta vacía
}
catch(IOException e)
{
//hay un problema al escribir el archivo
}3- No hay que silenciar las excepciones
public Image cargarImagen(String s)
{
try
{
//código que amenaza con lanzar excepciones comprobadas
}
catch(Exception e)
{
}//y arreglado ?????
}4- Propagar las excepciones NO está mal.
Los métodos de nivel superior suelen estar mejor equipados para notificar al usuario la existencia de errores o para abandonar órdenes que no han tenido éxito.
public void readStuff(String nombreFichero) throws IOException //esto es válido
{
InputStream in = new FileInputStream(nombreFichero);
...
}Clases anónimas
De forma general muchos desarrolladores se enfrentan en la situación de implementar una interfaz que sólo tiene uno o dos métodos, y que dichos métodos poseen un par de lineas de código. Esta situación es muy frecuente cuando trabajamos con AWT y Swing, donde los componentes necesitan manejar un determinado evento.
Para esto las clases Anónimas son muy útiles ya que nos permiten crear instancias que son usadas una sola vez. Normalmente no puedes instanciar directamente una interfaz, por ejemplo, no puedes llamar al constructor de la interfaz Runnable de la forma:
Runnable runner = new Runnable(); //Error
Pero java nos permite crear una instancia de una clase anónima local que pueda implementar la interfaz, lo siguiente si se puede hacer :
Runnable runner = new Runnable() {
public void run() {
for (int i=0; i<10000000; i++) {
contarOvejas();
}
}
};Bases del manejo de eventos
Fuentes de eventos
- Generan eventos.
- Ejemplos: botones, ventanas, barras de desplazamiento,etc.
- Poseen métodos que nos permiten registrar en ellos los oyentes de eventos.
- Cuando se produce un evento en la fuente, esta envía una notificación del evento a todos los oyentes que estén registrados para ese evento.
Oyentes de eventos (listeners)
- Escuchan eventos.
- Cualquier objeto puede ser nombrado oyentes de eventos, en la práctica seleccionaremos objetos que pueda llevar a cabo de forma cómoda la respuesta deseada.
Pasos para el manejo de eventos
- El objeto oyente es una instancia de una clase que implementa una interfaz de oyente.
- Una fuente de eventos es un objeto en que se pueden registrar objetos oyentes y enviar a esos objetos otros objetos eventos (EventObject)
- La fuente de eventos envía objetos de evento a todos los oyentes registrados en ella cuando se produce un evento.
- Los objetos oyentes utilizarán esa información contenida en el objeto evento para determinar su reacción frente al evento.
Registro de objeto oyente en objeto fuente
objetoFuenteDeEventos.addEventListener(objetoOyenteDeEventos):
ActionListener oyente = ...;
JButton boton = new JButtton("OK");
boton.addActionListener(oyente);Ahora el oyente recibirá una notificación siempre que se produzca un "Evento acción". Para los botones un evento acción sería un click.
Objeto oyente debe implementar la interfaz adecuada
class MiOyente implements ActionListener {
public void actionPerformed(ActionEvent evento) {
//reacción ante un click
}
}Ejemplo: manejo de un click de un botón

Características del ejemplo
- una lámina poblada con 3 botones
- tres objetos oyentes que se añadirán como oyentes de eventos a los botones
- cada vez que el usuario haga click en cualquiera de los botones de la lámina, el oyente asociado recibirá un ActionEvent que indica un click en el botón. El objeto oyente modificará el color de fondo de la página.
Creando botones
JButton(Icon icon)
//Creates a button with an icon.
JButton(String text)
//Creates a button with text.
JButton(String text, Icon icon)
//Creates a button with initial text and an icon.JButton botonAmarillo = new JButton("Amarillo";
JButton botonAzul = new JButton(new ImageIcon("blue-ball.gif");Agregando botones a la lámina
class LaminaBotones extends JPanel {
public LaminaBotones(){
JButton botonAmarillo = new JButton("Amarillo");
JButton botonAzul = new JButton("Azul");
JButton botonRojo = new JButton("Rojo");
add(botonAmarillo);
add(botonAzul);
add(botonRojo);
}
}
Agregando objetos oyentes
public void actionPerformed(ActionEvent e)
Invoked when an action occurs.
class AccionColor implements ActionListener{
private Color colorDeFondo;
public AccionColor(Color c){
colorDeFondo = c;
}
@Override
public void actionPerformed(ActionEvent e) {
//se especifica el color de fondo de la lamina
...
}
}AccionColor accionAmarillo = new AccionColor(Color.YELLOW);
AccionColor accionAzul = new AccionColor(Color.BLUE);
AccionColor accionRojo = new AccionColor(Color.RED);
botonAmarillo.addActionListener(accionAmarillo);
botonAzul.addActionListener(accionAzul);
botonRojo.addActionListener(accionRojo);Cómo accedemos a la variable lámina?
Hacer que AccionColor sea una clase interna de la clase LaminaBotones. Entonces sus métodos podrán acceder a la lámina externa automáticamente.
class LaminaBotones extends JPanel {
class AccionColor implements ActionListener{
private Color colorDeFondo;
public AccionColor(Color c){
colorDeFondo = c;
}
@Override
public void actionPerformed(ActionEvent e) {
//se especifica el color de fondo de la lamina
setBackground(colorDeFondo);
}
}
}Simplificación
- Construir un botón con una cadena como rótulo.
- Añadir el botón a la lámina.
- Construir un oyente de acciones dotado del color adecuado.
- Añadir el oyente al objeto fuente.
Simplificación 1
Utilizar método auxiliar
void hacerBoton(String nombre, Color colorDeFondo) {
JButton boton = new JButton(nombre);
add(boton);
AccionColor accion = new AccionColor(colorDeFondo);
boton.addActionListener(accion);
}Entonces el constructor de LaminaBotones quedaría
public LaminaBotonesS1() {
hacerBoton("Amarillo", Color.YELLOW);
hacerBoton("Azul", Color.BLUE);
hacerBoton("Rojo", Color.RED);
}Simplificación 2
Observar que la clase AccionColor se usa en un solo lugar, por lo tanto podemos convertirla en una clase anónima.
void hacerBoton(String nombre, Color colorDeFondo) {
//Creamos boton
JButton boton = new JButton(nombre);
//Añadimos botón a lámina
add(boton);
//Añadimos listener
boton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// se especifica el color de fondo de la lamina
setBackground(colorDeFondo);
}
});
}EventHandler: alternativa a clase interna anónima
botonCargar.addActionListener(
(ActionListener) EventHandler.create(ActionListener.class, marco, "cargarDatos"));botonCargar.addActionListener(new
ActionListener() {
public void actionPerformed(ActionEvent evento) {
marco.cargarDatos();
}
});Forma de Convertir los componentes en oyentes de eventos
Tenemos la libertad de hacer que cualquier objeto de una clase que implemente la interfaz ActionListener se el oyente de un botón.
class LaminaBotonesSCI extends JPanel implements ActionListener {
private JButton botonAmarillo;
private JButton botonAzul;
private JButton botonRojo;
public LaminaBotonesSCI() {
botonAmarillo = new JButton("Amarillo");
botonAzul = new JButton("Azul");
botonRojo = new JButton("Rojo");
botonAmarillo.addActionListener(this);
botonAzul.addActionListener(this);
botonRojo.addActionListener(this);
add(botonAmarillo);
add(botonAzul);
add(botonRojo);
}
@Override
public void actionPerformed(ActionEvent e) {
// averiguo origen del evento
Object origen = e.getSource();
// se especifica el color de fondo de la lamina
if (origen == botonAmarillo)
setBackground(Color.YELLOW);
else if (origen == botonAzul)
setBackground(Color.BLUE);
else if (origen == botonRojo)
setBackground(Color.RED);
}
}Ejemplo: captura de eventos de Window
Cuando el usuario del programa intenta cerrar la ventana se genera un WindowEvent.
WindowListener oyente = ...;
marco.addListener(oyente);
void
|
windowActivated(WindowEvent e)
Invoked when the Window is set to be the active Window.
|
void
|
windowClosed(WindowEvent e)
Invoked when a window has been closed as the result of calling dispose on the window.
|
void
|
windowClosing(WindowEvent e)
Invoked when the user attempts to close the window from the window's system menu.
|
void
|
windowDeactivated(WindowEvent e)
Invoked when a Window is no longer the active Window.
|
void
|
windowDeiconified(WindowEvent e)
Invoked when a window is changed from a minimized to a normal state.
|
void
|
windowIconified(WindowEvent e)
Invoked when a window is changed from a normal to a minimized state.
|
void
|
windowOpened(WindowEvent e)
Invoked the first time a window is made visible.
|

Clases adaptador
Todas las interfaces de oyente de AWT que tienen más de un método se ofrecen junto a una clase adaptador asociada que implementa todos los métodos de la interfaz pero no hace nada con ellos. Entonces si queremos implementar solo un método en nuestro oyente:
class Terminador extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
Evitaríamos:
class Terminador implements WindowListener {
@Override
public void windowOpened(WindowEvent e) {}
@Override
public void windowClosing(WindowEvent e) { System.exit(0);}
@Override
public void windowClosed(WindowEvent e) {}
@Override
public void windowIconified(WindowEvent e) {}
@Override
public void windowDeiconified(WindowEvent e) {}
@Override
public void windowActivated(WindowEvent e) {}
@Override
public void windowDeactivated(WindowEvent e) {}
}Simplificación 2
En vez de crear una clase Terminador utilizar clase anónima.
marco.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
};
});Este código hace lo siguiente:
- Define una clase sin nombre que extiende de WindowAdapter.
- Añade un método windowClosing a esa clase anónima.
- Hereda los seis métodos restantes que no hacen nada de WindowAdapter.
- Crea un objeto de esta clase que tampoco tiene nombre.
- Pasa ese objeto a addWindowListener.
WindowStateListener
Otro oyente que trabaja sobre ventanas.
Métodos:
void WindowStateChanged(WindowEvent evento)
se invoca después de que la ventana de ha maximizado, minimizado o devuelto a su tamaño normal.
WindowEvent
- int getNewState()
- int getOldState()
Proporcionan el estado nuevo y viejo de una ventana en los eventos de cambio de estado de ventana. El entero proporcionado es uno de los valores siguientes:
- Frame.NORMAL
- Frame.ICONIFIED
- Frame.MAXIMIZED_HORIZ
- Frame.MAXIMIZED_VERT
- Frame.MAXIMIZED_BOTH
Jerarquía de Eventos de AWT

Tipos de eventos
- ActionEvent
- AdjustmentEvent
- FocusEvent
- ItemEvent
- KeyEvent
- MouseWheelEvent
- WindowEvent
Interfaces
- ActionListener
- AdjustmentListener
- FocusListener
- ItemListener
- KeyListener
- MouseListener
- MouseMotionListener
- MouseWheelListener
- WindowListener
- WindowFocusListener
- WindowStateListener
Uso frecuente
Semántica y eventos de bajo nivel en AWT
Semánticos
Expresan lo que esta haciendo el usuario, por ejemplo hacer click en un botón. Por lo tanto ActionEvent es un evento semántico.
Bajo Nivel
Aquellos que hacen posible los eventos semánticos. En el caso de un click, esto es una pulsación del ratón, una serie de movimientos del ratón o bien podría ser una pulsación de una tecla.
Eventos
Semántico
- ActionEvent
- AdjustmentEvent
- ItemEvent
Bajo Nivel
- KeyEvent
- MouseEvent
- MouseWheelEvent
- FocusEvent
- WindowEvent
Clases de Uso Frecuente
Eventos de bajo nivel
Eventos de teclado: KeyListener
| void |
keyPressed(KeyEvent e)
Invoked when a key has been pressed. |
| void |
keyReleased(KeyEvent e)
Invoked when a key has been released. |
| void |
keyTyped(KeyEvent e)
Invoked when a key has been typed. |
Caracteres
Refiere al carácter A, B, C, etc. No a las teclas en sí.
Método keyTyped notifica carácter.
Códigos de teclas virtuales
Se indican con el prefijo VK_, por ejemplo VK_A o VK_MAYUS.
Se corresponden con teclas del teclado por ejemplo VK_A corresponde a la tecla A.
No se hace distinción entre mayúsculas y minúsculas.
Métodos keyPressed y keyReleased notifican teclas.
Con los métodos keyPressed y keyReleased es preciso verificar los códigos de letras. Las constates están definidas en la clase KeyEvent.
public void keyPressed(KeyEvent evento)
{
int codigoDeTecla = evento.getKeyCode();
...
}Con el método keyTyped llamamos al método getKeyChar para obtener el carácter real que se ha pulsado.
Para comprobar el estado de las teclas MAYUS, CONTROL, ALT podemos usar los métodos isShiftDown, isControlDown, isAltDown.
public void keyPressed(KeyEvent evento)
{
int codigoDeTecla = evento.getKeyCode();
if(codigoDeTecla == KeyEvent.VK_RIGHT && evento.isShiftDown()) {
...
}
}Acciones
Múltiples formas de manejar una orden: Interfaz ACTION
Una acción es un objeto que encapsula:
- una descripción de la orden
- un icono opcional
- los parámetros necesarios para ejecutar la orden
Métodos de Action
void actionPerformed(ActionEvent evento)
void setEnabled(boolean b)
boolean isEnabled()
void putValue(String clave, Object valor)
Object getValue(String clave)
void addPropertyChangeListener(PropertyChangeListener oyente)
void removePropertyChangeListener(PropertyChangeListener oyente)
Tabla de nombres
Modifier and TypeField and Description
| static String |
ACCELERATOR_KEY
The key used for storing a KeyStroke to be used as the accelerator for the action. |
| static String |
ACTION_COMMAND_KEY
The key used to determine the command String for the ActionEvent that will be created when an Action is going to be notified as the result of residing in a Keymap associated with aJComponent. |
| static String |
DEFAULT
Not currently used. |
| static String |
DISPLAYED_MNEMONIC_INDEX_KEY
The key used for storing an Integer that corresponds to the index in the text (identified by theNAME property) that the decoration for a mnemonic should be rendered at. |
| static String |
LARGE_ICON_KEY
The key used for storing an Icon. |
| static String |
LONG_DESCRIPTION
The key used for storing a longer String description for the action, could be used for context-sensitive help. |
| static String |
MNEMONIC_KEY
The key used for storing an Integer that corresponds to one of the KeyEvent key codes. |
| static String |
NAME
The key used for storing the String name for the action, used for a menu or button. |
| static String |
SELECTED_KEY
The key used for storing a Boolean that corresponds to the selected state. |
| static String |
SHORT_DESCRIPTION
The key used for storing a short String description for the action, used for tooltip text. |
| static String |
SMALL_ICON
The key used for storing a small Icon, such as ImageIcon. |
Ejemplo
Construimos un objeto tipo Action que pueda ejecutar ordenes de cambio de color. Almacenaremos el nombre de la orden, un ícono y el color deseado.
class AccionColor extends AbstractAction {
public AccionColor(String nombre, Icon icon, Color c) {
putValue(Action.NAME, nombre);
putValue(Action.SMALL_ICON, icon);
putValue(Action.SHORT_DESCRIPTION, "Poner la lamina de color: " + nombre.toLowerCase());
putValue("color", c);
}
@Override
public void actionPerformed(ActionEvent e) {
// obtengo color
Color colorDeFondo = (Color) getValue("color");
// se especifica el color de fondo de la lamina
setBackground(colorDeFondo);
}
}
Creamos tres objetos de esta clase de la siguiente forma:
Action actionAmarillo = new AccionColor("Amarillo", new ImageIcon("src/circuloAmarillo.png"), Color.YELLOW);
Action actionAzul = new AccionColor("Azul", new ImageIcon("src/circuloAzul.png"), Color.BLUE);
Action actionRojo = new AccionColor("Rojo", new ImageIcon("src/circuloRojo.png"), Color.RED);Asociamos estas acciones utilizando el constructor de tipo JButton que admite un objeto tipo Action. Este constructor lee el nombre y el icono a partir de la acción y fija esa acción como oyente.
JButton botonAmarillo = new JButton(actionAmarillo);
JButton botonAzul = new JButton(actionAzul);
JButton botonRojo = new JButton(actionRojo);
Manejar las mismas acciones desde el teclado
Los atajos de teclado se le entregan al objeto que tiene el foco. Hay 3 botones en el ejemplo. Solución utilización de mapas de entrada.
| Indicador | Se invoca la acción |
|---|---|
| WHEN_FOCUSED | Cuando este componente tiene el foco del teclado |
| WHEN_ANCESTOR_OF_FOCUSED_COMPONENT | Cuando este componente contiene el componente que tiene el foco del teclado. |
| WHEN_IN_FOCUSED_WINDOW | Cuando este componente está alojado en la misma ventana que el componente que tiene el componente que tiene el foco del teclado. |
Obteniendo mapa
InputMap mapEnt = lamina.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);Asociando los atajos de teclado
Primero es necesario generar objetos de la clase KeyStroke. Esta es una clase de conveniencia que encapsula la descripción de una tecla. Utilizamos el método estático getKeyStroke.
KeyStroke teclaCtrlB = KeyStroke.getKeyStroke("ctrl B");Creamos una clave para el objeto acción y añadimos la pareja combinación de teclas y clave de acción al mapa de entrada.
mapEnt.put(KeyStroke.getKeyStroke("ctrl Y"), "lamina.yellow");
mapEnt.put(KeyStroke.getKeyStroke("ctrl B"), "lamina.blue");
mapEnt.put(KeyStroke.getKeyStroke("ctrl R"), "lamina.red");Conseguimos el mapa de acción del componente de nivel superior (lámina) y añadimos la pareja de la clave de acción creada y el objeto de acción a este mapa.
ActionMap mapaAccion = getActionMap();
mapaAccion.put("lamina.yellow", actionAmarillo);
mapaAccion.put("lamina.blue", actionAzul);
mapaAccion.put("lamina.red", actionRojo);Ejercicios
- Construir una lámina con tres botones. Cada uno referenciando a un color. Cuando se hace click en un botón, cambiar el fondo de la lámina según ese botón. Aplicar las 3 simplificaciones.
- Construir un marco, el cual se cierre al hacer click en la cruz, sin utilizar el método setDefaultCloseOperation. Hacer las 2 simplificaciones propuestas.
- Construir un programa que dibuje presionando las teclas cursor del teclado. Al presionar la tecla mayus el trecho del trazo debe ser mayor. Aclaración la lámina debe tener el foco por lo tanto hay que setear setFocusable(true).
- Modificar el ejercicio uno para utilizar Actions. Hacer que además de cambiar el color haciendo click en los botones se pueda hacer lo mismo presionando las combinaciones de teclas ctrl+Y, ctrl+B y ctrl+R.
Curso Java Experto Clase 3
By Marina Garcia
Curso Java Experto Clase 3
- 947