A more sophisticated Bean
This next example is slightly more sophisticated, albeit frivolous. It’s a JPanel that draws a little circle around the mouse whenever the mouse is moved. When you press the mouse, the word “Bang!” appears in the middle of the screen, and an action listener is fired.
The properties you can change are the size of the circle as well as the color, size, and text of the word that is displayed when you press the mouse. A BangBean also has its own addActionListener( ) and removeActionListener( ), so you can attach your own listener that will be fired when the user clicks on the BangBean. You should be able to recognize the property and event support:
//: bangbean:BangBean.java
// A graphical Bean.
package bangbean;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import com.bruceeckel.swing.*;
public class
BangBean extends JPanel implements Serializable {
private int xm, ym;
private int cSize = 20; // Circle size
private String text = "Bang!";
private int fontSize = 48;
private Color tColor = Color.RED;
private ActionListener actionListener;
public BangBean() {
addMouseListener(new ML());
addMouseMotionListener(new MML());
}
public int getCircleSize() { return cSize; }
public void setCircleSize(int newSize) {
cSize = newSize;
}
public String getBangText() { return text; }
public void setBangText(String newText) {
text = newText;
}
public int getFontSize() { return fontSize; }
public void setFontSize(int newSize) {
fontSize = newSize;
}
public Color getTextColor() { return tColor; }
public void setTextColor(Color newColor) {
tColor = newColor;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize);
}
// This is a unicast listener, which is
// the simplest form of listener management:
public void addActionListener(ActionListener l)
throws TooManyListenersException {
if(actionListener != null)
throw new TooManyListenersException();
actionListener = l;
}
public void removeActionListener(ActionListener l) {
actionListener = null;
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
Graphics g = getGraphics();
g.setColor(tColor);
g.setFont(
new Font("TimesRoman", Font.BOLD, fontSize));
int width = g.getFontMetrics().stringWidth(text);
g.drawString(text, (getSize().width - width) /2,
getSize().height/2);
g.dispose();
// Call the listener's method:
if(actionListener != null)
actionListener.actionPerformed(
new ActionEvent(BangBean.this,
ActionEvent.ACTION_PERFORMED, null));
}
}
class MML extends MouseMotionAdapter {
public void mouseMoved(MouseEvent e) {
xm = e.getX();
ym = e.getY();
repaint();
}
}
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
} ///:~
The first thing you’ll notice is that BangBean implements the Serializable interface. This means that the application builder tool can “pickle” all the information for the BangBean by using serialization after the program designer has adjusted the values of the properties. When the Bean is created as part of the running application, these “pickled” properties are restored so that you get exactly what you designed.
You can see that all the fields are private, which is what you’ll usually do with a Bean—allow access only through methods, usually using the “property” scheme.
When you look at the signature for addActionListener( ), you’ll see that it can throw a TooManyListenersException. This indicates that it is unicast, which means it notifies only one listener when the event occurs. Ordinarily, you’ll use multicast events so that many listeners can be notified of an event. However, that runs into threading issues, so it will be revisited under the heading “JavaBeans and synchronization” later in this chapter. In the meantime, a unicast event sidesteps the problem.
When you click the mouse, the text is put in the middle of the BangBean, and if the actionListener field is not null, its actionPerformed( ) is called, creating a new ActionEvent object in the process. Whenever the mouse is moved, its new coordinates are captured and the canvas is repainted (erasing any text that’s on the canvas, as you’ll see).
Here is the BangBeanTest class to test the Bean:
//: c14:BangBeanTest.java
import bangbean.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;
public class BangBeanTest extends JFrame {
private JTextField txt = new JTextField(20);
// During testing, report actions:
class BBL implements ActionListener {
private int count = 0;
public void actionPerformed(ActionEvent e) {
txt.setText("BangBean action "+ count++);
}
}
public BangBeanTest() {
BangBean bb = new BangBean();
try {
bb.addActionListener(new BBL());
} catch(TooManyListenersException e) {
txt.setText("Too many listeners");
}
Container cp = getContentPane();
cp.add(bb);
cp.add(BorderLayout.SOUTH, txt);
}
public static void main(String[] args) {
Console.run(new BangBeanTest(), 400, 500);
}
} ///:~
When a Bean is in a development environment, this class will not be used, but it’s helpful to provide a rapid testing method for each of your Beans. BangBeanTest places a BangBean within the applet, attaching a simple ActionListener to the BangBean to print an event count to the JTextField whenever an ActionEvent occurs. Usually, of course, the application builder tool would create most of the code that uses the Bean.
When you run the BangBean through BeanDumper or put the BangBean inside a Bean-enabled development environment, you’ll notice that there are many more properties and actions than are evident from the preceding code. That’s because BangBean is inherited from JPanel, and JPanel is also Bean, so you’re seeing its properties and events as well.