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:
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 daniel@wigitek.com
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();
}