HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

What is a Callback?
A callback is a mechanism by which the user's action on a software application's graphical user interface (GUI) is connected to the code implementing the application's response to this action. It is a familiar concept to X Toolkit and Motif programmers. In reality, the actual callback is the part of the callback mechanism that implements the application response. In this sense, a callback is also known as a command in object-oriented circles. This article describes how to implement support for a Java version of the X Toolkit/Motif-style callback for AWT.

Advantages of Using Callbacks
There are many advantages of using callbacks instead of standard AWT event handling:

  • It clearly separates the application's GUI code from the code implementing the application's response to any user interaction with the GUI.
  • For those programming in Java 1.0, there is no need to subclass an AWT component to implement an action, thus preventing subclass proliferation.
  • For those programming in JDK 1.1, Listeners instances are centralized in one location.
  • More importantly, Java version-dependent code is localized in one class. Thus, application code written using the callback mechanism achieves Java version independence. In other words, an application may be ported easily from JDK 1.0 to 1.1 without rewriting a single event handling code.
How Callback Works
Figure 1 shows the workings of the callback mechanism. It shows the relationship of the event originator, the event handler and the callback. The event handler is the centralized location where all AWT events are delivered, and it decides whether any application response code is interested in processing this event. This is implemented in the CallbackList class. The AWT component is where the event originated has to be extended or subclassed to hand off events to the centralized event handler rather than processing the event itself. The programmer supplies the application response callback code by extending the Callbackable class, and registers this code with the extended AWT component.

Figure 1
Figure 1:

The Callbackable Class
Callbackable is implemented as an abstract class to enforce a uniform structure to all callback code. The callback code is where the programmer implements the application's response to an event. This is necessary because the CallbackList expects a specific method in a registered callback; specifically, public abstract void boolean activate( Event e ). Information related to the component and the event that triggers this code can be retrieved from the Event object that is passed as the parameter. The actual implementation of the Callbackable interface is very simple and is shown in Listing 1.

The CallbackList Class
The CallbackList maintains a list of registered instances of Callbackable. This is also where the majority of the code for handling the AWT differences between Java 1.0 and 1.1 is located. In 1.0, the CallbackList is fairly simple. It extends the Vector class and keeps a list of registered instances of Callbackable. In JDK 1.1, this class becomes a little bit more complicated as it also has to act as the Listener of all AWT components. Listing 2 shows the complete source code for the Java 1.0 version of CallbackList, and Listing 3 shows the same for Java 1.1. When the programmer registers an instance of a concrete subclass of Callbackable, it gets added to the CallbackList. Multiple instances of Callbackable may be registered for a single AWT component in CallbackList. When an interaction occurs with an AWT component, the event is passed to a specific instance of the CallbackList for that AWT component, which goes through its Vector of registered instances of Callbackable and activates each one in turn.

Adding Support for Callbackable in AWT
A user action reaches an instance of an AWT component as an event. This event must be passed on to the CallbackList where it can be redirected to the appropriate Callbackable. To do this, an AWT component must be extended. Listings 4 and 5 show the extended AWT Button as an example for Java 1.0 and 1.1 respectively, but other AWT components can be extended the same way. In implementing the extension to AWT Button, one has to decide what events or compound events are important and a CallbackList instantiated to represent each. In this case, it will be the activate (mouse click), arm (mouse down) and disarm (mouse up) events. So, the activateCallbackList, armCallbackList and disarmCallbackList are created for it. The programmer registers an instance of a Callbackable using the addCallback method. AddCallback simply adds a Callbackable to the appropriate CallbackList. Instead of requiring the programmer to subclass Button and write a new handleEvent code for each button that was instantiated, the programmer inserts the operation code in the subclass of Callbackable itself. This enforces the division between GUI code and operational code and makes the code more reliable and reusable.

Using Callback
Listing 6 shows a sample program that uses the "enhanced" Button class. A concrete subclass of Callbackable is instantiated and added to an instance of the Button class. Note the use of a constructor and the set methods to pass extra information to the Callbackable. To find out which component triggered this callback, e.target is used. A single application will suffice for both Java 1.0 and 1.1 because code specific to the Java version has been encapsulated in the implementation of callback support.

Conclusion
In many ways, the callback mechanism is similar to listeners in Java 1.1, where the actual operational code is delegated to an instance of the Listener class. However, with listeners, an event specific method in an event source specific class is triggered for each specific event. For example, a mouse click will trigger the mouseClicked method in MouseListener. While with callbacks, there is only one class, Callbackable, and only one method, activate, to remember. In addition, implementing a callback layer over Java's own event handling allows one's application to better survive any future AWT paradigm shift.

The follow-up article to this will discuss how to enforce uniformity in the callback-extended AWT components by using the Widget interface class.

Download Source Code
Source code for this article can be downloaded free from http://www.wigitek.com. A fully implemented version containing extended AWT code can also be purchased from Wigitek Corporation at the same Web site.

About the Author
Daniel Dee has more than 10 years of experience working in the development of GUI software toolkits, starting with X Windows and then Java, since their inception. He is currently the president of Wigitek Corporation, a company providing software tools and consulting services for the development Java-based data-driven dynamic graphics software. He received an MS degree in Computer System Engineering from the University of Massachusetts. Daniel can be reached at [email protected]

	

Listing 1.
  
/**  
 * Copyright (c) 1997 Daniel Dee  
 * Description:  
   Package contains common classes for the ViviGraphics Widget  
   Toolkit - a callback-based toolkit.  
   Originally, part of the Eva Toolkit - the prototype  
   implementation.  
 */  
package com.wigitek.vivigraphics.widget.common;  

import java.awt.Event;  

/**  
 * This class implements the callback mechanism that is required  
   to support the Widget interface. You will typically subclass  
   this to implement your own activate method in order to perform  
   functions specific to events that trigger the callback.  
 * @version $Revision$  
 * @author Originally written by Daniel Dee, 3/17/97  
 * @author Last updated by $Author$, $Date$  
 */  
public abstract class Callbackable extends Object  
{  
    /**  
     * Performs functions specific to the event that triggers this  
       callback. By default, it prints information about the activating  
       object when this callback was registered and the activating event.   
       You should normally override this method to perform function   
       specific to your application.  
     * @param evt        the event that triggers this callback  
     */  
    public abstract boolean activate( Event e );  
}  

Listing 2.
  
/**  
 * Copyright (c) 1997 Daniel Dee  
 * Description:  
   Package contains common classes for the Vivigraphics Widget  
   Toolkit - a callback-based callback-based toolkit.  
   Originally, part of the Eva Toolkit - the prototype  
   implementation.  
 */  
package com.wigitek.vivigraphics.widget.gui;  

import java.awt.Event;  
import java.util.Vector;  
import java.io.IOException;  
import com.wigitek.vivigraphics.widget.common.Callbackable;  

/**  
 * This class chains callbacks associated with a single callback type  
   together.  
 * @version $Revision$  
 * @author Originally written by Daniel Dee, 3/17/97  
 * @author Last updated by $Author$, $Date$  
 */  
public class CallbackList extends Vector  
{  
    /**  
     * Constructs a CallbackList. Creates a Vector with 100 initial elements  
       and a increment size of 100.  
     */  
    public CallbackList()  
    {  
        super(100, 100);  
    }  

    /**  
     * Removes a Callbackable from the CallbackList.  
     * @param cb the callback  
     */  
    public boolean remove( Callbackable cb )  
    {  
        boolean returnValue = false;  

        for( int i=0; i < size(); i++ )  
        {  
            Callbackable _cb = (Callbackable)elementAt(i);  
            if( _cb == cb )  
            {  
                removeElementAt(i);  
                returnValue = true;  
            }  
        }  
        return returnValue;  
    }  

    /**  
     * Removes a CallbackClientPair at the indexed position.  
     * @param index the position of the CallbackClientPair in the  
                    CallbackList.  
     */  
    public boolean removeAt( int index )  
    {  
        try  
        {  
            removeElementAt( index );  
            return true;  
        }  
        catch( ArrayIndexOutOfBoundsException e )  
        {  
            return false;  
        }  
    }  

    /**  
     * Adds a callback to a chain.  
     * @param callback     the callback  
     * @return the Callbackable  
     */  
    public Callbackable add( Callbackable cb )  
    {  
     if( cb == null )  
      cb = defaultCB;  
        addElement( cb );  
        return cb;  
    }  

    /**  
     * Calls all the callbacks in this chain.  
     * @param evt the event that triggers this callbacklist  
     * @return true if event has been processed by the callback  
               and no further processing is necessary; false  
               if further processing from the activating object  
               is required.  
     */  
    public boolean activate( Event e )  
    {  
        boolean returnValue = true;  

        for( int i=0; i < size(); i++ )  
        {  
            Callbackable cb = (Callbackable)elementAt(i);  
            returnValue = cb.activate( e );  
        }  

        return returnValue;  
    }  
      
    private CallbackListDefaultCallbackable defaultCB =   
       new CallbackListDefaultCallbackable();  
}  

/**  
 * Default Callbackable.  
 */  
class CallbackListDefaultCallbackable extends Callbackable  
{  
    /**  
     * Prints information about the activating  
       object.  
     * @param evt        the event that triggers this callback  
     */  
    public boolean activate( Event evt )  
    {  
        System.out.println( "Calling object is " + evt.target.toString() + "." );  
        System.out.println( "Activating event is " + evt.toString() + "." );  
        return true;  
    }  
}  

Listing 3.
  
/**  
 * Copyright (c) 1997 Daniel Dee  
 * Description:  
   Package contains common classes for the Vivigraphics Widget  
   Toolkit - a callback-based callback-based toolkit.  
   Originally, part of the Eva Toolkit - the prototype  
   implementation.  
 */  
package com.wigitek.vivigraphics.widget.gui;  

import java.awt.Event;  
import java.awt.AWTEvent;  
import java.util.Vector;  
import java.io.IOException;  
import java.awt.event.*;  
import com.wigitek.vivigraphics.widget.common.Callbackable;  

/**  
 * This class chains callbacks associated with a single callback type  
   together.  
 * @version $Revision$  
 * @author Originally written by Daniel Dee, 3/17/97  
 * @author Last updated by $Author$, $Date$  
 */  
public class CallbackList extends Vector  
                     implements ActionListener,  
                                AdjustmentListener,  
                                ComponentListener,  
                                ContainerListener,  
                                FocusListener,  
                                ItemListener,  
                                KeyListener,  
                                MouseListener,  
                                MouseMotionListener,  
                                TextListener,  
                                WindowListener  
{  
    /**  
     * Constructs a CallbackList. Creates a Vector with 100 initial elements  
       and a increment size of 100.  
     */  
    public CallbackList()  
    {  
        super(100, 100);  
    }  

    /**  
     * This method is called when an action event occurs inside the  
       source object. The corresponding callbacks added  
       by the user are activated.  
     * @param evt the event  
     */  
    public void actionPerformed(ActionEvent evt)  
    {  
     Event e = new Event( evt.getSource(), evt.getID(), evt );  
        activate(e);  
    }  

 // TO BE IMPLEMENTED  
    public void adjustmentValueChanged(AdjustmentEvent evt) {}  
    public void componentResized(ComponentEvent evt) {}  
    public void componentMoved(ComponentEvent evt) {}  
    public void componentShown(ComponentEvent evt) {}  
    public void componentHidden(ComponentEvent evt) {}  
    public void componentAdded(ContainerEvent evt) {}  
    public void componentRemoved(ContainerEvent evt) {}  
    public void focusGained(FocusEvent evt) {}  
    public void focusLost(FocusEvent evt) {}  
    public void itemStateChanged(ItemEvent evt) {}  
    public void keyTyped(KeyEvent evt) {}  
    public void keyPressed(KeyEvent evt) {}  
    public void keyReleased(KeyEvent evt) {}  
    public void mouseClicked(MouseEvent evt) {}  
    public void mousePressed(MouseEvent evt) {}  
    public void mouseReleased(MouseEvent evt) {}  
    public void mouseEntered(MouseEvent evt) {}  
    public void mouseExited(MouseEvent evt) {}  
    public void mouseDragged(MouseEvent evt) {}  
    public void mouseMoved(MouseEvent evt) {}  
    public void textValueChanged(TextEvent evt) {}  
    public void windowOpened(WindowEvent evt) {}  
    public void windowClosing(WindowEvent evt) {}  
    public void windowClosed(WindowEvent evt) {}  
    public void windowIconified(WindowEvent evt) {}  
    public void windowDeiconified(WindowEvent evt) {}  
    public void windowActivated(WindowEvent evt) {}  
    public void windowDeactivated(WindowEvent evt) {}  

    /**  
     * Removes a Callbackable from the CallbackList.  
     * @param cb the callback  
     */  
    public boolean remove( Callbackable cb )  
    {  
        boolean returnValue = false;  

        for( int i=0; i < size(); i++ )  
        {  
            Callbackable _cb = (Callbackable)elementAt(i);  
            if( _cb == cb )  
            {  
                removeElementAt(i);  
                returnValue = true;  
            }  
        }  
        return returnValue;  
    }  

    /**  
     * Removes a CallbackClientPair at the indexed position.  
     * @param index the position of the CallbackClientPair in the  
                    CallbackList.  
     */  
    public boolean removeAt( int index )  
    {  
        try  
        {  
            removeElementAt( index );  
            return true;  
        }  
        catch( ArrayIndexOutOfBoundsException e )  
        {  
            return false;  
        }  
    }  

    /**  
     * Adds a callback to a chain.  
     * @param callback     the callback  
     * @return the Callbackable  
     */  
    public Callbackable add( Callbackable cb )  
    {  
     if( cb == null )  
      cb = defaultCB;  
        addElement( cb );  
        return cb;  
    }  

    /**  
     * Calls all the callbacks in this chain.  
     * @param evt the event that triggers this callbacklist  
     * @return true if event has been processed by the callback  
               and no further processing is necessary; false  
               if further processing from the activating object  
               is required.  
     */  
    public boolean activate( Event e )  
    {  
        boolean returnValue = true;  

        for( int i=0; i < size(); i++ )  
        {  
            Callbackable cb = (Callbackable)elementAt(i);  
            returnValue = cb.activate( e );  
        }  

        return returnValue;  
    }  
      
    private CallbackListDefaultCallbackable defaultCB =   
       new CallbackListDefaultCallbackable();  
}  

/**  
 * Default Callbackable.  
 */  
class CallbackListDefaultCallbackable extends Callbackable  
{  
    /**  
     * Prints information about the activating  
       object.  
     * @param evt        the event that triggers this callback  
     */  
    public boolean activate( Event evt )  
    {  
        System.out.println( "Calling object is " + evt.target.toString() + "." );  
        System.out.println( "Activating event is " + evt.toString() + "." );  
        return true;  
    }  
} 
 
Listing 4.
  
/**  
 * Copyright (c) 1997 Daniel Dee  
 * Description:  
   Package contains common classes for the ViviGraphics Widget  
   Toolkit - a callback-based toolkit.  
   Originally, part of the Eva Toolkit - the prototype  
   implementation.  
 */  
package com.wigitek.vivigraphics.widget.gui;  

import java.awt.Event;  
import java.lang.String;  
import com.wigitek.vivigraphics.widget.common.Callbackable;  
import com.wigitek.vivigraphics.widget.gui.CallbackList;  

/**  
 * This class extends the java.awt.Button class to support  
   the callback mechanism by implementing the Widget interface.  
 * @version $Revision$  
 * @author Originally written by Daniel Dee, 2/21/97  
 * @author Last updated by $Author$, $Date$  
 * @see Widget  
 */  
public class Button extends java.awt.Button  
{  
    /**  
     * Constructs a Button with no label and with name "unnamed".  
     */  
    public Button()  
    {  
        this( "", "unnamed" );  
    }  

    /**  
     * Constructs a Button with a string label and with name "unnamed".  
     * @param label the specified label  
     */  
    public Button( String label )  
    {  
        this(label, "unnamed");  
    }  

    /**  
     * Constructs a Button with a string label and with given name.  
     * @param label the specified label  
     * @param name  the specified name  
     */  
    public Button( String label, String name )  
    {  
        super(label);  
        this.name = name;  
    }  

    /**  
     * Returns a String that represents the value of this Object.  
       Overrides the method in java.lang.Object.  
     * @return a String  
     * @see Object  
     */  
    public String toString()  
    {  
        String string = super.toString();  
        int length = string.length();  
        return string.substring( 0, length-1 ) + ",name=" + name + "]";  
    }  

     /**  
     * Registers a callback for the top level frame. Top level frame  
       currently recognizes only ACTIVATE_CALLBACK when the particular  
       instance of the button is pushed. Unknown callbacks  
       are ignored.  
     * @param callbackName the type of callback to register  
     * @param callback     the object of the Callbackable class that will activated  
                           when trigger by the callback type given by callbackName  
     * @see Callbackable  
     */  
    public void addCallback( String callbackName, Callbackable callback )  
    {  
        if( callbackName.compareTo(ACTIVATE_CALLBACK) == 0 )  
            activateCallbackList.add( callback );  
    }  

    /**  
     * This method is called when an action event occurs inside this  
       Button. Depending on the event, the corresponding callbacks added  
       by the user are activated.  
     * The method returns true to indicate that it has successfully  
       handled the action; or false if the event that triggered  
       the action should be passed up to the component's parent.  
     * @param evt the event  
     * @return true if the event has been handled and no further  
               action is necessary; false if the event is to be  
               given to the component's parent.  
     */  
    public boolean action(Event evt, Object arg)  
    {  
        activateCallbackList.activate( evt );  
        return true;  
    }  

    // Name of instance of this class.  
    private String name;  

    // Callback when button is pushed.  
    private CallbackList activateCallbackList = new CallbackList();  
    public static final String ACTIVATE_CALLBACK = "activateCallback";  
}  

Listing 5.
  
/**  
 * Copyright (c) 1997 Daniel Dee  
 * Description:  
   Package contains common classes for the ViviGraphics Widget  
   Toolkit - a callback-based toolkit.  
   Originally, part of the Eva Toolkit - the prototype  
   implementation.  
 */  
package com.wigitek.vivigraphics.widget.gui;  

import java.awt.Event;  
import java.lang.String;  
import com.wigitek.vivigraphics.widget.common.Callbackable;  
import com.wigitek.vivigraphics.widget.gui.CallbackList;  

/**  
 * This class extends the java.awt.Button class to support  
   the callback mechanism by implementing the Widget interface.  
 * @version $Revision$  
 * @author Originally written by Daniel Dee, 2/21/97  
 * @author Last updated by $Author$, $Date$  
 * @see Widget  
 */  
public class Button extends java.awt.Button  
{  
    /**  
     * Constructs a Button with no label and with name "unnamed".  
     */  
    public Button()  
    {  
        this( "", "unnamed" );  
    }  

    /**  
     * Constructs a Button with a string label and with name "unnamed".  
     * @param label the specified label  
     */  
    public Button( String label )  
    {  
        this(label, "unnamed");  
    }  

    /**  
     * Constructs a Button with a string label and with given name.  
     * @param label the specified label  
     * @param name  the specified name  
     */  
    public Button( String label, String name )  
    {  
        super(label);  
        this.name = name;  
        addActionListener(activateCallbackList);  
    }  

    /**  
     * Returns a String that represents the value of this Object.  
       Overrides the method in java.lang.Object.  
     * @return a String  
     * @see Object  
     */  
    public String toString()  
    {  
        String string = super.toString();  
        int length = string.length();  
        return string.substring( 0, length-1 ) + ",name=" + name + "]";  
    }  

    /**  
     * Registers a callback for the top level frame. Top level frame  
       currently recognizes only ACTIVATE_CALLBACK when the particular  
       instance of the button is pushed. Unknown callbacks  
       are ignored.  
     * @param callbackName the type of callback to register  
     * @param callback     the object of the Callbackable class that will be activated  
       when triggered by the callback type given by callbackName  
     * @see Callbackable  
     */  
    public void addCallback( String callbackName, Callbackable callback )  
    {  
        if( callbackName.compareTo(ACTIVATE_CALLBACK) == 0 )  
            activateCallbackList.add( callback );  
    }  

    // Name of instance of this class.  
    private String name;  

    // Callback when button is pushed.  
    private CallbackList activateCallbackList = new CallbackList();  
    public static final String ACTIVATE_CALLBACK = "activateCallback";  
}  

Listing 6
  
import java.awt.Frame;  
import com.wigitek.vivigraphics.widget.common.*;  
import com.wigitek.vivigraphics.widget.gui.*;  
      
public class ButtonTest extends Frame  
{  
 public ButtonTest( String title )  
 {  
  super(title);  
 }  
 /**  
 * This is the main program to test the Button class.  
 * @param args unused  
 */  
 public static void main( String args[] )  
 {  
   ButtonTest applic = new ButtonTest( "Eva Toolkit: Button Test" );  
   Button button = new Button( "Click me" );  
   button.addCallback( Button.ACTIVATE_CALLBACK, null );  
   applic.add( "Center", button );  
     
   // Note that one can easily make this application completely   
   // Java 1.0 & Java 1.1-compliant by using setSize and   
   // setVisible here and implementing them in terms of resize and    
   // show/hide in Java 1.0 with a callback enabled subclass of // Frame.  
   applic.resize(300,75);  
   applic.show();  
 }  
  
      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.