Adding functionality to JButton, the right way
Category: Java
12:51 AM, Mon, Aug 6 2007
I just saw an article on the NetBeans site about adding functionality to buttons in Swing, using Matisse. This article recommends using Matisse to add an actionPerformed method to the button. That works, but that is rarely the right way to add functionality to a UI control such as a button.
NetBeans Matisse is a fantastic IDE. I use it for 100% of my work in Java. It does give the Java developer many options on how to perform development tasks. One of its options is giving the developer a choice between writing an actionPerformed method, or adding an Action to the object itself. The article describes the former method, and most developers pick that method first, even though it results in more complicated code which is more difficult to modify. I'll show how to do the same task, using Matisse, with an Action instead of a actionPerformed method. I'll be using NetBeans as the IDE, but any other IDE, or text editor, could be used.
First create JFrame with a JPanel and JMenu. Add a JButton and a Quit menu item:

We have three controls now:
- The Quit item in the file menu
- The button labeled "Button"
- The button labeled "Change Actions"
By using Actions, we'll show how keep the Quit menu item and the Button in sync with each other.
Go to source view, and use this bit of code to add an anonymous inner class to the MainFrame class:
private final Action quitAction = new AbstractAction() {
public void actionPerformed(ActionEvent actionEvent) {
System.exit(1);
}
};
public Action getQuitAction() {
return quitAction;
}
private final Action changeAction = new AbstractAction() {
public void actionPerformed(ActionEvent actionEvent) {
quitAction.putValue(Action.NAME, actionTextField.getText());
}
};
public Action getChangeAction() {
return changeAction;
}
We have added two anonymous inner classes. An anonymous inner class is a class which is defined and used in the same place, and has no name. The syntax for creating one is a bit strange. In this case, the magic is:
private final Action quitAction = new AbstractAction() { ... };
Anonymous inner classes are defined and created by using new AbstractClassName() { ... }. The syntax looks strange until you use it a dozen times, and then it makes sense. Note that both these actions are marked final because there is no setter. They are immutable.
Note also the apostrophe at the end of the assignment. If we do:
private StringBuilder companyName = new StringBuilder("Chiral Software");
of course there is an apostrophe, because new StringBuilder("Chiral Software") is a value in the assignment. In the same way, new AbstractAction() { ... } is a value. It happens to be a value which also defines its own unnamed (anonymous) class.
If you look in the classes directory, you'll see class files with names like MainFrame$1.class, MainFrame$2.class, etc. Those are class files created by the compiler, which correspond to the anonymous inner classes. You might compile one .java file and end up with more than one .class file. This is why.
Why create getters for these two anonymous inner classes? So we can attach them to user controls from within NetBeans. Right-click on the Button button, bringing up the menu:

The Properties window:

Click on the "..." next to Action, which is the top item:

Notice that the "Property" radio button is selected, and "No property selected" is in the select list. Click the "..." to the far right of the Property radio button. This brings up the property selector window:

Notice that our two Actions can be selected. These two Actions are available because we have get methods for them: getQuitAction() and getChangeAction(). Select quiteAction, and click "ok" all the way out.
Repeat the process for the Quit menu item, so that both the quit button and the Quit menu item now share the same Action object, the one which we defined using an anonymous inner class.
Then bind the changeAction to the change button.
Finally, to make this application into a nice jar file, we must set the main class property. Go to the Project window, and right click on the project file to bring up the project properties. Click on the Run item. For Main Class, click browse, and select the appropriate main class. This will result in a jar file which can be run by double-clicking in Windows.
We're done. Run the code. The application will come up. Type something in the text field and click the change button.

Notice that both the button, and the Quit menu item, change their names at the same time. Notice that both the quit button and the quit menu item work by firing the same Action.actionPerformed(ActionEvent) method.
Now you should see the advantages of using an AbstractAction, instead of using the standard technique, which is having NetBeans provide you with an action performed method. The disadvantage is, you generally should use anonymous inner classes, and if you're not used to their syntax, they look strange. This strangeness will go away once you have used them a dozen times.
The advantages are huge. All your actions can be managed centrally. Using the properties, you can change the text, icon, enabled / disabled state of all the UI controls at the same time. This can be done dynamically. And you only have to write the code to do the action in one place, so if you want to add some more functionality, such as sending an email every time the application quits, you would do that in one place.
Do it the right way. Use Actions, not a listener on the button. Listeners on the button are what NetBeans will add for you if you use the Action Performed item from the Events menu. NetBeans makes that easy but it is not the right way to do this.
If you look at the NetBeans generated code, NetBeans actually creates an anonymous inner class and calls JButton.addActionListener(ActionListener) to bind that anonymous inner class to the button. The NetBeans generated ActionListener has a body of the actionPerformed() method which calls a regular method in the enclosing class. This is why it is so easy to add an action to an object in NetBeans; it handles the anonymous inner class for you. But it's the wrong way to do it in most cases of user interface controls.
Download the JButton AbstractAction example source. This file is a NetBeans project, with an Ant build.xml script. This was developed and tested with Java 6. It may work on earlier versions but I haven't tried.