Wednesday, 25 May 2011

Events and the ActionListener

The ActionListener event handlers are an implementation of the Observer pattern that is woven throughout Java’s Swing classes. It’s used by buttons to inform their clients that they’ve been pushed, it’s used by menu items to tell the application that a selection has been made, by the JFileChooser to select files and by many other classes.

Although, primarily used by Swing components, you can easily use this pattern anywhere, it’s simply a matter of implementing the ActionListener interface and the

    public void actionPerformed(ActionEvent e)

...method.

The example below demonstrates an ActionListener event handler in the form of a simple news server and client. The first thing to do is to implement the server. This class keeps hold of a few headlines whilst running a simple thread. Every now and again, the thread will pick-out a headline and send it to all listeners. When using Swing components, the methods implemented by this server would be implemented by classes such as JButton, JScrollBar, JTextField etc.

public class NewsServer extends Thread {

 
private static Random rnd = new Random();

 
private static final String[] dummyHeadlines = { //
 
"War time bomber found on the Moon", "There is no more news at the moment", //
     
"Monster Raving Looney Party in landslide election win", //
     
"Spam irradicated from the Internet", "Life found on Mars", //
     
"And the latest lottery winner is...", "Gramatical errors found in news headlines" };

 
private final List<ActionListener> listeners = new CopyOnWriteArrayList<ActionListener>();

 
/**
   * In this model, all classes that initiate events implement an addXXXListener(...) method;
   * where XXX is the name of the event that we're interested in. In this case, it's the
   * standard ActionEvent, but we can define our own by extending the java.util.EventObject
   * class.
   */
 
public void addActionListener(ActionListener l) {

   
listeners.add(l);
 
}

 
public synchronized void removeActionListener(ActionListener l) {

   
listeners.remove(l);
 
}

 
/**
   * The run method for the thread chooses a headline and then sends it to each of the
   * listeners. This is achieved by using a random number generator and, at the appropriate
   * juncture, traversing the ActionListerner list, creating a new ActionEvent class and
   * feeding it to the list item's actionPerform method.
   */
 
@Override
 
public void run() {

   
int counter = 1;

   
while (counter < 11) {

     
if (isNewHeadline()) {
       
String headlineText = chooseHeadLine();
        notifyListeners
(counter++, headlineText);
     
}
     
sleep(1);
   
}
  }

 
private boolean isNewHeadline() {

   
// give ourselves a one in three
    // chance of distributing a story
   
return rnd.nextInt(3) == 1;
 
}

 
private String chooseHeadLine() {

   
// Next choose the headline id
   
int headlineId = rnd.nextInt(dummyHeadlines.length);
   
return dummyHeadlines[headlineId];
 
}

 
private void notifyListeners(int counter, String headlineText) {

   
for (ActionListener l : listeners) {
     
ActionEvent e = new ActionEvent(this, counter, headlineText);
      l.actionPerformed
(e); // perform the action
   
}
  }

 
private void sleep(int time) {

   
try {
     
SECONDS.sleep(time);
   
} catch (InterruptedException e) {
     
/* do nothing just continue */
   
}
  }
}

This code is complimented by the NewsClient code. By convention, the JDK implements the ActionListener interface using an inner class and the NewsClient sticks to this convention.

public class NewsClient {

 
/**
   * The standard way to implement events is to use an inner class.
   */
 
class myListener implements ActionListener {
   
/**
     *
<code>actionPerformed()</code> method from the <code>ActionListener</code>
    
* interface. This implementation just prints out the news to the console.
     *
     *
@param e The action event object containing our news.
     */
   
@Override
   
public void actionPerformed(ActionEvent e) {

     
System.out.println("More News: " + e.getActionCommand() + ", id " + e.getID());
   
}
  }

 
public ActionListener getActionListener() {

   
return new myListener();
 
}
}

The NewsApp code holds the client and server together, demonstrating that your server code should be able to handle multiple listeners. This code runs until a maximum of 10 random headlines have been distributed to the listeners, which, in this case, simply display them on the screen.

public class NewsApp {

 
private final NewsServer newsServer;

 
private final NewsClient ticker;

 
public NewsApp(NewsServer newsServer, NewsClient ticker) {

   
this.newsServer = newsServer;
   
this.ticker = ticker;
 
}

 
/**
   * Simple method that uses a news server object and then waits whilst it prints out news
   * for a certain amount of time. Note that just to make things more interesting, two
   * ActionListeners are added server, just to demonstrate that it can handle this situation.
   */
 
public void printNews() {

   
System.out.println("Kicking off the news service");

   
// add a listener
   
ActionListener listener = ticker.getActionListener();
    newsServer.addActionListener
(listener);
    newsServer.start
(); // let the server run

   
try {
     
TimeUnit.SECONDS.sleep(7);

     
// to spice things up abit - add another listener here
     
System.out.println("adding second listener");
      newsServer.addActionListener
(ticker.getActionListener());

      TimeUnit.SECONDS.sleep
(5);

      System.out.println
("remove the first listener");
      newsServer.removeActionListener
(listener);

      newsServer.join
(); // now wait for the server to complete

   
} catch (InterruptedException e) {
     
System.out.println("Thread interrupted " + e.getMessage());
   
}
  }

 
/**
   * Main method used to test what I'm talking about
   */
 
public static void main(String[] args) {

   
NewsApp app = new NewsApp(new NewsServer(), new NewsClient());
    app.printNews
();
 
}
}


Job Done...

No comments: