Java/Swing/Eventos e Action listeners

A comunicação entre os diversos widgets é feita através de eventos (events, em inglês) e de action listeners. Os conceitos estão explicados no livro Programação em GUI, capítulo Eventos, sinais, slots e callbacks.

Primeira tentativa

editar

O objetivo é criar um programa que mostre um botão (com a string OK) e uma caixa de texto. Ao clicar-se no botão, a caixa de texto responde, apresentando a string OK.

Para começar, vamos escrever o esqueleto do programa:

// arquivo swing_button_1.java
// inicio do programa swing_button - versao 1.0

import javax.swing.*;  //All swing components live
		       //in the javax.swing package

import java.awt.*;
import java.awt.event.*;

// many widgets under a box
public class swing_button_1 {
 
	public static void main(String[] args) {

		// creates the Frame
 		JFrame frame = new JFrame("swing_button_1");

		// defines frame
 		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
 		// creates the (vertical) box
		Box vbox = new Box(BoxLayout.Y_AXIS);

		// places the box inside the frame
		frame.add(vbox);
 
		// creates the elements of the vbox
		// creates the button
		JButton button1 = new JButton("OK");
		JTextField entry1 = new JTextField("");

		// add button and textfield inside the vbox
		vbox.add(button1);
		vbox.add(entry1);

		// set frame to a decent size (default is a small frame)
		frame.setSize(212, 258);
 
		// makes frame (and everything in it) visible 
		frame.setVisible(true);
	}
}
// fim do programa swing_button - versao 1

O programa acima não faz nada: pode-se clicar no botão, mas isto não tem efeitos. Precisamos incluir dois elementos:

  • o botão button1 deve incluir um actionListener, para que o clicar nele tenha algum efeito
  • a caixa de texto entry1 deve incluir um actionPerformed, para que ela reaja ao clicar do botão

Incluir um actionListener no botão é muito simples: basta chamar o membro addActionListener, passando como argumento um objeto do tipo ActionListener:

		button1.addActionListener(al);

Já a criação do actionListener é mais complicada. Um actionListener deve ter um membro actionPerformed; assim, poderíamos ter:

		ActionListener al = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.out.println("OK");
			}
		};

A versão preliminar do código, que ainda não faz aparecer "OK" na caixa de texto, mas faz aparecer "OK" na console, é:

// programa swing_button_1.java
// inicio do programa swing_button - versao 1.1

import javax.swing.*;  //All swing components live
		       //in the javax.swing package

import java.awt.*;
import java.awt.event.*;

// many widgets under a box
public class swing_button_1 {
 
	public static void main(String[] args) {

		// creates the Frame
 		JFrame frame = new JFrame("swing_button_1");

		// defines frame
 		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
 
		// creates the (vertical) box
		Box vbox = new Box(BoxLayout.Y_AXIS);

		// places the box inside the frame
		frame.add(vbox);
 
		// creates the elements of the vbox
		// creates the button
		JButton button1 = new JButton("OK");
		JTextField entry1 = new JTextField("");

		// ActionListener: makes button1 iteract with the System
		ActionListener al = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.out.println("OK");
			}
		};
		// add ActionListener to the button
		button1.addActionListener(al);

		// add button and textfield inside the vbox
		vbox.add(button1);
		vbox.add(entry1);

		// set frame to a decent size (default is a small frame)
		frame.setSize(212, 258);
 
		// makes frame (and everything in it) visible 
		frame.setVisible(true);
	}
}
// fim do programa swing_button - versao 1.1

E aqui a coisa se complica: não dá para simplesmente trocar a variável al para:

		// ActionListener: makes button1 iteract with entry1
		ActionListener al = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				entry1.setText("OK");
			}
		};

porque o compilador dá erro, com uma mensagem incompreensível (Cannot refer to a non-final variable entry1 inside an inner class defined in a different method).

Solução orientada a objetos

editar

A solução para este programa está em reescrever a classe swing_button_1 de forma a aproveitar as vantagens da programação orientada a objetos.

Assim, teremos uma classe swing_button_2 que, em vez de conter apenas um main e fazer tudo usando variáveis locais do main, é, ela própria, uma classe do Swing.

Por exemplo, quase todos widgets que foram criados no main podem se tornar membros da classe swing_button_2, sendo esta uma subclasse do widget principal.

Um esboço da reorganização, com o código que mostra os widgets mas não faz nada, seria:

// programa swing_button_2.java
// inicio do programa swing_button - versao 2.0

import javax.swing.*;  //All swing components live
		       //in the javax.swing package

import java.awt.*;
import java.awt.event.*;

// a classe swing_button_2 antes era subclasse de Object
// agora, torna-se subclasse de JFrame
public class swing_button_2 extends JFrame {

	// os widgets que eram criados no main
	// agora são membros da classe
	static Box vbox;
	static JButton button1;
	static JTextField entry1;

	public static void main(String[] args) {

		// o frame é criado no main (como antes)
		// a diferença é que chamamos o constructor da classe
		// este constructor se encarrega de montar vbox, button1 e entry1
 		swing_button_2 frame = new swing_button_2("swing_button_2");

		// o resto é igual
		frame.add(vbox);
 		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(212, 258);
		frame.setVisible(true);
	}

	// o trabalho de criar os widgets vbox, button1 e entry1 é feito no constructor da classe
	public swing_button_2(String title) {

		// super chama o constructor da classe JFrame
		super(title);
 
		// o resto é igual
		vbox = new Box(BoxLayout.Y_AXIS);
		button1 = new JButton("OK");
		entry1 = new JTextField("");

		vbox.add(button1);
		vbox.add(entry1);

	}
}
// fim do programa swing_button - versao 2.0

Incluindo o ActionListener

editar

Como visto acima, precisamos agora adicionar um ActionListener ao widget button1, e precisamos que este ActionListener tenha o método actionPerformed.

A forma mais natural de fazer isto é tornar o próprio frame um ActionListener, o que pode ser feito trocando-se a definição da classe de:

public class swing_button_2 extends JFrame {

para

public class swing_button_2 extends JFrame implements ActionListener {

Então, todos objetos da classe swing_button_2, além dos métodos da classe JFrame, também herdarão os métodos da classe ActionListener, que, no caso, é apenas um: actionPerformed. Este método deve ser escrito:

	public void actionPerformed(ActionEvent e) {
		entry1.setText("OK");
	};

Finalmente, o membro button1 do objeto frame deve se conectar ao objeto frame, de forma que o método actionPerformed do frame seja executado quando o botão for clicado. Assim, temos que fazer:

		button1.addActionListener(this);

Código, versão final

editar

A versão final do código fica:

// programa swing_button_2.java
// inicio do programa swing_button - versao 2.1

import javax.swing.*;  //All swing components live
		       //in the javax.swing package

import java.awt.*;
import java.awt.event.*;

// a classe swing_button_2 antes era subclasse de Object
// agora, torna-se subclasse de JFrame e implementa as
// interfaces da classe ActionListener
public class swing_button_2 extends JFrame implements ActionListener {

	// os widgets que eram criados no main
	// agora são membros da classe
	static Box vbox;
	static JButton button1;
	static JTextField entry1;

	// ActionListener faz button1 iteragir com entry1
	public void actionPerformed(ActionEvent e) {
		entry1.setText("OK");
	};

	public static void main(String[] args) {

		// o frame é criado no main (como antes)
		// a diferença é que chamamos o constructor da classe
		// este constructor se encarrega de montar vbox, button1 e entry1
 		swing_button_2 frame = new swing_button_2("swing_button_2");

		// o resto é igual
		frame.add(vbox);
 		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(212, 258);
		frame.setVisible(true);
	}

	// o trabalho de criar os widgets vbox, button1 e entry1 é feito no constructor da classe
	public swing_button_2(String title) {

		// super chama o constructor da classe JFrame
		super(title);
 
		// o resto é igual
		vbox = new Box(BoxLayout.Y_AXIS);
		button1 = new JButton("OK");
		entry1 = new JTextField("");

		// button1 ganha um ActionListener
		button1.addActionListener(this); 

		vbox.add(button1);
		vbox.add(entry1);

	}
}
// fim do programa swing_button - versao 2.1


Ligações externas

editar