All hail the PropertyChangeListener

Often times when you are building an application you need to hook multiple components together in such a way that when one component changes others must do something. When you are building custom components there is often the temptation to build a custom set of listeners to go along with it. This seems like good component etiquette; after all this is how most of the javax.swing.* components are built. Still, that's a big pain to create new listener types that must be implemented, just for observing simple changes. Plus it tightly couples your classes which can make your code brittle when making changes later. There must be a better way. And there is!

PropertyChangeListeners

All Swing components implement the property pattern, meaning that there is an addPropertyChangeListener() method on all subclasses of JComponent. There is even a special version which lets you listen to just a particular property by passing the property name into addPropertyChangeListener() along with your listener. Most component properties are already set up to send events when they change. This includes things like the text of a JTextField and the background color of a JButton. JComponent also has a firePropertyChange() method which lets your custom Swing components send their own change events without ever having to much with event classes. The result is a very easy way to hook components together with a minimum of new code, and no new interfaces or classes.

Here's an example. I was working on a tiny bitmap tile editor. There is a "+" button which lets you make the grid a bit bigger. When it's pressed the grid editor panel needs to make itself bigger. When the grid becomes bigger a small label needs to reflect the new size of the grid. You can see a chain of events here that have to be implemented. I could create custom events (like GridSizeChangeEvents or something equally horrendous), but that's a lot of work for what boils down to "update yourself". Let's see how it would work using the PropertyChangeListener instead.

First, I create the editor. It is a custom JPanel with a method called setGridwidth(). Each time the grid width is changed it rebuilds the internal grid data and then fires a property change event about it. Chose to set the property from true to false, though it doesn't really matter what I send as long as it's different. I just want to indicate that a change has happened. I have un-creatively named the property "grid" and anyone else can listen to it. Here is what it looks like:

public class TileBuilderEditorPanel extends JPanel {
...

    public void setGridwidth(int gridwidth) {
        this.gridwidth = gridwidth;
        rebuildGrid();
    }
    private void rebuildGrid() {
        int[][] newgrid = new int[gridwidth][gridheight];
...
        this.gridcells = newgrid;
        repaint();
        firePropertyChange("grid",false,true);
    }
}

Back in my main class I want to update the label whenever the grid size changes. This is simple with an anonymous listener class like this:

editor.addPropertyChangeListener("grid", new PropertyChangeListener() {
    public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
       viewer.repaint();
       grid_size.setText(realeditor.getGridwidth() + " x "
			 + realeditor.getGridheight());
    }
});

Notice that the addPropertyChangeListener takes an argument to specify the property to listen for. By telling the component that I only want grid events I don't have put a check for the property name inside of my listener. Since I don't actually care what the grid changed to, just that it changed at all, I don't need to mess with the PropertyChangeEvent (though the information is there if I want it).

Now I can create an action listener on the "+" button that will make the grid width one cell bigger whenever the width_plus button is pressed.

private void width_plusActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
    editor.setGridwidth(realeditor.getGridwidth()+1);
}

Note, the width_plusActionPerformed method is generated by Netbean's GUI builder and then attached to the actual width_plus button behind the scenes, which means I have even less code to write.

Now when I press the button it will call editor.setGridwidth() which will update the internal data structure, repaint itself, then fire a grid property event. An anonymous listener will look for the event and then update the label. It's that simple!

Here's what the (currently unfinished) application looks like:colorzoo.MainScreenSnapz002.png

For more on PropertyChangeListeners you can read the javadocs here or this section of the JavaBeans tutorial.

- Josh

Talk to me about it on Twitter

Posted February 26th, 2006

Tagged: java.net