An interesting aspect of the mechanism of dispatch maps from MFC is that the programmer
doesn't care anymore about interpreting the parameters of an incoming message and
calling the appropriate method. Still, there are the ugly macros BEGIN_MESSAGE_MAP,
END_MESSAGE_MAP and the ON_xxx, but they are handled by the ClassWizard,
so they are not very annoying.
Actually, these maps are relations between events that may occur and the methods to be called in response.
Java allows a nicer approach due to the reflection mechanism. One can dynamically inspect an
object for finding and calling a method of it.
For example, in a dialog one may find useful to attach to every button an action listener
that responds to a click by searching a method of the dialog with the name obtained from the caption of
the button and calling that method. More generally, the method must belong to a target object
passed at the creation of the listener.
There are cases where more buttons in a dialog or items from a menu have the same label.
In this case one can use the pair of methods putClientProperty/
getClientProperty of the class JComponent to store/retrieve the name of the method to be called.
The example demonstrates this by implementing an ActionListener and using
it with several sources of action events.
Here is the code of the actionPerformed method.
public void actionPerformed (ActionEvent e)
{
if (target == null)
return;
// Obtain the name of the method from the action command.
String methodName = toMethodName (e.getActionCommand ());
// If the source is a JComponent, look for the property "MethodName".
if (e.getSource () instanceof JComponent)
{
JComponent source = (JComponent)e.getSource ();
Object prop = source.getClientProperty ("MethodName");
if (prop != null)
methodName = (String)prop;
}
if (methodName != null)
{
try
{
// The prototype of the method is void methodName (ActionEvent e)
Class[] paramTypes = {Class.forName ("java.awt.event.ActionEvent")};
Object[] paramValues = {e};
// Obtain the method and invoke it.
Method method = target.getClass ().getDeclaredMethod (methodName, paramTypes);
method.invoke (target, paramValues);
}
catch (NoSuchMethodError noSuchMethodError)
{
System.err.println ("Method: void " + methodName + " (ActionEvent e) not found");
return;
}
catch (Exception exc)
{
exc.printStackTrace ();
return;
}
}
}
I think the comments are explanatory. The prototype of the method to be called is
void method (ActionEvent e).
The ActionEvent can be broken into components and passed in this way by changing the
prototype of the method (see the paramTypes variable that represents the types
of the parameters).
There are several ways to use it:
- Create one listener for a target. In the example this is done b
this.listener = new ActionDispatchListener (this);
- The label of the method is enough. In this case just add a method with the name as obtained
from the
toMethodName into the target object and add the listener to the list of action
listeners the source. In the example this is used in the File|Exit menu item.
mi = (JMenuItem)file.add (new JMenuItem ("Exit", 'X'));
mi.addActionListener (listener);
- Call
putClientProperty ("MethodName", <method name>) for the event source,
add the method with the specified name and add the listener to the list of listeners. In the
example this is used for the button in the JInternalFrame derived class.
JButton button = new JButton ("Click Me");
button.putClientProperty ("MethodName", "onClickMe");
button.addActionListener (sample.listener);
- If you plan to use this in many cases, for example for creating the
items of a menu, one can derive a class and passing the name of the method to
the constructor. In the example this is done by the
JMethodMenuItem class.
Of course, the mechanism can be applied for other types of listeners. One such dispatch
listener might implement several listener interfaces in this way.