Enviar um café pro programador

Pode me ajudar a transformar cafeína em código?

Tratamento de eventos - Extends e Implements, Classe Interna e Objeto anônimo - Como mudar a cor de um JFrame

No tutorial passado de nossa apostila de Java, demos introdução ao tratamento de eventos e ações em GUI. Explicamos os conceitos de ActionListener, ActionEvent, actionPerformed e Event Handling, de um modo geral.

Lá mostramos uma maneira de criar um tratador de eventos, onde criamos uma classe que implementa a classe abstrata ActionListener.
Como mudar o background de um programa em JavaNeste artigo iremos mostrar outras maneiras de criar esse tratador de eventos.

Vamos fazer isso através de um aplicativo que cria botões com JButton que fazem com que o fundo (background) de nosso programa mude de cor quando clicamos nesses botões.

Estendendo o JFrame e implementando a ActionListener

Se você notar bem, fizemos basicamente duas coisas no artigo passado de nossa apostila:

  • criamos uma classe chamada "Botao" que estende, ou seja, é um JFrame
  • criamos a classe "ButtonHandler", que implementa a interface "ActionListener"

Essas duas classes que tratam os eventos e ações, e exibem o resultado.
Temos umas desvantagem nesse método que é que precisamos passar para a classe "ButtonHandler" os JButtons que criamos em nosso JFrame.

Isso foi feito através da composição, pois passamos objetos de uma classe para outra.
Tal ação foi necessária pois o tratador de eventos, ButtonHandler, tenha noção da existência desses botões que estão em outra classe, para que possa tomar uma atitude sempre que esses botões forem clicados.

Agora imagine uma aplicação real, com dezenas de botões, menus, campos de texto, campos pra selecionar etc etc etc de opções em GUI (Graphic User Interface), seria extremamente incômodo ficar passando essas informações sobre os elementos, através de composição.

Por isso, vamos usar um método especial, que não vai mais ser necessário a criação da classe "ButtonHandler", somente usaremos nossa "Main" e "Botao".
O segredo da coisa é: fazer com essa classe "Botao" seja um JFrame e ao mesmo tempo implemente a interface ActionListener.

Isso é feito da seguinte maneira:

public class Botao extends JFrame implements ActionListener

Porém, como sabemos, devemos obrigatoriamente implementar os métodos abstratos de uma interface.
Felizmente, a interface ActionListener só tem um método, o actionPerformed.

Então, basicamente só temos que colocar esse método na classe "Botao", que será o responsável por tratar os eventos. Como os botões estão na mesma classe, não é necessário passar objetos de uma classe pra outra.

Alterando a cor do background (fundo) de um JFrame e o método repaint()

Para mostrar um exemplo deste método de implementação de um tratamento de eventos, vamos mostrar um aplicativo que vai exibir três botões: "verde", "azul"  e "amarelo", e ao clicarmos nele a cor de fundo da janela (o background do JFrame) vai mudar de acordo com o botão que criamos.

Quando criamos os botões no exemplo do artigo passado, tínhamos que criar um objeto do tipo "ButtonHandler", pois esta era a classe que tinha o método actionPerformed, e passávamos esse objeto como argumento para o método addActionListener de cada JButton que criávamos.

Mas agora não temos mais essa classe, pois a "Botao.java" implementa essa interface.
Como passamos o objeto para o método addActionListener, então?
Simples, passamos o próprio método. Isso é feito simplesmente escrevendo this.

Já para alterarmos a cor do fundo do background do JFrame, usamos o método:
getContentPane().setBackground(Color c)

Que recebe um objeto da classe "Cor" como argumento (Color.GREEN, Color.DARK, Color.WHITE etc)

Outro método importantíssimo para podermos alterar o conteúdo de um JFrame durante uma aplicação é o método repaint().
Sua funcionalidade é simples: ele desenha de novo o JFrame na tela.

Isso é necessário pois ao clicarmos em um dos botões, ele vai alterar a característica do JFrame (a cor do fundo). Porém, precisamos avisar ao nosso aplicativo Java para que ele desenhe novamente a GUI para que o usuário veja que algo foi alterado.

Logo, após termos detectado que botão foi clicado usando o método getSource() e alterado a cor de fundo através do método getContentPane().setBackground(), nós usamos o método repaint() para redesenhar nossa JFrame.

Assim, o código de nosso programa é simplesmente:

Main.java

import javax.swing.JFrame;

public class Main {
 public static void main(String[] args) {
    Botao botoes = new Botao();
    
    botoes.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    botoes.setSize(400,400);
    botoes.setVisible(true);
 }
}


Botao.java

import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JButton;

public class Botao extends JFrame implements ActionListener{
 private JButton verde = new JButton("Verde"),
        azul = new JButton("Azul"),
        amarelo = new JButton("Amarelo");
 
 public Botao(){
  super("Alterando a cor de Background");
  setLayout(new FlowLayout());
  
  verde.addActionListener(this);
  add(verde);
  
  azul.addActionListener(this);
  add(azul);
  
  amarelo.addActionListener(this);
  add(amarelo);
 }
 
 public void actionPerformed(ActionEvent evento) {
  if(evento.getSource() == verde)
   this.getContentPane().setBackground(Color.GREEN);
  
  if(evento.getSource() == azul)
   this.getContentPane().setBackground(Color.BLUE);
  
  if(evento.getSource() == amarelo)
   this.getContentPane().setBackground(Color.YELLOW);
   
  repaint();
 }

}

Notaram como ficou mais simples e enxuto nosso programa?

Classe Interna em Java

Outra maneira de tratarmos eventos de uma GUI é através das classes internas.
Classe interna é um conceito bem útil e interessante que o Java nos fornece.

Como o próprio nome pode sugerir, classe interna é uma classe que é declarada DENTRO de outra classe! Por exemplo:
public class Botao Externa{
 
 private class Interna{

 }
}

Vamos usar a classe interna para tratar nossos eventos, logo, esta classe deve implementar a interface ActionListener. E como uma interface é uma classe com todos os métodos abstratos, precisamos implementar sempre todos os métodos, que no caso desta classe é apenas o nosso conhecido método actionPerformed().

Vamos simplesmente declarar uma classe interna, chamada ButtonHandler, que vai implementar a ActionListener e tratar nossos eventos, que são os cliques em nos botões de cores, que simplesmente mudam a cor de nosso JFrame.

Uma importante vantagem da classe interna, é que, por ela estar dentro de outra, ela vai ter acesso aos métodos e variáveis desta classe externa. Assim, nossa classe ButtonHandler tratadora de eventos, vai ter acesso aos métodos, JButtons, variáveis, JFrame etc, da classe externa.

Veja como fica o código de nosso mesmo programa, agora usando a técnica da classe interna:

Main.java

import javax.swing.JFrame;

public class Main {
 public static void main(String[] args) {
    Botao botoes = new Botao();
    
    botoes.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    botoes.setSize(400,400);
    botoes.setVisible(true);
 }

}

Botao.java

import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JButton;

public class Botao extends JFrame{
 private JButton verde = new JButton("Verde"),
        azul = new JButton("Azul"),
        amarelo = new JButton("Amarelo");
 private ButtonHandler handler = new ButtonHandler();
 
 public Botao(){
  super("Alterando a cor de Background");
  setLayout(new FlowLayout());
  
  verde.addActionListener(handler);
  add(verde);
  
  azul.addActionListener(handler);
  add(azul);
  
  amarelo.addActionListener(handler);
  add(amarelo);
 }
 
 private class ButtonHandler implements ActionListener{
  public void actionPerformed(ActionEvent evento) {
   if(evento.getSource() == verde)
    getContentPane().setBackground(Color.GREEN);
   
   if(evento.getSource() == azul)
    getContentPane().setBackground(Color.BLUE);
   
   if(evento.getSource() == amarelo)
    getContentPane().setBackground(Color.YELLOW);
    
   repaint();
  }
 }
}

Objeto anônimo

Por fim, a apostila Java Progressivo vai ensinar uma última maneira na qual você pode usar para trabalhar com tratamento de eventos, que é a do Objeto anônimo (ou classe).
Mas antes, para entender o que é isso, vamos relembrar um pouco alguns conceitos de Orientação a Objetos em Java.

Para que possamos usar o método addActionListener em nossos JComponents, como JButton, precisamos passar um objeto como argumento. Na verdade, precisamos passar uma referência, um endereço de memória para que o método vá lá e utilize daquele objeto para tratar seus eventos.

Geralmente declaramos nossos objetos assim: NomedaClasse nomeDoObjeto;
E para atribuir um endereço de memória à ele, fazemos: nomeDoObjetos = new NomeDaClasse();
É essa palavra especial new que irá pegar um espaço na memória para o objeto.
Ou seja, o new  retorna uma referência, que é tudo que nosso método addActionListener precisa.

Assim, nem sempre é necessário declarar um objeto, dar um nome a ele, e usar o new.
Nos exemplos passados, quando passamos o objeto handler, podemos substituir isso por "new ButtonHandler()" que teremos exatamente o mesmo efeito, pois ambos casos passar uma referência de um objeto da classe ButtonHandler() para o método addActionListener.
Isso se chama objeto anônimo, pois ele não tem nome, simplesmente usamos o new.

Então, em vez de passar o nome de um objeto, vamos passar somente a referência:
new ActionListener()

Mas vamos fazer outra coisa interessante e essencial: vamos implementar a classe ActionListener e seu método actionPerformed() ali mesmo, na hora de passar o argumento.
Faremos algo do tipo:
verde.addActionListener( new ActionListener() { implementação} );

Para ficar mais legível, geralmente se faz:
verde.addActionListener( new ActionListener() {
   implementação
 }
);

Veja como fica nosso programa, usando artifícios de Objeto/Classe Anônimo(a):

Main.java

import javax.swing.JFrame;

public class Main {
 public static void main(String[] args) {
    Botao botoes = new Botao();
    
    botoes.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    botoes.setSize(400,400);
    botoes.setVisible(true);
 }

}

Botao.java

import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JButton;

public class Botao extends JFrame{
 private JButton verde = new JButton("Verde"),
          azul = new JButton("Azul"),
   amarelo = new JButton("Amarelo");
 
 public Botao(){
  super("Alterando a cor de Background");
  setLayout(new FlowLayout());
  
  verde.addActionListener( new ActionListener() {
   public void actionPerformed(ActionEvent evento) {
    getContentPane().setBackground(Color.GREEN);
    repaint();
   }
   }
  );
  add(verde);
  
  azul.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent evento) {
    getContentPane().setBackground(Color.BLUE);
    repaint();
   }
   }
  );
  add(azul);
  
  amarelo.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent evento) {
    getContentPane().setBackground(Color.YELLOW);
    repaint();
   }
   }
  );
  add(amarelo);
 }
 
}

Afinal, qual a melhor maneira de tratar eventos em Java?

Vimos diversas maneiras de se tratar eventos em Java.
Primeiro, no tutorial passado de nossa apostila, fizemos o tratamento de eventos criando uma classe externa (como fazemos normalmente) e passando para ela, através de composição, os JButtons como objetos.

Já neste tutorial, fizemos a própria classe que cria e cuida dos Botões ser JFrame e ActionListener, e criamos o método actionPerformed na própria classe.
Em seguida, fizemos uma classe interna e por fim usamos a técnica da classe anônima.

E aí, qual técnica usar? Como programar para tratar eventos?
Bom, antes de tudo, é importante frisar que o bom é ter opções.
Como mostramos, todas quatro maneiras funcionam da mesma forma, o que vai mudar é a eficiência, organização e tempo que vai levar para cada implementação.

O último método, sobre objeto anônimo, deve ser usado somente quando a implementação do tratamento de eventos for coisa simples, como um ou outro botão que dispara um evento único.
Isso porque temos que implementar tudo em cada componente, e se tiver vários botões por exemplo ,seu código ficaria enorme e extremamente repetitivo.
Portanto, uma dica é usar objeto anônimo em tratamento simples e pequenos de eventos.

As duas maneiras iniciais deste tutorial são as mais usadas, principalmente pela organização.
Se você tiver uma classe não muito grande, como a nossa que simplesmente trata de 3 botões, pode ser uma boa fazer essa class implementar a interface ActionListener diretamente, pois apenas temos que adicionar um método.

Porém, se sua classe for grande e complexa, é bom criar outra classe, uma classe interna para tratar os eventos. Fica mais organizada e terá a vantagem desta classe interna ter acesso aos elementos da classe externa.

O interessante é que você pode criar diversas classes para tratar os mais diversos tipos de evento.
No momento apenas trabalhamos com botões, mas em breve iremos trabalhar com lists, checkbox, menus, campos de texto, e aí vai ser necessário criar tratamentos de eventos diferentes para cada um destes tipos de JComponents, e ter uma classe para tratar cada componente é realmente uma boa estratégia.

Já a primeira maneira, que mostramos no tutorial passado de nossa apostila, é realmente a mais trabalhosa, pois temos que ficar enviando e recebendo objetos entre classes (Composição), além de ter que criar outra classe. Este método é realmente pouco usado, e fizemos apenas por questões didáticas.

3 comentários:

Nuno disse...

Existe alguma maneira de mudar a cor de uma forma aleatória, ou seja, o proprio programa definir ao acaso as cores?

JWilliam Rj disse...

Amigo, acho que vc pode usar uma lista de eventos dentro um sort. É apenas uma idéia!

Anônimo disse...

Nuno não tentei fazer isso, mas sla use um Random pra gerar um número aleatório de 1 ate 10 ou quantas cores quiser, depois simplesmente crie um if pra cada numero e cada if define uma cor, ou use um case :P

Contribuir com o Java Progressivo

Que tal apoiar e fazer crescer o ensino da programação no Brasil ?

Ajudar nosso país a crescer e se desenvolver cada vez mais, tecnologicamente?

Clica abaixo pra saber mais!

Apoiar o Projeto Progressivo


Tutoriais de Java