Published: May 25, 2002
By Richard G. Baldwin
Java Programming Notes # 107
Viewing tip
You may find it useful to open another copy of this lesson in a separate browser window. That will make it easier for you to scroll back and forth among the different figures and listings while you are reading about them.
Supplementary material
I recommend that you also study the other lessons in my extensive collection of online Java tutorials. You will find those lessons published at Gamelan.com. However, as of the date of this writing, Gamelan doesn't maintain a consolidated index of my Java tutorial lessons, and sometimes they are difficult to locate there. You will find a consolidated index at Baldwin's Java Programming Tutorials.
An Action object is an object that is instantiated from any class that implements the Action interface.
(To avoid confusion at the outset, let me point out that the Action interface is not the same as the ActionListener interface.)This leads to the obvious question, "What is the Action interface?"
Sun summarizes the purpose of the Action interface in the following way:
"The Action interface provides a useful extension to the ActionListener interface in cases where the same functionality may be accessed by several controls."A real-world example
Most text editors and word processors provide a copy action. Typically, the copy action makes it possible to highlight some text, and to copy it to the system clipboard.
The copy action can often be initiated in several ways. There will usually be a copy item on the Edit menu. Usually, there will also be a copy button on a toolbar.
(Typically also, the copy item on the menu will have an accelerator keystroke such as Ctrl+C, but that is the topic of a different article.)Need the same behavior
Regardless of whether the user initiates the copy action by selecting the menu item or by clicking the toolbar button, the behavior should be the same. (That behavior is usually to copy highlighted text onto the system clipboard.)
Copy action can be disabled
With the editor that I am using, whenever no text has been selected, the copy action is disabled. Whenever text is selected, the copy action becomes enabled. It is important that the copy item on the menu and the copy button on the toolbar become enabled and disabled in unison. (It would be very confusing if the copy menu item were to become disabled while the copy button on the toolbar remains enabled.)
ActionListener objects
In Java, the behavior of menu items and toolbar buttons is normally controlled by registering ActionListener objects on those sources, and defining the behavior in the actionPerformed method of the ActionListener object.
(If you are unfamiliar with the use of Action listeners, you will find numerous tutorial lessons that discuss the Delegation or JavaBeans event model at Baldwin's Java Programming Tutorials.)It is a straightforward matter to instantiate an ActionListener object and to register it on more than one source (a menu item and a toolbar button, for example). This will guarantee that action events will be handled in an identical way regardless of which source fires the event.
Enable and disable
It is somewhat less straightforward to guarantee that all of the sources are enabled and disabled in unison, particularly from a software maintenance viewpoint. Whenever the enabled state changes, it is necessary to individually enable or disable all of the sources on which the ActionListener object is registered.
Short of examining the source code, there is no easy way to determine how many and which sources a given ActionListener object has been registered on. You could, of course, create and maintain a list of such sources, but that would entail extra programming effort.
Action object to the rescue
This is where an Action object shines. The Action interface extends the ActionListener interface. Therefore, an Action object is an action listener, which provides a definition for the actionPerformed method.
In addition, and this is very important, the Action object is also a source of PropertyChange events. (An ordinary ActionListener object is not normally a source of PropertyChange events.)
Here is part of what Sun has to say about PropertyChange events:
"A "PropertyChange" event gets delivered whenever a bean changes a "bound" or "constrained" property. A PropertyChangeEvent object is sent as an argument to the PropertyChangeListener and VetoableChangeListener methods."What are bound and constrained properties?
If you are unfamiliar with the concept of bound properties in JavaBeans components, you will find several tutorial lessons on JavaBeans components at Baldwin's Java Programming Tutorials.
In a nutshell, a bound property is a property that will:
Two-way communication
An Action object has a bound property, of type boolean, named enabled. Whenever an Action object is set or registered on an action-aware component, at least two important things happen.
Registered as an ActionListener object
First, the Action object is registered as an ActionListener object on the action-aware component. The behavior of the actionPerformed method, as defined in the Action object, handles Action events fired by the component.
What does action-aware mean?
An action-aware class is a class that either defines or inherits
the setAction method shown in Figure 1. An action-aware
component is an object instantiated from such a class.
public void setAction(Action a)
Sets the Action for the ActionEvent source. Parameters:
Figure 1 |
This is the method that sets up the two-way communication described earlier (and sometimes does some other things as well, which I will discuss later).
What are the action-aware components?
A review of the Java API documentation indicates that as of Java version 1.4.0, the following classes are action-aware according to the definition given above. (Note, however, not all of these classes are suitable for instantiating typical Action objects. For example, I can't think of a good reason to use a MetalScrollButton as an Action object, but there may be such a reason that I haven't thought of.)
The invocation of the setAction method on an action-aware component sets the Action property for the ActionEvent source. In other words, it establishes the behavior that will be exhibited when the action-aware component fires an Action event.
The new Action replaces any previously set Action objects. However, it does not affect any ActionListener objects that were independently added using the addActionListener method (I will demonstrate this later). Also, if the Action object is already a registered ActionListener, it is not re-registered.
Sets certain component properties
Another important result of setting the Action property on the action-aware component is that certain other properties belonging to the component are also set from the values encapsulated in the Action object. The properties that get set may differ for different component classes. For example, if the component is a JButton, the properties that get set include:
(Note that this documentation for version 1.4.0 is considerably different from the documentation for version 1.3.1 with respect to the AbstractButton, JButton, and JMenuItem classes. In particular, the list of properties that get set has been expanded in all three cases. I don't know if this is the result of changes, or earlier typographical errors.)For example, here is what Sun has to say about the configurePropertiesFromAction method for the AbstractButton class (v 1.4.0):
"The properties which are set may differ for subclasses. By default, the properties which get set are Text, Icon, Enabled, ToolTipText, ActionCommand, and Mnemonic."The Sun documentation for the configurePropertiesFromAction method for the JButton class says essentially the same thing as the documentation for the AbstractButton class.
In comparison, here is what Sun has to say about the configurePropertiesFromAction method for the JMenuItem class:
"By default, this method sets the same properties as AbstractButton.configurePropertiesFromAction(), plus Accelerator."In other words, a JMenuItem object can set and track one property, Accelerator, which is not tracked by a JButton object.
PropertyChange notification
Earlier I stated that whenever an Action object is set or registered on an action-aware component, at least two important things happen. I have been discussing the first of the two things that happen. It is now time to discuss the second important thing that happens.
Setting the Action object on an action-aware component causes the component to be registered as a PropertyChange listener on the Action object. Thereafter, whenever the value of the enabled property of the Action object changes, the component is notified of the change.
(It is the responsibility of the component to respond to that notification and to cause its enabled property to track the value of the enabled property of the Action object.)Keyed properties
Certain other properties belonging to the Action object are also tracked and updated on the component as the Action object's keyed properties change.
(Disclaimer: Even though I refer to these values as keyed properties, it is probably not technically correct to call them properties or bound properties, because the methods for setting and getting their values do not conform to the JavaBeans design patterns for properties. However, they behave like bound properties, meaning that the component is notified when the values of the keyed properties change.)A PropertyChangeListener
This update process is handled by causing the component to act like a PropertyChangeListener registered on the Action object.
(The components can't actually be PropertyChangeListeners in their own right, because they neither define nor inherit the method named PropertyChange.)Some smoke and mirrors is being used here. This process is handled by the method named createActionPropertyChangeListener.
(I'm not certain exactly how this is handled, but my guess is that the invocation of the setAction method on a particular component causes the invocation of the createActionPropertyChangeListener method on that component. I suspect that this method instantiates an object from a class that implements the PropertyChangeListener interface. This PropertyChangeListener object is registered on the Action object so that it is notified when a property change occurs. My guess is that it also holds a reference to the component for which it was instantiated. When it is notified of a PropertyChange event on the Action object, it updates the component's properties using current values from the Action object. This causes the component to behave like a PropertyChangeListener.)
Overridden createActionPropertyChangeListener
Different overridden versions of the createActionPropertyChangeListener method exist for different component classes, so the properties that are tracked and updated may differ from one component class to the next.
For example, if the component is a JButton, the properties that are tracked and automatically updated include those in the following list:
In typical fashion, the propertyChange method shown in Figure
2 is invoked on every registered listener when the Action object
fires a PropertyChange event.
public void propertyChange(PropertyChangeEvent evt)
This method gets called when a bound property is changed. Parameters:
Figure 2 |
It is the responsibility of the propertyChange method in each registered listener object to use this notification to cause the enabled state of the component to track the value of the enabled property of the Action object. This method also causes other properties discussed above to track the values of the corresponding keyed properties of the Action object.
Two different programming styles
Two different programming styles can be used to accomplish the above behavior. The first of those programming styles existed prior to the release of JDK 1.3. The second approach was released with JDK 1.3 and updated in JDK 1.4.0.
The earlier approach, (which is still supported in version 1.4.0 but is not the preferred approach), makes use of a specialized version of the add method for three specific container types: JMenu, JPopupMenu, and JToolBar. (This method is limited to use with those three container types). The signature of the specialized version of the add method for each of the three container types respectively is:
This specialized version of the add method is defined for the container classes JMenu, JPopupMenu, and JToolBar. Here are descriptions for this method as defined for those three classes respectively:
Although this method is still supported as of JDK 1.4.0, in all three cases, Sun has something like the following to say:
As of JDK 1.3, this is no longer the preferred method for adding Actions to a container. Instead it is recommended to configure a control with an action using setAction, and then add that control directly to the Container.However ...
Even though this is not the preferred approach, you are very likely to encounter code that uses this approach. Therefore, I am providing a brief description of this approach, and will compare it with the preferred approach.
(All of the discussion, from the beginning of the article down to the beginning of the above discussion on the specialized add method, was based on the preferred approach.)Use is restricted to three container types
It is important to emphasize that this approach can only be used with the following container classes (because other container classes neither define nor inherit the specialized version of the add method):
(For example, you could place an action-aware component in a JButton if you had a reason to do so. However, you may want to avoid using AWT container classes in order to avoid the problems inherent in mixing heavyweight and lightweight components.)The specialized add method
When the specialized add method is invoked on one of the three containers listed above, passing an Action object as a parameter, the following things happen:
The major differences between this approach and the preferred approach seem to be:
The bottom line is that the preferred approach is the more general of the two. For example, if you had a reason to do so, you could put an action-aware JTextField object in a JMenu object, or an action-aware JRadioButton object in a JButton object.
A sample program
Now it is time to take a look at some code. A complete listing of the sample program named ActionObj02 is shown in Listing 22 near the end of this article.
This program uses the preferred approach to place two action-aware components in each of the following three container types: JMenu, JToolBar, and JPanel.
(Note that the earlier approach did not support the JPanel container type.)Two different Action objects are used to populate the three containers. Within each container, one of the components is registered with one of the Action objects, and the other component is registered with the other Action object.
The JMenu container is populated with two JMenuItem objects. The JToolBar container is populated with two JButton objects. The JPanel container is populated with one JButton object and one JMenuItem object.
View at startup
Figure 3 shows a view of the program as it appears at startup (the JMenu object is hidden from view).
Figure 3 View of Program at Startup
The title for the hidden menu is shown at the top in Figure 3. A tool bar object is shown immediately below the menu bar. A JPanel object is shown at the bottom.
The four checkboxes in the center are used to manipulate the enabled and icon properties of the two Action objects. This, in turn, causes the enabled property of each of the components to change, and also causes the icon displayed on each of the components to change, when the checkboxes are checked and cleared.
The expanded menu
Figure 4 shows the screen display of the program with the menu expanded, but with no other changes.
Figure 4 View with Menu Expanded
As you can see, the two menu items represent the same Action objects as the components on the tool bar and the components in the JPanel.
Manipulating the Action objects
The checkboxes in the center can be used to manipulate the Action objects. For example, Figure 5 shows the output with one of the Action objects disabled by checking the upper-left checkbox. (Note that the text on the checkbox also changes when it is checked.)
Figure 5 View with One Action Item Disabled
As you can see in Figure 5, disabling one of the Action objects causes both of the visible action-aware components registered (as PropertyChange listeners) on that object to be disabled.
(The menu also contains a component registered as a PropertyChange listener on that Action object. If you could see that component, you would see that it is disabled also.)In a similar fashion, checking the upper-right checkbox would disable the other Action object and the three components registered as PropertyChange listeners on it.
Toggle between two icons
The enabled property is clearly a property in the sense that its setter and getter methods match the JavaBeans design pattern for properties.
As we will see later, the property that specifies the icon to be displayed on the components doesn't use the standard setter and getter methods for properties. Regardless, changing the property that specifies the icon in the Action object causes the components registered on that object to display a different icon.
Figure 6 shows the result of checking the lower-left checkbox. Checking and clearing this checkbox toggles the specified icon in the Action object between two different gif files.
Figure 6 View with a Different Icon
As you can see in Figure 6, changing the property that specifies the icon in one of the Action objects causes the icon displayed on registered components to change from a red ball to a blue ball. Again, if you could see the corresponding item on the menu, you would see that it has changed also.
The Action interface
According to Sun,
"The Action interface provides a useful extension to the ActionListener interface in cases where the same functionality may be accessed by several controls."Previous sections in this article have discussed how such a capability may be used.
Here is a quotation from John Zukowski, which may help to illuminate the purpose of the Action interface.
"The Action interface is an extension to the ActionListener interface that's very flexible for defining shared event handlers independent of the components that act as the triggering agents. ..., the interface implements ActionListener and defines a lookup table data structure whose keys act as bound properties. Then, when an Action is associated with a component, these display properties are automatically carried over to it."The actionPerformed method and other things
Because the Action interface extends the ActionListener interface, it declares the actionPerformed method. In addition, this interface makes it possible to define the following items in a single location:
In addition to the actionPerformed method inherited from the ActionListener interface, the Action interface declares six methods in support of the functionality described above.
The two methods shown in Figure 7 are the standard JavaBeans setter
and getter methods for the enabled property.
isEnabled() - Returns the enabled state of the Action.
setEnabled(boolean b) - Sets the enabled state of the Action. Figure 7 |
Although it isn't obvious in the method declarations, the enabled property is a bound property as described earlier in this article. (In fact, the word bound doesn't appear anywhere in the JavaDocs description of javax.swing Interface Action for Java 2 Platform SE v1.4.0.)
PropertyChangeListener registration
The two methods shown in Figure 8 are the standard JavaBeans methods
for registering and unregistering a PropertyChangeListener object.
addPropertyChangeListener(PropertyChangeListener listener) -
Adds a PropertyChange listener.
removePropertyChangeListener(PropertyChangeListener listener)- Removes a PropertyChange listener. Figure 8 |
All objects that are registered as a PropertyChangeListener on the Action object are notified when the value of any bound property changes.
Keyed properties
The two methods shown in Figure 9 are used to put and get
the values of properties that I will refer to as keyed properties.
These methods behave like typical put and get methods for
a hashtable object.
void putValue(String key, Object value) - Sets one of this object's
properties using the associated key.
Object getValue(String key) - Gets one of this object's properties using the associated key. Figure 9 |
The values of these properties consist of:
The keys associated with these values are declared by the Action interface as public static final fields of type String, having the following names and purposes.
Because these fields are declared in an interface, they are inherently final. They are all references to String objects. Although the documentation doesn't specify the values encapsulated in the String objects, it can be determined experimentally that as of version 1.4.0, the string values encapsulated in the String objects are:
Key-value pairs
The values associated with these keys (in a key-value pair sense) in any particular program are dependent on the program. You will see how some of these keys are used in the sample program to be discussed later, so I won't discuss them further at this time.
Keyed properties are bound properties
Apparently each of these keyed properties behaves like a bound property; meaning that all registered PropertyChangeListener objects are notified whenever the value of one of these properties changes. Whether or not that listener object takes any specific action as a result of the change is determined by the author of the class from which the PropertyChangeListener object is instantiated.
Implementing the Action interface
As is usually the case, you can start from scratch and define a new class that implements the Action interface. Among other things, this will require you to provide PropertyChange support.
If you elect to take this route, I recommend that you consider using an object instantiated from the PropertyChangeSupport class or the SwingPropertyChangeSupport class to handle the registration and notification of PropertyChangeListener objects.
However, in most cases, you won't need to start from scratch. Rather, you can define a new class that extends the AbstractAction class, and override any methods whose behavior needs to be different from that defined in that class.
The AbstractAction class
According to Sun,
"This class provides default implementations for the JFC Action interface. Standard behaviors like the get and set methods for Action object properties (icon, text, and enabled) are defined here. The developer need only subclass this abstract class and define the actionPerformed method."Often, by extending this class, instead of defining a new class that implements the Action interface, you can save yourself a lot of programming effort.
(As a side note regarding the importance of the Action interface, more than two-dozen classes in v1.4.0 extend the AbstractAction class.)Fields of AbstractAction class
In addition to the final fields inherited from the Action interface, this class defines the following two fields:
The second field is of type boolean, and specifies whether the action is enabled at a particular point in time. The default value is true.
Methods of AbstractAction class
The AbstractAction class provides default implementations for each of the six methods declared in the Action interface (See Figures 7, 8, and 9.)
In addition, the AbstractAction class defines the four methods
shown in Figure 10.
clone() - Clones the abstract action.
firePropertyChange(String propertyName, Object oldValue, Object newValue) - Supports reporting bound property changes. getKeys() - Returns an array of Objects which are keys for which values have been set for this AbstractAction, or null if no keys have values set. getPropertyChangeListeners() - Returns an array of all the PropertyChangeListeners added to this AbstractAction with addPropertyChangeListener(). Figure 10 |
The behavior of the clone method should be self-explanatory.
firePropertyChange method
This method can be used in a subclass to fire a PropertyChange event. When a PropertyChange event is fired, the SwingPropertyChangeSupport object will send the appropriate PropertyChangeEvent to all registered PropertyChangeListener objects.
getKeys method
Returns an array of Object references which are the keys for which values have been set. Returns null if no keys have their values set. Can be used in an action-aware component to identify and get the values for keyed properties.
getPropertyChangeListeners()
Returns an array of all the PropertyChangeListeners added to this AbstractAction object with addPropertyChangeListener(). The array is empty if no listeners have been added. Can be used in a subclass to get a list of listeners.
Now for some code
As is my practice, I will discuss the program named ActionObj02 in
fragments. A complete listing of the program is shown in Listing
22 near the end of the lesson. The first fragment, shown in Listing
1, simply uses the main method to instantiate an object of the GUI
class. I will discuss the definition of the GUI class later.
public class ActionObj02{ public static void main( String[] args){ new GUI(); }//end main }//end ActionObj02 Listing 1 |
The MyAction class
Listing 2 shows the entire class definition for the MyAction
class from which Action objects will be instantiated.
class MyAction extends AbstractAction{ public void actionPerformed( ActionEvent e){ System.out.println("Action: " + ((AbstractButton)e.getSource()). getActionCommand()); }//end actionPerformed }//end class MyAction Listing 2 |
Note that the MyAction class doesn't implement the Action interface directly. Rather, it extends the AbstractAction class and implements the Action interface through inheritance. This causes it to inherit the default behavior of the methods defined in the AbstractAction class and discussed earlier in this lesson.
Overrides actionPerformed
The MyAction class overrides the actionPerformed method. The overridden behavior of the method is to get and display the action command for any source object (on which it is registered) that fires an ActionEvent. By default, the action command is simply the text that is visible on the face of the source object. Thus, clicking any of the six components located on the JMenu, the JToolBar, or the JPanel (see Figure 4) will cause the text on the face of that component to be displayed.
The GUI class
Listing 3 shows the beginning of the GUI class.
class GUI extends JFrame{ JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("Menu"); JToolBar toolBar = new JToolBar(); JPanel panel = new JPanel(); Listing 3 |
The code in Listing 3 creates the following container objects, which are visible in Figure 4:
Listing 4 creates two Action objects (instances of the MyAction
class discussed above). These two Action objects will
be registered on various action-aware components later.
Action actionObj01 = new MyAction(); Action actionObj02 = new MyAction(); Listing 4 |
Control
Listing 5 creates the four check boxes and a control panel shown in
the center of Figure 3.
JCheckBox ckBox01 = new JCheckBox( "Disable01"); JCheckBox ckBox02 = new JCheckBox( "Disable02"); JCheckBox ckBox03 = new JCheckBox( "Toggle Icon01"); JCheckBox ckBox04 = new JCheckBox( "Toggle Icon02"); JPanel controlPanel = new JPanel(); Listing 5 |
The four check boxes will be used to manipulate the behavior of the two Action objects, and hence the behavior of the six associated action-aware components. Note that the text shown on the face of the checkboxes will be modified when those checkboxes are checked and cleared.
The GUI constructor
Listing 6 shows the beginning of the constructor for the GUI
class.
GUI(){//constructor menuBar.add(menu); menuBar.setBorder( new BevelBorder( BevelBorder.RAISED)); setJMenuBar(menuBar); Listing 6 |
The code in Listing 6 adds the JMenu object to the JMenuBar object, which is the standard way to construct a menu in Java.
Then it invokes the setBorder method on the menu bar to cause its edges to become visible. I did this to make it easy to visually separate the menu bar from the tool bar that appears immediately below it in Figure 3. (You can learn more about Swing borders at Baldwin's Java Programming Tutorials.)
Set the JMenuBar
Finally, the code in Listing 6 adds the menu bar to the JFrame. Note, however, that this is accomplished by invoking the setJMenuBar method on the JFrame object instead of invoking an add method, (which is the way that other components are normally added to a JFrame container.)
A decorated JToolBarcontainer
Listing 7 decorates the JToolBar object with raised bevel borders,
and places it in the NORTH position of the JFrame object.
This causes the toolbar to appear immediately below the menu bar in Figure
3.
toolBar.setBorder(new BevelBorder( BevelBorder.RAISED)); getContentPane().add( toolBar,BorderLayout.NORTH); Listing 7 |
(If need be, you can also learn more about the BorderLayout manager and the getContentPane method at Baldwin's Java Programming Tutorials.)
A decorated JPanel container
Listing 8 decorates the JPanel object with raised bevel borders,
and places it in the SOUTH position of the JFrame object.
This causes it to appear at the bottom of the JFrame in Figure 3.
panel.setBorder(new BevelBorder( BevelBorder.RAISED)); getContentPane().add( panel,BorderLayout.SOUTH); Listing 8 |
Setting keyed values
Except for the definition of the MyAction class discussed earlier,
very little of the code discussed so far has been specific to the Action
interface. However, the code in Listing 9 is very specific to the
Action
interface.
actionObj01.putValue( Action.NAME,"actionObj01"); Listing 9 |
Listing 9 invokes the putValue method on one of the Action objects to set the value for one of the keyed properties. As mentioned earlier, each Action object has a container for keyed properties that behaves much as a hashtable behaves. Values are stored against keys, and can be accessed later by specifying the key.
Values are stored in that container by invoking the putValue method and passing a key and a value as parameters. In this case, one of the constants (final variables) defined in the Action interface is used as the key. The literal String object "actionObj01" is passed as the value.
(Note that as discussed earlier, the constant is referred to symbolically as Action.NAME, and no explicit use is made of the actual String value encapsulated in that constant.)The remaining keyed properties
Listing 10 sets the values for each of the remaining keyed properties.
actionObj01.putValue( Action.SMALL_ICON,new ImageIcon( "redball.gif")); actionObj01.putValue( Action.SHORT_DESCRIPTION, "ToolTip for actionObj01"); actionObj02.putValue( Action.NAME,"actionObj02"); actionObj02.putValue( Action.SMALL_ICON,new ImageIcon( "bulb2.gif")); actionObj02.putValue( Action.SHORT_DESCRIPTION, "ToolTip for actionObj02"); Listing 10 |
As mentioned earlier, these keyed properties behave as bound properties, meaning that registered objects are notified when their values change. Checking and clearing two of the checkboxes in this program causes the values of the SMALL_ICON properties to change, thus causing the icons displayed on the components registered on the respective Action objects to change accordingly.
Build the menu
Code that I discussed earlier created a menu bar and placed a menu on
that bar. Now the time has come to place some menu items on that
menu. This process begins in Listing 11.
JMenuItem mnuA1 = new JMenuItem(); mnuA1.setAction(actionObj01); menu.add(mnuA1); Listing 11 |
Invoke the setAction method
The code in Listing 11 is very significant relative to the use of Action objects. Listing 11 instantiates a new JMenuItem object and then invokes the setAction method on that object, passing a reference to one of the Action objects as a parameter.
As discussed earlier, this causes the Action object to be registered as an action listener on the JMenuItem object.
PropertyChange listener registration
More importantly, however, for the purpose of this discussion, invocation of the setAction method causes the JMenuItem object to be registered as a PropertyChange listener on the Action object. Following this, when the enabled property, or any of the keyed properties of the Action object change, the JMenuItem object will be notified of the change.
The JMenuItem class is designed such that this will cause some of the corresponding properties on the JMenuItem object to change in order to track the change on the Action object. (A JMenuItem object doesn't set or track all of the indexed properties.)
After the JMenuItem object has been properly prepared, it is added to the menu in Listing 11.
An alternative to invoking setAction
Listing 12 shown an alternative approach to invoking the setAction
method on the new JMenuItem object. (This approach appears
to achieve the same result as the invocation of the setAction method.).
//Put a JMenuItem on the menu. Set // its Action object in the // constructor. This is a // different approach. JMenuItem mnuA2 = new JMenuItem(actionObj02); menu.add(mnuA2); Listing 12 |
The JMenuItem class has a constructor that accepts a reference to an Action object as a parameter, and according to the Sun documentation, "Creates a menu item whose properties are taken from the specified Action."
An understatement
It appears that the statement in the Sun documentation is something of an understatement. In particular, it appears that constructing the JMenuItem object in this manner causes the action property of that object to be set to the Action object, just as though the setAction method were invoked on that object as in Listing 11.
As explained in the discussion of Listing 11, this involves much more than the Sun documentation statement would imply. In particular, the Action object becomes an action listener on the JMenuItem object, and the JMenuItem also becomes a change listener on the Action object.
Registration of two action listeners
Registering an Action object on an action-aware component
does not prohibit the registration of ordinary ActionListener objects
on the same component. This is illustrated in Listing 13.
JButton butB1 = new JButton(); butB1.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ System.out.println( "Ordinary Action Listener"); }//end actionPerformed() }//end ActionListener );//end addActionListener butB1.setAction(actionObj01); toolBar.add(butB1); Listing 13 |
Listing 13 instantiates a new JButton object, and then uses an anonymous inner class to register an ordinary ActionListener object on that component.
(If you are unfamiliar with anonymous inner classes, see Baldwin's Java Programming Tutorials.)Then it invokes the setAction method on the same JButton object, which causes the specified Action object to also be registered as an action listener on the JButton object.
Two actionPerformed methods are invoked
Thereafter, when the JButton object is clicked with the mouse, causing an action event to be fired by the JButton object, the following two lines of text appear on the screen:
Action: actionObj01
Ordinary Action Listener
The first line of output text is produced by the actionPerformed method defined in the Action object (see Listing 2).
The second line of output text is produced by the actionPerformed method defined for the ordinary ActionListener object (see Listing 13).
Finish populating the toolbar
Listing 14 finishes populating the toolbar by placing another JButton
object on the toolbar.
JButton butB2 = new JButton(); butB2.setAction(actionObj02); toolBar.add(butB2); Listing 14 |
Listing 14 invokes the setAction method on the JButton object to register the other Action object on that JButton. Thus, two different Action objects are registered on the two JButton objects on the toolbar.
Synchronized with menu items
Furthermore, the same two Action objects that are registered on the JButton objects on the tool bar are also registered on the two JMenuItem objects on the menu. Thus, the menu items and the toolbar buttons are tied together (in pairs) so as to behave in a synchronous fashion.
When one of the Action objects becomes disabled, the corresponding menu item and the corresponding toolbar button also become disabled. When the other Action object becomes disabled, the other menu item and the other toolbar button become disabled.
When an Action object becomes enabled, the corresponding menu item and the corresponding toolbar button become enabled.
When the value of the keyed property of an Action object identified with the key Action.SMALL_ICON changes, the corresponding menu item and the corresponding toolbar button track the change. This causes the image displayed on both of those components to change.
Various containers can be used
Probably the most common use of the Action interface is to synchronize the behavior of items on a menu with buttons on a toolbar. However, the use of the Action interface is not confined to those two types of containers. Rather, the Action interface can be used to synchronize the behavior of action-aware components in just about any kind of container. This is illustrated in Listing 15.
(You may want to avoid AWT containers to avoid the problems involved in mixing heavyweight and lightweight components. For example, it appears that you cannot control icons on a JButton that is placed in an AWT Panel.)
JButton butC = new JButton(); butC.setAction(actionObj01); panel.add(butC); JMenuItem mnuC = new JMenuItem(); mnuC.setAction(actionObj02); panel.add(mnuC); Listing 15 |
Populate the JPanel
Listing 15 creates a new JButton object and a new JMenuItem object and places them in a common JPanel container object. The setAction method is used, along with the same two Action objects discussed earlier, to synchronize the behavior of these two components with the two components on the menu, and the two components on the toolbar.
(Hopefully by now you will have compiled and run this program. Due to the limitations of my verbal descriptions of behavior, running the program may ultimately be necessary for you to fully understand how this program behaves.)Construct control panel, set size, etc.
Listing 16 constructs the control panel containing check boxes that
will be used to manipulate the properties of the two Action objects.
Listing 16 also sets the size of the GUI and makes it visible.
//Construct the control panel and // put it in the Center controlPanel.setLayout( new GridLayout(2,2,3,3)); controlPanel.add(ckBox01); controlPanel.add(ckBox02); controlPanel.add(ckBox03); controlPanel.add(ckBox04); getContentPane().add(controlPanel, BorderLayout.CENTER); //Set the size and make the GUI // visible setSize(350,200); setVisible(true); setTitle("Copyright 2002, " + "R.G.Baldwin"); Listing 16 |
The code in Listing 16 is completely straightforward and doesn't deserve further discussion in this article.
Register checkbox event handlers to manipulate Action properties
The anonymous inner listener class in Listing 17 makes it possible for the user to manipulate the enabled property of one of the Action objects and its associated visual components.
The code in Listing 17, (and the code that follows in other listings), registers ActionListener objects on each of the checkboxes.
(Note that these ActionListener objects registered on the checkboxes are completely independent of the Action objects registered on the action-aware components.)This control structure makes it possible for the user to disable and enable the two Action objects independently of one another simply by checking or clearing the checkboxes.
This control structure also makes it possible to toggle the icons between
two different images on each Action object when the Action
object is enabled.
ckBox01.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ if(e.getActionCommand(). equals("Disable01")){ ckBox01.setText( "Enable01"); actionObj01.setEnabled( false); //Disable ability to toggle // the IconImage. ckBox03.setEnabled(false); }else{ ckBox01.setText( "Disable01"); actionObj01.setEnabled( true); //Enable ability to toggle // the IconImage. ckBox03.setEnabled(true); }//end else }//end actionPerformed() }//end ActionListener );//end addActionListener Listing 17 |
Two important statements
Although the code in Listing 17 appears to be long and complex, it is relatively straightforward.
The actionPerformed method shown in Listing 17 is invoked when the checkbox fires an action event (when the user checks or clears the checkbox). Let me draw your attention to the two boldface statements in Listing 17. These two statements use the current state of the checkbox object to make a decision between two alternatives, and then to change the value of the enabled property of the Action object referred to by actionObj01. (The state of the checkbox object is also changed accordingly.)
When the value of the enabled property of the Action object is changed, the three components registered earlier as change listeners on the Action object are notified of the change. The code in the classes from which those components were instantiated causes them to become enabled or disabled accordingly. In other words, the enabled state of all three components tracks the enabled property of the Action object.
The other Action object
The code in Listing 18 uses a different checkbox object to provide essentially
the same enabled/disabled control for the other Action object
and its associated components. Since this code replicates the code
in Listing 17, I deleted most of it for brevity. You can view all
of the code in Listing 22 near the end of this lesson.
ckBox02.addActionListener( //...code deleted for brevity );//end addActionListener Listing 18 |
Manipulate the icons
Listing 19 is an anonymous inner listener class that makes it possible for the user to manipulate a keyed property value of an Action object, the value of which specifies the icon to be displayed on registered components.
Basically, this code causes the value of the property that specifies
the icon displayed on the components to toggle between two different ImageIcon
objects based on two different gif files.
ckBox03.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ //Get file name for the // ImageIcon object. String gif = (actionObj01. getValue( Action.SMALL_ICON)). toString(); if((gif).equals( "redball.gif")){ actionObj01.putValue( Action.SMALL_ICON, new ImageIcon( "blueball.gif")); }else{ actionObj01.putValue( Action.SMALL_ICON, new ImageIcon( "redball.gif")); }//end else }//end actionPerformed() }//end ActionListener );//end addActionListener Listing 19 |
Change the displayed icon
The logic in Listing 19 is straightforward. When the checkbox fires an action event, the code in the actionPerformed method uses the getValue method of the Action interface to get the name of the gif file that specifies the icon. Then it changes the value of that keyed property in the Action object to an ImageIcon object based on the other gif file, thereby toggling the value between two ImageIcon objects based on the two different gif files.
When the value of this keyed property changes, the three components that were earlier registered as change listeners on the Action object are notified of the change. The code in the classes from which the three components were instantiated causes the icon that is displayed on the component to track the corresponding property value of the Action object.
Do it again for the other Action object
Listing 20 provides the same icon toggling capability for the other
Action
object. Once again, since this code replicates the code in Listing
19, I deleted most of it for brevity. You can view all of the code
in Listing 22 near the end of this lesson.
ckBox04.addActionListener( //... code deleted for brevity );//end addActionListener Listing 20 |
Finally, the end of the program
Listing 21 is a simple WindowListener that terminates the program when
the user clicks the close button on the JFrame object.
This is relatively standard material, which should not require a discussion
in this context.
this.addWindowListener( new WindowAdapter(){ public void windowClosing( WindowEvent e){ System.exit(0); }//end windowClosing() }//end WindowAdapter );//end addWindowListener }//end constructor }//end GUI class Listing 21 |
Listing 21 is also the end of the GUI class definition.
(Note that you will need a couple of gif files containing small images to use as ImageIcon objects. Any small image will do. Just be sure to handle the file names properly.)
/*ActionObj02.java Rev 03/30/02 Copyright 2002, R.G.Baldwin Illustrates use of action objects. Uses the setAction method that was released with V1.3 Tested using JDK 1.4.0 under Win2000. **************************************/ import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; public class ActionObj02{ public static void main( String[] args){ new GUI(); }//end main }//end ActionObj02 //===================================// class GUI extends JFrame{ //Create three containers JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("Menu"); JToolBar toolBar = new JToolBar(); JPanel panel = new JPanel(); //Create two Action objects Action actionObj01 = new MyAction(); Action actionObj02 = new MyAction(); //Create four check boxes and a // control panel, which will be used // to manipulate the Action objects // and their visual components. JCheckBox ckBox01 = new JCheckBox( "Disable01"); JCheckBox ckBox02 = new JCheckBox( "Disable02"); JCheckBox ckBox03 = new JCheckBox( "Toggle Icon01"); JCheckBox ckBox04 = new JCheckBox( "Toggle Icon02"); JPanel controlPanel = new JPanel(); GUI(){//constructor //Construct and decorate the menu // and put it in place. menuBar.add(menu); menuBar.setBorder( new BevelBorder( BevelBorder.RAISED)); setJMenuBar(menuBar); //Decorate the JToolBar and place // it in the North position. toolBar.setBorder(new BevelBorder( BevelBorder.RAISED)); getContentPane().add( toolBar,BorderLayout.NORTH); //Decorate the JPanel and place it // in the South position. panel.setBorder(new BevelBorder( BevelBorder.RAISED)); getContentPane().add( panel,BorderLayout.SOUTH); //Set some keyed properties for // each of the two Action objects. actionObj01.putValue( Action.NAME,"actionObj01"); actionObj01.putValue( Action.SMALL_ICON,new ImageIcon( "redball.gif")); actionObj01.putValue( Action.SHORT_DESCRIPTION, "ToolTip for actionObj01"); actionObj02.putValue( Action.NAME,"actionObj02"); actionObj02.putValue( Action.SMALL_ICON,new ImageIcon( "bulb2.gif")); actionObj02.putValue( Action.SHORT_DESCRIPTION, "ToolTip for actionObj02"); //Put a JMenuItem on the menu. Set // its Action object using the // setAction method. This is // one approach. JMenuItem mnuA1 = new JMenuItem(); mnuA1.setAction(actionObj01); menu.add(mnuA1); //Put a JMenuItem on the menu. Set // its Action object in the // constructor. This is a // different approach. JMenuItem mnuA2 = new JMenuItem(actionObj02); menu.add(mnuA2); //Put a JButton on the toolbar. // Set its Action object using the // setAction method. Also register // an ordinary Action listener // on it. JButton butB1 = new JButton(); butB1.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ System.out.println( "Ordinary Action Listener"); }//end actionPerformed() }//end ActionListener );//end addActionListener butB1.setAction(actionObj01); toolBar.add(butB1); //Put a JButton on the toolbar. // Set its Action object. JButton butB2 = new JButton(); butB2.setAction(actionObj02); toolBar.add(butB2); //Put a JButton on the JPanel. // Set its Action object. JButton butC = new JButton(); butC.setAction(actionObj01); panel.add(butC); //Put a JMenuItem on the JPanel. // Set its Action object. JMenuItem mnuC = new JMenuItem(); mnuC.setAction(actionObj02); panel.add(mnuC); //Construct the control panel and // put it in the Center controlPanel.setLayout( new GridLayout(2,2,3,3)); controlPanel.add(ckBox01); controlPanel.add(ckBox02); controlPanel.add(ckBox03); controlPanel.add(ckBox04); getContentPane().add(controlPanel, BorderLayout.CENTER); //Set the size and make the GUI // visible setSize(350,200); setVisible(true); setTitle("Copyright 2002, " + "R.G.Baldwin"); //The following anonymous inner // allow the user to manipulate the // Action objects and their // associated visual components. //Register ActionListener objects // on each of the check boxes. // This makes it possible to // disable and enable the two // Actions objects independently of // one another. It also makes it // possible to toggle the icons // between two different images // on each Action object when the // Action object is enabled. ckBox01.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ if(e.getActionCommand(). equals("Disable01")){ ckBox01.setText( "Enable01"); actionObj01.setEnabled( false); //Disable ability to toggle // the IconImage. ckBox03.setEnabled(false); }else{ ckBox01.setText( "Disable01"); actionObj01.setEnabled( true); //Enable ability to toggle // the IconImage. ckBox03.setEnabled(true); }//end else }//end actionPerformed() }//end ActionListener );//end addActionListener ckBox02.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ if(e.getActionCommand(). equals("Disable02")){ ckBox02.setText( "Enable02"); actionObj02.setEnabled( false); //Disable ability to toggle // the IconImage. ckBox04.setEnabled(false); }else{ ckBox02.setText( "Disable02"); actionObj02.setEnabled( true); //Enable ability to toggle // the IconImage. ckBox04.setEnabled(true); }//end else }//end actionPerformed() }//end ActionListener );//end addActionListener ckBox03.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ //Get file name for the // ImageIcon object. String gif = (actionObj01. getValue( Action.SMALL_ICON)). toString(); if((gif).equals( "redball.gif")){ actionObj01.putValue( Action.SMALL_ICON, new ImageIcon( "blueball.gif")); }else{ actionObj01.putValue( Action.SMALL_ICON, new ImageIcon( "redball.gif")); }//end else }//end actionPerformed() }//end ActionListener );//end addActionListener ckBox04.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ //Get file name for the // ImageIcon object. String gif = (actionObj02. getValue( Action.SMALL_ICON)). toString(); if((gif).equals( "bulb2.gif")){ actionObj02.putValue( Action.SMALL_ICON, new ImageIcon( "bulb1.gif")); }else{ actionObj02.putValue( Action.SMALL_ICON, new ImageIcon( "bulb2.gif")); }//end else }//end actionPerformed() }//end ActionListener );//end addActionListener //Create a WindowListener used // to terminate the program this.addWindowListener( new WindowAdapter(){ public void windowClosing( WindowEvent e){ System.exit(0); }//end windowClosing() }//end WindowAdapter );//end addWindowListener }//end constructor }//end GUI class //===================================// //This is the class from which the // Action objects are instantiated. class MyAction extends AbstractAction{ public void actionPerformed( ActionEvent e){ System.out.println("Action: " + ((AbstractButton)e.getSource()). getActionCommand()); }//end actionPerformed }//end class MyAction Listing 22 |
Copyright 2002, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
Richard has participated in numerous consulting projects, and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
-end-