You have used source components such as JButton . This section demonstrates how to create a custom source component.
A source component must have the appropriate registration and deregistration methods for adding and removing listeners. Events can be unicasted (only one listener object is notified of the event) or multicasted (each object in a list of listeners is notified of the event). The naming pattern for adding a unicast listener is
public void add< Event >Listener(< Event >Listener l) throws TooManyListenersException;
The naming pattern for adding a multicast listener is the same, except that it does not throw the TooManyListenersException .
public void add< Event >Listener(< Event >Listener l)
The naming pattern for removing a listener (either unicast or multicast) is:
public void remove< Event >Listener(< Event >Listener l)
A source component contains the code that creates an event object and passes it to the listening components by calling a method in the listener's event listener interface. You may use a standard Java event class like ActionEvent to create event objects or may define your own event classes if necessary.
The Course class in §7.16, "Case Study: The Course Class," models the courses. Suppose a Course object fires an ActionEvent when the number of students for the course exceeds a certain enrollment cap. The new class named CourseWithActionEvent is shown in Figure 27.5.
The source component is responsible for registering listeners, creating events, and notifying listeners by invoking the methods defined in the listeners' interfaces. The CourseWithActionEvent component is capable of registering multiple listeners, generating ActionEvent objects when the enrollment exceeds the cap, and notifying the listeners by invoking the listeners' actionPerformed method. Listing 27.2 implements the new class.
1 import java.util.*; 2 import java.awt.event.*; 3 4 public class CourseWithActionEvent { 5 private String name = "default name" ; 6 private ArrayList<String> students = new ArrayList<String>(); 7 private int enrollmentCap = 10 ; 8 9 private ArrayList<ActionListener> actionListenerList; 10 11 public CourseWithActionEvent() { 12 } 13 14 public CourseWithActionEvent(String name) { 15 this .name = name; 16 } 17 18 public void addStudent(String student) { 19 students.add(student); 20 21 if (students. size () > enrollmentCap) { 22 // Fire ActionEvent 23 processEvent( new ActionEvent( this , 24 ActionEvent.ACTION_PERFORMED, null )); 25 } 26 } 27 28 public String[] getStudents() { 29 return (String[])students.toArray(); 30 } 31 32 public int getNumberOfStudents() { 33 return students.size(); 34 } 35 36 public int getEnrollmentCap() { 37 return enrollmentCap; 38 } 39 40 public void setEnrollmentCap( int enrollmentCap) { 41 this .enrollmentCap = enrollmentCap; 42 } 43 44 /** Register an action event listener */ 45 public synchronized void addActionListener 46 (ActionListener listener) { 47 if (actionListenerList == null ) { 48 actionListenerList = new ArrayList<ActionListener>( 2 ); 49 } 50 51 if (!actionListenerList.contains(listener)) { 52 actionListenerList.add(listener); 53 } 54 } 55 56 /** Remove an action event listener */ 57 public synchronized void removeActionListener 58 (ActionListener listener) { 59 if (actionListenerList != 60 null && actionListenerList.contains(listener)) { 61 actionListenerList.remove(listener); 62 } 63 } 64 65 /** Fire ActionEvent */ 66 private void processEvent(ActionEvent e) { 67 ArrayList list; 68 69 synchronized ( this ) { 70 if (actionListenerList == null ) return ; 71 list = (ArrayList)actionListenerList.clone(); 72 } 73 74 for ( int i = ; i < list.size(); i++) { 75 ActionListener listener = (ActionListener)list.get(i); 76 listener.actionPerformed(e); 77 } 78 } 79 } |
Since the source component is designed for multiple listeners, a java.util.ArrayList instance actionListenerList is used to hold all the listeners for the source component (line 9). The data type of the elements in the array list is ActionListener . To add a listener, listener , to actionListenerList , use
actionListenerList.add(listener); (line 52 )
To remove a listener, listener , from actionListenerList , use
actionListenerList.remove(listener); (line 61 )
The if statement (lines 47 “49) ensures that the addActionListener method does not add the listener twice if it is already in the list. The removeActionListener method removes a listener if it is in list. actionListenerList is an instance of ArrayList , which functions as a flexible array that can grow or shrink dynamically. Initially, actionListenerList is new ArrayList(2) (line 48), which implies that the capacity of the list is 2, but the capacity can be changed dynamically. If more than two listeners are added to actionListenerList , the list size will be automatically increased.
Note
Instead of using ArrayList , you can also use javax.swing.event.EventListenerList to store listeners. Using EventListenerList is preferred, since it provides the support for synchronization and is efficient in the case of no listeners. |
The addActionListener and removeActionListener methods are synchronized to prevent data corruption on actionListenerList when attempting to register multiple listeners concurrently (lines 45, 57).
The addStudent method adds a new student to the course and checks whether the number of students is more than the enrollment cap. If so, it creates an ActionEvent and invokes the processEvent method to process the event (lines 23 “24).
The UML diagram for ActionEvent is shown in Figure 27.2. To create an ActionEvent , use the constructor
ActionEvent(Object source, int id, String command)
where source specifies the source component, id identifies the event, and command specifies a command associated with the event. Use ActionEvent.ACTION_PERFORMED for the id . If you don't want to associate a command with the event, use null .
The processEvent method (lines 66 “78) is invoked when an ActionEvent is generated. This notifies the listeners in actionListenerList by calling each listener's actionPerformed method to process the event. It is possible that a new listener may be added or an existing listener may be removed when processEvent is running. To avoid corruption on actionListenerList , a clone list of actionListenerList is created for use to notify listeners. To avoid corruption when creating the clone, invoke it in a synchronized block, as in lines 69 “72:
synchronized ( this ) { if (actionListenerList == null ) return ; list = (ArrayList)actionListenerList.clone(); }
Listing 27.3 gives a test program that creates a course using the new class (line 5), sets the enrollment cap to 2 (line 8), registers a listener (line 10), and adds three students to the course (lines 11 “13). When line 13 is executed, the addStudent method adds student Tim to the course and fires an ActionEvent because the course exceeds the enrollment cap. The course object invokes the listener's actionPerformed method to process the event and displays a message Enrollment cap exceeded .
1 import java.awt.event.*; 2 3 public class TestCourseWithActionEvent { 4 CourseWithActionEvent course = 5 new CourseWithActionEvent( "Java Programming" ); 6 7 public TestCourseWithActionEvent() { 8 course.setEnrollmentCap( 2 ); 9 ActionListener listener = new Listener(); 10 course.addActionListener(listener); 11 course.addStudent( "John" ); 12 course.addStudent( "Jim" ); 13 course.addStudent( "Tim" ); 14 } 15 16 public static void main(String[] args) { 17 new TestCourseWithActionEvent(); 18 } 19 20 private class Listener implements ActionListener { 21 public void actionPerformed(ActionEvent e) { 22 System.out.println( "Enrollment cap exceeded" ); 23 } 24 } 25 } |
The flow of event processing from the source to the listener is shown in Figure 27.6.