Introdução à Interface Gráfica com o Usuário (GUI) e ao pacote javax.swing

Eventos em Java.  revisado em 8/6/2010

 

 

Interface Gráfica

A interface gráfica é formada por uma grande quantidade de componentes que são colocados em containers. Os containeres mais usados são janelas (instâncias de JFrame) e painéis (JPanel). Componentes podem ser dispostos (colocados) sobre containeres, e incluem botões, menus, rótulos (labels), e também outros painéis menores.

 

Nas primeiras versões de Java, a GUI era formada pelas classes do pacote java.awt (Automatic Windowing Toolkit). Essas classes (Frame, Panel, Button, etc) apenas repassavam para o Sistema Operacional subjacente (Windows, MacOS, Linux, etc) a tarefa de desenhar a interface, de modo que a aparência mudava com a plataforma. A partir do Java 2 (1.2 em diante) foi introduzido o pacote javax.swing. Nele, as classe antigas foram refeitas (JFrame,JPanel, JButton, etc) e sua aparência passou a ser independente de plataforma.

 

Exemplo ilustrativo:

No exemplo, que será visto em detalhes mais adiante, 3 botões são adicionados em um painel, da classe PainelBotoes, uma subclasse de JPanel. Cada botão tem com um rótulo indicando uma cor (amarelo, vermelho e azul). O que se deseja é que, ao se clicar um botão, o painel seja pintado com a cor indicada no rótulo do botão.  Os 3 botões são variáveis de instância de PainelBotoes.

 

Gerenciadores de Layout:

O construtor da classe PainelBotoes cria os 3 botões, e adiciona cada um ao painel (a si mesmo) com o método add(). Os botões são adicionados ao painel usando um objeto layout manager.  A expressão significa  "gerenciador de layout", ou "gerenciador de disposição", ou seja, um objeto que possui métodos já programados com uma estratégia definida para dispor os diversos componente sobre a área do painel. A palavra layout de tão usada ganhou recentemente um aportuguesamento oficial, recebendo a grafia "leiaute". 

 

Instâncias da classe JPanel, ou de suas subclasses, já são construídas tendo como gerenciador de layout default um gerenciador  da classe java.awt.FlowLayout. Esse gerenciador dispõe os componentes sobre a área do painel de forma similar às linhas de um texto, ou seja, de cima para baixo, e da esquerda para a direita.

 

Existem diversas outras classes de gerenciadores de layout em Java. A classe BorderLayout (gerenciador padrão da classe JFrame) dispõe os componentes em cinco áreas: Norte, Sul, Leste, Oeste, e Centro. A classe GridLayout dispõe os componentes na forma de uma grade de células como uma matriz de linhas e colunas. É possível alterar o gerenciador de layout de um painel enviando a ele a mensagem setLayout(<instancia de um gerenciador>).

 

Eventos em Java

 

Java utiliza um mecanismo especial para tratar eventos, em especial os causados por interações entre usuários e a GUI. Eventos são objetos de primeira classe em Java, sendo subclasses de java.uitl.EventObject e java.awt.AWTEvent.

 

A idéia central é permitir que a ocorrência de um mesmo evento possa causar reações distintas e independentes sobre diversos objetos de uma aplicação, de forma flexível, facilitando a expansão e modificação dos sistemas.

 

Objetos da GUI que podem gerar algum tipo de evento (por exemplo, um botão que pode ser clicado) mantêm uma lista particular de objetos ouvintes, interessados em serem notificados da ocorrência de seus eventos. A lista é alterada por iniciativa dos ouvintes, que enviam uma mensagem padronizada ao potencial gerador do evento, pedindo para ser registrado na sua lista. Esses objetos podem a qualquer momento também pedir para serem removidos da lista. Quando um evento ocorre (por exemplo, o botão é clicado) o botão, no caso, examina a sua lista de ouvintes, e envia a cada um uma mensagem padrão, passando como parâmetro uma instância de uma classe específica de eventos. Cada ouvinte deve ter implementado em sua classe um método para tratar essa mensagem padrão. Isso é assegurado por Java quando exige que todo objeto, para se registrar como ouvinte, deve implementar em sua classe uma interface apropriada que força que ele seja capaz de responder às mensagens geradas pelo evento.

 

O ouvinte pode assim examinar o parâmetro recebido na mensagem, identificar a origem e outras características do evento, e a partir daí executar as ações apropriadas.

 

No nosso primeiro exemplo, o construtor registra o próprio painel como ouvinte dos “eventos de clicar” gerados pelos 3 botões. Isso é feito através das mensagens addActionListener(this) enviadas para os 3 botões. Essa mensagem faz com que o painel seja incluído nas listas de ouvintes dos 3 botões.

Veremos mais adiante porque essa forma de programar não é recomendada (embora pareça mais simples).

 

Para que isso seja possível, a classe PainelBotoes precisa implementar a interface java.awt.event.ActionListener. Isso é necessário porque o argumento do método addActionListener() deve ser do tipo ActionListener. Confira isso examinando a classe JButton (na realidade esse método está definido na superclasse de JButton).

 

 Essa interface tem a seguinte definição, que está na API do Java, no pacote java.awt.event :

 

public interface ActionListener {

  public void actionPerformed (ActionEvent e);

}

 

 Ao se clicar um botão com o mouse, um evento da classe ActionEvent é gerado. Como o painel foi registrado como ouvinte desses eventos, ele recebe a notificação da ocorrência do evento na forma de uma mensagem para ele de nome actionPerformed(ActionEvent evt). O argumento da mensagem é um objeto da classe ActionEvent, e contém informação que permite identificar qual objeto gerou o evento. O método getSource() aplicado ao evento retorna o objeto que gerou o evento.

 

 

Painéis e Janelas

 

Para entender o restante do programa, um painel precisa estar dentro de um container, como mencionado acima. Containeres são objetos que podem conter componentes. Uma janela (JFrame) contém um container predefinido que se obtém através do método getContentPane(). Todos os componentes que não são menus devem ser colocados nesse container. No exemplo, é definida uma classe JanelaBotoes (extensão de JFrame). O construtor de JanelaBotoes adiciona uma instância de PainelBotoes ao seu container, com a linha: 

          add(new PainelBotoes);

 

Isso a partir da versão 5.0. Até a versão 1.4 era necessário escrever:

          contentPane.add(new PainelBotoes);

 

Finalmente, uma classe TestaBotoes é criada, com um método main() que cria uma instância de JanelaBotoes, e envia a ela o método setVisible(true)para que a janela apareça.

 

 

O código completo do exemplo foi adaptado do livro Core Java – vol I. de Cay Horstmann:

Nesta primeira versão, a classe PainelBotoes implementa ActionListener, e o próprio painel será ouvinte dos 3 botões. As classes estão no pacote PainelOuvinte:

 

a) Classe do painel com os 3 botoes, onde o painel será o ouvinte dos 3 botões:

 

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

 

public class PainelBotoes extends JPanel implements ActionListener {

// variaveis de instancia:

        private JButton botaoAmarelo;

        private JButton botaoAzul;

        private JButton botaoVermelho;

 

// Construtor

  public PainelBotoes(){

        botaoAmarelo = new JButton("Amarelo");

        this.add(botaoAmarelo); //this é opcional. Referencia o painel.

        botaoAmarelo.addActionListener(this);

 

        botaoAzul = new JButton("Azul");

        add(botaoAzul);

        botaoAzul.addActionListener(this);//registrando o painel como ouvinte

 

        botaoVermelho = new JButton("Vermelho");

        add(botaoVermelho);

        botaoVermelho.addActionListener(this);

   }

 

// metodo de ouvinte, para tratar os eventos gerados ao clicar um botao

   public void actionPerformed(ActionEvent evt){

      Object source = evt.getSource();

// A variavel color precisa ter um valor inicial

// pois os if's poderiam todos falhar, em teoria.

      Color color = getBackground();

      if (source == botaoAmarelo) color = Color.yellow;

      else if (source == botaoAzul) color = Color.blue;

      else if (source == botaoVermelho) color = Color.red;

      setBackground(color);

      repaint();

   }

}

 

 

b) A classe que define a janela:

 

import javax.swing.*;

public class JanelaBotoes extends JFrame {

  public JanelaBotoes(){

      setTitle("Teste de Botoes"); //metodo de JFrame

      setSize(450, 300);

      setDefaultCloseOperation(EXIT_ON_CLOSE);

      add(new PainelBotoes());

      setVisible(true);

   }

}

 

 

c) Classe para testar:

 

public class TesteBotoes{

      public static void main(String[] args){

            JanelaBotoes frame = new JanelaBotoes();

      }

}

 

Melhorando o programa:

Há um defeito básico na forma como programamos o ouvinte dos eventos de botão: cada vez que um botão é clicado, um mesmo ouvinte é avisado. O método actionPerformed precisa usar um comando if para testar de qual botão partiu o evento. Isso não é bom porque um mesmo método determina o comportamento de vários botões. E em um programa O-O bem feito, cada método deve fazer apenas uma ação.

Imagine se mais tarde desejamos retirar um dos botões, ou incluir um botão novo, ou alterar o comportamento de um deles. Teremos que alterar partes desse único método, potencialmente introduzindo erros nos comportamentos dos demais botões, e obrigando a retestar todos os botões.

 

O ideal é ter um ouvinte separado para cada botão.

 

A solução usualmente adotada é criar uma classe interna para cada ouvinte. Uma classe interna é definida dentro de outra classe, e tem acesso a todas as variáveis e métodos privados da classe hospedeira.

 

Com o uso de três classes internas, podemos ter três métodos actionPerformed diferentes, o que seria impossível dentro de uma única classe. Veja como fica a nova solução abaixo, onde o painel não é mais o ouvinte dos eventos dos botões:

 

import java.awt.Color;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import javax.swing.JButton;

import javax.swing.JPanel;

public class PainelBotoes  extends JPanel {

 

private static final long serialVersionUID = 1L;

// variaveis de instancia:

private JButton botaoAmarelo;

private JButton botaoAzul;

private JButton botaoVermelho;

 

   // Construtor

   public PainelBotoes(){

         botaoAmarelo = new JButton("Amarelo");

         this.add(botaoAmarelo); //this é opcional. Referencia o painel.

         botaoAmarelo.addActionListener(new OuvinteAmarelo());

 

         botaoAzul = new JButton("Azul");

         add(botaoAzul);

         botaoAzul.addActionListener(new OuvinteAzul()); // ouvinte interno

         botaoAzul.addActionListener(new ImprimeConsole()); //ouvinte externo

 

         botaoVermelho = new JButton("Vermelho");

         add(botaoVermelho);

         botaoVermelho.addActionListener(new OuvinteVermelho());

   }

 

   // Classes ouvintes internas

   class OuvinteAmarelo implements ActionListener{

          public void actionPerformed(ActionEvent evt){

                    Color color = Color.yellow;

                    setBackground(color);

                    repaint();

          }

   }

  

   class OuvinteAzul implements ActionListener{

          public void actionPerformed(ActionEvent evt){

                    Color color = Color.blue;

                    setBackground(color);

                    repaint();

          }

   }

  

   class OuvinteVermelho implements ActionListener{

          public void actionPerformed(ActionEvent evt){

                    Color color = Color.red;

                    setBackground(color);

                    repaint();

          }

   }

}

import javax.swing.JFrame;

 

public class PainelColoridoComBotoes {

 

      public static void main(String[] args) {

            PainelColoridoComBotoes gui = new PainelColoridoComBotoes();

            gui.go();

      }

     

      public void go(){

            JFrame frame = new JFrame();

            frame.setTitle("Teste de Botoes"); //metodo de JFrame

            frame.setSize(450, 300);

            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            frame.add(new PainelBotoes());

            frame.setVisible(true);

      }

}