Eventos, listeners

Computação II - Ciência da Computação


Prof.: Danilo S. Carvalho

Nessa aula, aprenderemos a controlar as ações dos elementos da interface através da captura e manipulação de eventos.


Ao final da aula, vamos poder aplicar os conceitos de listeners e classes anônimas para definir o comportamento da interface e finalmente construir uma aplicação GUI completa.

Quando interagimos de alguma maneira com qualquer elemento da interface que construímos, esse elemento produz um evento informando a interação ocorrida e caracterizando-a.

Por exemplo, ao clicar em um JButton, o botão produz um evento "click", entendido como a ação (Action) do botão, no jargão do toolkit Swing.

Da mesma maneira, ao clicar em qualquer ponto de uma janela, a janela produz um evento de mouse, que contém a informação das coordenadas da janela onde foi clicado.

Para isso, registramos um Listener no controle que desejamos monitorar, que ficará "ouvindo" os anúncios de eventos daquele controle, e realizará uma ação quando o evento de interesse for anunciado.

No caso do JButton, chamamos o método addActionListener() para registrar um listener que vai monitorar o botão, "ouvindo" os seus cliques.

Esse método recebe uma instância que implemente a interface ActionListener (java.awt.event), um listener específico para monitorar ações.

Outros exemplos de listener são o KeyListener (monitora eventos do teclado), MouseListener (monitora eventos de clique do mouse) e o MouseMotionListener (monitora eventos de movimento do mouse).

                        
                            public class JButton ... {
                                ...

                                public void addActionListener(ActionListener l) {
                                    ...
                                }
                            }       
                        
                    
                        
                            public class JButton ... {
                                ...

                                public void addActionListener(ActionListener l) {
                                    ...
                                }
                                
                                ...

                                public void addMouseMotionListener(MouseMotionListener l) {
                                    ...
                                }
                            }     
                        
                    

No caso do ActionListener, a instância deve ter sobrescrito o método actionPerformed().

Isso é possível implementando a interface ActionListener em uma classe que criamos.

Como essa classe será utilizada apenas para definir as ações desse botão nessa tela, declaramos ela como uma classe interna (inner class) da classe responsável pela tela.

Escrevemos a classe interna logo abaixo da declaração dos campos (componentes) da interface.

Enfim, passamos uma instância da classe que criamos para o addActionListener() do componente ao qual queremos atribuir o comportamento programado.

                        
                            public class AcaoBotaoEnviar implements ActionListener {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    // Implementamos a ação do botão aqui.
                                }
                            }
                        
                    
                        
                            import java.awt.*;
                            import java.awt.event.*;
                            import java.io.*;
                            import javax.swing.*;

                            public class ExemploSwingMensagem {
                                private JFrame janela;
                                private JLabel lblMensagem;
                                private JTextArea txtMensagem;
                                private JButton btnEnviar;
                                
                                public class AcaoBotaoEnviar implements ActionListener {
                                    @Override
                                    public void actionPerformed(ActionEvent e) {
                                        try (PrintStream out = new PrintStream(new FileOutputStream("mensagem.txt"))){
                                            out.print(txtMensagem.getText());
                                        }
                                        catch (FileNotFoundException fnfe) {
                                            System.out.println("Não foi possível gravar no arquivo mensagem.txt");
                                        }
                                    }
                                }
                                
                                
                                private void initWindow() {
                                    janela = new JFrame("Enviar mensagem");
                                    janela.setSize(400, 300);
                                    janela.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                                    
                                    lblMensagem = new JLabel("Mensagem");
                                    txtMensagem = new JTextArea();
                                    btnEnviar = new JButton("Enviar");
                                    
                                    JPanel painelLabel = new JPanel();
                                    painelLabel.add(lblMensagem);
                                    
                                    txtMensagem.setLineWrap(true);
                                    JScrollPane txtScroll = new JScrollPane(txtMensagem);
                                    
                                    btnEnviar.addActionListener(new AcaoBotaoEnviar());
                                    
                                    janela.add(BorderLayout.NORTH, painelLabel);
                                    janela.add(BorderLayout.CENTER, txtScroll);
                                    janela.add(BorderLayout.SOUTH, btnEnviar);
                                    
                                    janela.setVisible(true);
                                }

                                public static void main(String[] args) {
                                    new ExemploSwingMensagem().initWindow();

                                }

                            }
                        
                    
                        
                            import java.awt.*;
                            import java.awt.event.*;
                            import java.io.*;
                            import javax.swing.*;

                            public class ExemploSwingMensagem {
                                private JFrame janela;
                                private JLabel lblMensagem;
                                private JTextArea txtMensagem;
                                private JButton btnEnviar;
                                
                                public class AcaoBotaoEnviar implements ActionListener {
                                    @Override
                                    public void actionPerformed(ActionEvent e) {
                                        try (PrintStream out = new PrintStream(new FileOutputStream("mensagem.txt"))){
                                            out.print(txtMensagem.getText());
                                        }
                                        catch (FileNotFoundException fnfe) {
                                            System.out.println("Não foi possível gravar no arquivo mensagem.txt");
                                        }
                                    }
                                }
                                
                                
                                private void initWindow() {
                                    janela = new JFrame("Enviar mensagem");
                                    janela.setSize(400, 300);
                                    janela.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                                    
                                    lblMensagem = new JLabel("Mensagem");
                                    txtMensagem = new JTextArea();
                                    btnEnviar = new JButton("Enviar");
                                    
                                    JPanel painelLabel = new JPanel();
                                    painelLabel.add(lblMensagem);
                                    
                                    txtMensagem.setLineWrap(true);
                                    JScrollPane txtScroll = new JScrollPane(txtMensagem);
                                    
                                    btnEnviar.addActionListener(new AcaoBotaoEnviar());
                                    
                                    janela.add(BorderLayout.NORTH, painelLabel);
                                    janela.add(BorderLayout.CENTER, txtScroll);
                                    janela.add(BorderLayout.SOUTH, btnEnviar);
                                    
                                    janela.setVisible(true);
                                }

                                public static void main(String[] args) {
                                    new ExemploSwingMensagem().initWindow();

                                }

                            }
                        
                    

Entretanto, à medida o número de componentes e ações em uma janela vai aumentando, essa forma de escrever o código vai ficando trabalhosa e redundante.

Precisamos criar uma nova classe para cada evento de interesse, em cada componente. Isso é especialmente problemático se as ações para um evento envolvem poucas linhas de código.

Na verdade o nome das classes listeners não é importante e idealmente poderíamos implementar seu funcionamento de forma simplificada.

Para esse fim, a linguagem Java oferece o recurso de classes anônimas.

Uma classe anônima é um recurso sintático que permite a criação, herança/implementação e instanciação de uma classe em um mesmo bloco de código, sem mesmo atribuir a ela um nome.

Usamos esse recurso através da sintaxe abaixo.

A classe do que parece ser um construtor aqui é a classe que está sendo herdada ou a interface implementada pela classe anônima.

O corpo declarado entre chaves é o corpo da classe anônima, incluindo qualquer campo ou método.

No exemplo abaixo, criamos uma classe anônima derivada de ArrayList<String> para imprimir o conteúdo da lista em linhas separadas.

                        
                            SuperClasse varClasseAnonima = new SuperClasse() {
                                // Corpo da classe anônima
                            };
                        
                    
                        
                            import java.util.*;

                            public class ExemploClasseAnonima {

                                public static void main(String[] args) {
                                    List<String> listaString = Arrays.asList(new String[]{"a", "b", "c"});
                                    
                                    List<String> listaAnonima = new ArrayList<String>(listaString) {
                                        @Override
                                        public String toString() {
                                            StringBuilder sBuilder = new StringBuilder(); 
                                            for (String str : this) {
                                                sBuilder.append(str);
                                                sBuilder.append("\n");
                                            }
                                            
                                            return sBuilder.toString();
                                        }
                                    };

                                    System.out.println(listaString);
                                    System.out.println();
                                    System.out.println(listaAnonima);
                                }

                            }
                        
                    

Podemos aplicar o mesmo recurso para registrar listeners à componentes em uma GUI.

Em vez de de uma instância de uma classe interna, criamos uma classe anônima implementando a interface listener apropriada, sobrescrevendo o método de ação relevante (nesse caso o actionPerformed).

Dessa forma, a ação correspondente ao evento fica declarada no mesmo ponto onde está sendo registrada.

Conforme o código da ação vai ficando mais complexo, deslocamos ele para um método da classe da janela, ou outra apropriada a lidar com a ação, mantendo a legibilidade.

                        
                            import java.awt.*;
                            import java.awt.event.*;
                            import java.io.*;
                            import javax.swing.*;

                            public class ExemploSwingMensagem {
                                private JFrame janela;
                                private JLabel lblMensagem;
                                private JTextArea txtMensagem;
                                private JButton btnEnviar;
                                
                                private void initWindow() {
                                    janela = new JFrame("Enviar mensagem");
                                    janela.setSize(400, 300);
                                    janela.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                                    
                                    lblMensagem = new JLabel("Mensagem");
                                    txtMensagem = new JTextArea();
                                    btnEnviar = new JButton("Enviar");
                                    
                                    JPanel painelLabel = new JPanel();
                                    painelLabel.add(lblMensagem);
                                    
                                    txtMensagem.setLineWrap(true);
                                    JScrollPane txtScroll = new JScrollPane(txtMensagem);
                                    
                                    btnEnviar.addActionListener(new ActionListener() {
                                        @Override
                                        public void actionPerformed(ActionEvent e) {
                                            try (PrintStream out = new PrintStream(new FileOutputStream("mensagem.txt"))){
                                                out.print(txtMensagem.getText());
                                            }
                                            catch (FileNotFoundException fnfe) {
                                                System.out.println("Não foi possível gravar no arquivo mensagem.txt");
                                            }
                                        }
                                    });
                                    
                                    janela.add(BorderLayout.NORTH, painelLabel);
                                    janela.add(BorderLayout.CENTER, txtScroll);
                                    janela.add(BorderLayout.SOUTH, btnEnviar);
                                    
                                    janela.setVisible(true);
                                }

                                public static void main(String[] args) {
                                    new ExemploSwingMensagem().initWindow();
                                }

                            }
                        
                    
                        
                            import java.awt.*;
                            import java.awt.event.*;
                            import java.io.*;
                            import javax.swing.*;

                            public class ExemploSwingMensagem {
                                private JFrame janela;
                                private JLabel lblMensagem;
                                private JTextArea txtMensagem;
                                private JButton btnEnviar;
                                
                                private void enviar() {
                                    try (PrintStream out = new PrintStream(new FileOutputStream("mensagem.txt"))){
                                        out.print(txtMensagem.getText());
                                    }
                                    catch (FileNotFoundException fnfe) {
                                        System.out.println("Não foi possível gravar no arquivo mensagem.txt");
                                    }
                                }
                                
                                private void initWindow() {
                                    janela = new JFrame("Enviar mensagem");
                                    janela.setSize(400, 300);
                                    janela.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                                    
                                    lblMensagem = new JLabel("Mensagem");
                                    txtMensagem = new JTextArea();
                                    btnEnviar = new JButton("Enviar");
                                    
                                    JPanel painelLabel = new JPanel();
                                    painelLabel.add(lblMensagem);
                                    
                                    txtMensagem.setLineWrap(true);
                                    JScrollPane txtScroll = new JScrollPane(txtMensagem);
                                    
                                    btnEnviar.addActionListener(new ActionListener() {
                                        @Override
                                        public void actionPerformed(ActionEvent e) {
                                            enviar();
                                        }
                                    });
                                    
                                    janela.add(BorderLayout.NORTH, painelLabel);
                                    janela.add(BorderLayout.CENTER, txtScroll);
                                    janela.add(BorderLayout.SOUTH, btnEnviar);
                                    
                                    janela.setVisible(true);
                                }

                                public static void main(String[] args) {
                                    new ExemploSwingMensagem().initWindow();
                                }
                            }
                        
                    

Agora sabemos como controlar as ações dos componentes Swing através dos eventos emitidos por eles e do uso de listeners e classes anônimas.


Agora temos controle total sobre a GUI e podemos programar uma aplicação completa.

Pergunta:

  1. Dado o exemplo de classe anônima visto na classe ExemploClasseAnonima nessa aula, que padrão de projeto podemos implementar rapidamente com classes anônimas usando a mesma ideia?

Exercício:

  1. Complete o programa da calculadora digital, fazendo ela funcionar como uma calculadora de verdade.

Até a próxima aula!