Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java Q&A


Mar99: Java Q&A

Andy, a developer for NuMega Technologies, can be contacted at [email protected].


The Microsoft delegate keyword brings a shudder to many pure Java developers. This keyword locks an application to the Microsoft VM, eliminating the possibility of running the application on a nonWindows platform. However, delegate provides you with a new event-handling scheme that can simplify complex GUI applications.

Another problem is that delegate works with the Windows Foundation Class (WFC), but does not work with the existing AWT or the new Java Foundation Classes (JFC) event models. Both the AWT and JFC event models could benefit from the delegate scheme.

Bringing delegates to pure Java code is currently not possible. However, creating something that behaves similar to a delegate is not too difficult. Consequently, I'll present classes that mimic the delegate keyword, allowing you to build a complex user interface with simpler event-handling code. The complete source code and related files for this delegate-like implementation are available electronically (see "Resource Center," page 5).

Event Handling and AWT

Each Java AWT and JFC GUI object sends various events. Events often represent user actions, like pressing a key or clicking a mouse button. Events also represent state changes in the application, such as the window closing or the window needing to be repainted. Event objects often represent specific areas of the application. For example, there are WindowEvent objects that represent events relating to the state of the current window. Handling these events is beyond the scope of this column; however, I will examine a few of the more common event-handling schemes.

The first (and simplest) way to handle events in a GUI application is to override one (or a few) of the core event-handling methods -- processEvent, processComponentEvent, processKeyEvent, processMouseEvent, or processMouseMotionEvent. These methods dispatch events to an object of type java.util.EventListener. However, you can override any of these methods to handle a given event.

Overriding an event-dispatching method raises a couple of important issues. First, these dispatchers only dispatch events directly related to the GUI object. For example, if a user clicks on a button within a frame, only the button's processEvent method is called. Also, the overridden event-dispatching method must call the base class's event-dispatching method; otherwise, other event handlers may not execute.

The second way to handle an event is by registering a java.util.EventListener object with a GUI object. Such an object (an event listener) gives you a lot of flexibility. This event-handling scheme allows for three different ways of implementing event-handling code.

EventListener-based Event Handlers

Event-dispatching methods call EventListener object methods. Consequently, EventListener objects should implement some sort of event-handling code for a given event type. Event-handling code can do anything from disabling a button or menu option, to saving the current document and exiting the application.

The java.awt.event package contains most of the standard Event and EventListener classes. There are several different event classes, and for each Event class there is an EventListener class. Each Event class represents a variety of different pieces of information about the GUI object. For example, a WindowEvent object may indicate if a window is being resized or closed. The corresponding WindowListener class has methods that handle the various WindowEvent states. Therefore, there are methods that specifically handle window resizing and closing events.

Using EventListener classes is straightforward; you create a new class and implement all the methods of the EventListener interface for the event you want to receive. For example, if an application wants to receive WindowEvent objects, the application must have a class that implements all the methods of the WindowListener interface. The only issue is that many of these EventListener classes have several methods that you must implement. Many of these methods are unnecessary for most applications, so you end up writing a lot of code that never gets used.

Realizing that implementing unused methods in interfaces is annoying, Sun implemented most of the EventListener classes with event-adapter classes. These adapter classes have the function definitions of the underlying interface stubbed out. You can simply inherit from the adapter class and override the methods for the events you are interested in handling.

There are three ways to handle events using these EventListener derived classes. The first is that you implement the EventListener in the applet or application GUI class. Obviously, you can implement as many EventListener interfaces as you wish. Doing this is usually helpful because the event handlers have direct access to all class fields. The biggest drawback is that you may have to stub out many unused event-handler functions.

The second method is to override an event-adapter class, or implement a new EventListener class as a class separate from the main GUI class. This method is more modular and relatively easy to debug. It also has the virtue of not forcing you to implement every event method, if you are using the event-adapter class. The downside is that the new event-handler class may not have access to all the GUI class's member fields.

The third method is to implement an event adapter as an anonymous or nested class. This method is a more tricky, and sometimes more difficult to debug. However, both anonymous and nested classes have access to the GUI class's member fields.

The notion of an anonymous class is a little strange. The idea is that I can define a class inline during construction. Example 1 illustrates this notion. When the compiler executes on the Java source file, the compiler generates a new class file representing the anonymous class. The result is something like "Foo$1.class" for the anonymous class. Consequently, each time you build your application, the anonymous class name may change. Beyond this, there is almost no difference between an anonymous class and a nested class; both can access all the fields and methods of the enclosing class.

The downside to anonymous classes is that debuggers may have difficulty with breakpoints and debug information. It has also been argued that the construct is more difficult to read and is not portable from one application to the next.

After creating an event-handling class, you must bind the event handler to a GUI object. The GUI object will then send events corresponding to the event handler via the event-dispatching methods.

Overriding event dispatchers or implementing EventListener classes brings up a few issues. First, overriding an event-dispatcher method requires you to add some code to check for a particular event. However, this event checking occurs constantly, and consequently introduces a significant amount of overhead. Second, implementing separate EventListener classes causes the event-handling code to be disjointed from the main GUI class's code, thus some symmetry between creation and event handling is lost. Third, implementing EventListener interfaces in the main GUI class often forces you to write a lot of unused code to stub out event-handler definitions. Finally, using anonymous or nested classes, though they work fine, can make the code somewhat less readable, and the classes are no longer portable between applications.

These gripes are just a few irritations that you can deal with rather easily. However, all of these methods require you to write a lot of boilerplate code that is used in nearly every GUI application. Eliminat-ing the boilerplate code would certainly simplify the development process and remove some of the tedium.

This leads us to the delegate keyword.

The delegate Keyword

A delegate can simply be described as a proxy. You register a method with the delegate, and then register the delegate with a WFC GUI object. When the GUI object receives an event, the GUI object forwards the event to the delegate, which forwards the event to the event-handling method you wrote. You may define any method to receive an event, however, the method must have a specific signature.

A method signature is made up of the order and type of parameters, along with the return type. Event-handler methods in the delegate model use the same basic signature. The event-handler method has no return type (void), and takes two parameters. The first is of type Object and represents the original sender of the event. The second parameter is the event object itself.

Listing One shows a delegate in use. The initForm() method builds the user interface in a WFC application. This method is often automatically generated using Visual J++ 6.0. The interesting line is the call to sortButton.addOnClick(new EventHandler(this.sortClick)).

The sortButton.addOnClick() method takes a parameter of type EventHandler, a delegate class. All delegates require a java.lang.reflect.Method object as a parameter. The Method object represents the user-defined event-handling method. For this EventHandler object, the event-handling function is this.sortClick.

The effect of this line of code is the binding of a method (sortClick) to a delegate, then a delegate to a GUI object (sortButton). Consequently, every time users click on the button, the sortButton object sends an event to the delegate, the delegate forwards the event to the sortClick method.

This implementation allows the delegate class to be a separate entity from the main GUI class. However, the event-handling code remains in the main GUI class. The advantage is that you only need to implement a method to handle a specific event type. Moreover, you don't have to write any boilerplate code for event handling.

delegates are actually more complex than I've just described. A delegate is actually a special type of class, but its declaration is completely different from any other class declaration in Java. In fact, a delegate's declaration looks more like a function. Listing Two is a custom delegate necessary to handle some asynchronous sorting. There is no constructor in this declaration. All delegate constructors take one parameter, a Method object. Instead, the delegate declaration defines the signature of the event-handling method. According to this listing, the user-defined event-handler method must have two parameters, the first of type Object, representing the sender of the event, and a second of type WFCSortEvent, representing the event itself.

Probably the most important thing to note about Microsoft delegates is the conspicuous lack of code. The compiler automatically generates the remaining code to represent the delegate object; you don't have to do anything more than define the delegate's signature, and the compiler does the rest. Subsequently, you get a class file that represents the delegate object, all of which is automatically generated.

Listing Three shows the WFC class that binds the delegate class to the object. Listing Four shows how the class actually sends an event. Finally, Listing Five shows the application binding a local event-handing method to the delegate, and subsequently to the object that will send the event.

This seems like a lot of code to allow for something as simple as a notification message. However, much of this code was automatically generated by the Visual J++ environment, so it took only about a second for the code to appear.

Building delegate-like functionality in pure Java is straightforward. The reflection package provides us with all the necessary functionality. Consider building a delegate to handle WindowEvent objects. You simply need to implement a WindowListener class. This class must store a set of Method objects. Each Method object represents a user-defined event-handler method. The class needs to override the WindowListener methods, and invoke the appropriate Method object. This results in the same sort of event- forwarding functionality provided by Microsoft delegates.

The WindowDelegate and its base class (available electronically) binds event-handling methods to specific event types. Once this binding is done, you just bind the user-defined event-handler to the GUI object. Listing Six performs the binding in the main GUI class, and the subsequent event-handler method.

The resulting WindowDelegate class is a reusable object, allowing you to use this delegate-like class in every application, without having to modify the delegate class itself. However, this pure Java delegate class will work on all AWT and JFC applications.

Conclusion

The delegate class I present here brings all the benefits of both EventListener and delegate classes. The pure Java delegate class lets you register a method in any class as an event handler for any class that supports the JDK 1.1 event model. The event-handling code remains in the main GUI class, but does not break the EventListener model. Finally, all the boilerplate code is written once for all applications, eliminating the tedium of writing classes or implementing methods that are never used.

DDJ

Listing One

Button sortButton = new Button();private void initForm()
{
        sortButton.setDock(ControlDock.BOTTOM);
        sortButton.setLocation(new Point(0, 277));
        sortButton.setSize(new Point(300, 23));
        sortButton.setTabIndex(0);
        sortButton.setText("Sort");
        sortButton.addOnClick(new EventHandler(this.sortClick));
        /*Download complete sample for full code */       
}
private void sortClick(Object source, Event e)
{
        m_pDlg = new ProgressDialog();
        m_pDlg.show();
        m_pDlg.progressBar.setMaximum( m_items.length );
        
        m_items = m_app.sortItems( m_items );
        
        showItems();
}

Back to Article

Listing Two

public delegate void WFCSortEventDelegate ( Object sender, WFCSortEvent e ); 

Back to Article

Listing Three

package WFCExtensions;

</p>
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.lang.Delegate;
public class WFCApp extends App
{
    WFCSortEventDelegate m_SortCompletionEvent;
    WFCSortEventDelegate m_SortProgressEvent;
    public WFCApp ( int items )
    {
        super ( items );
    }
    public void addOnSortCompletionEvent(WFCSortEventDelegate value)
    {
        m_SortCompletionEvent = (WFCSortEventDelegate)Delegate.
                                 combine(m_SortCompletionEvent, value);
    }
    public void addOnSortProgressEvent(WFCSortEventDelegate value)
    {
        m_SortProgressEvent = (WFCSortEventDelegate)Delegate.
                                 combine(m_SortProgressEvent, value);
    }
    protected void onSortCompletionEvent(WFCSortEvent event)
    {
        if (m_SortCompletionEvent != null) m_SortCompletionEvent.
                                                 invoke(this, event);
    }
    protected void onSortProgressEvent(WFCSortEvent event)
    {
        if (m_SortProgressEvent != null) m_SortProgressEvent.
                                                 invoke(this, event);
    }
    public void removeOnSortCompletionEvent(WFCSortEventDelegate value)
    {
        m_SortCompletionEvent = (WFCSortEventDelegate)Delegate.
                                   remove(m_SortCompletionEvent, value);
    }
    public void removeOnSortProgressEvent(WFCSortEventDelegate value)
    {
        m_SortProgressEvent = (WFCSortEventDelegate)Delegate.
                                    remove(m_SortProgressEvent, value);
    }
    protected void sortCompleteEvent( )
    {
        WFCSortEvent e = new WFCSortEvent();
        onSortCompletionEvent( e );
    }
    protected void sortProgressEvent( int position, int total )
    {
        WFCSortEvent e = new WFCSortEvent ( position, total );
        onSortProgressEvent( e );
    }
    public static class ClassInfo extends com.ms.wfc.core.ClassInfo
    {
        public static final EventInfo sortCompletionEvent = new EventInfo(
            WFCApp.class, "sortCompletionEvent", EventHandler.class);
        public static final EventInfo sortProgressEvent = new EventInfo(
            WFCApp.class, "sortProgressEvent", EventHandler.class);
        public void getEvents(IEvents events)
        {
            super.getEvents(events);
            events.add(sortProgressEvent);
            events.add(sortCompletionEvent);
        }
        public void getProperties(IProperties props)
        {
            super.getProperties(props);
        }
    }
}

Back to Article

Listing Four

import java.util.*;

</p>
public abstract class App
{
    protected int[] m_items;
    public App(int items)
    {
        m_items = new int[ items ];
        fillItems( m_items );
    }
    protected void fillItems( int[] items )
    {
        Random rnd = new Random();
        for ( int i = 0; i < items.length; i++ )
        {
            int tmp;
            do 
            {
                tmp = rnd.nextInt(); 
            } while ( tmp < 0 );
            m_items[i] = tmp; 
        }
    }
    public int[] sortItems( int[] items )
    {
        int[] sortedList = new int[ items.length ];
        System.arraycopy( m_items, 0, sortedList, 0, m_items.length );
        for ( int i = 0; i < sortedList.length; i++ )
        {
            for( int j = i; j < sortedList.length; j++ )
            {
                if (  sortedList[i] > sortedList[j] )
                {
                    int tmp = sortedList[j];
                    sortedList[j] = sortedList[i];
                    sortedList[i] = tmp;
                }
            }
           sortProgressEvent( i, m_items.length );
        }
        sortCompleteEvent();
        return sortedList;
    }
    public int[] getItems( )
    {
        return m_items;
    }
    protected abstract void sortCompleteEvent( );
    protected abstract void sortProgressEvent( int position, int total );
}

Back to Article

Listing Five

public class Example1 extends Form{
    WFCApp m_app;
    public Example1()
    {
        super();
        m_app = new WFCApp ( 2500 );
        m_items = m_app.getItems();
        m_sortedItems = m_app.sortItems( m_items );
        
        // bind custom delegate classes to WFCApp class
        m_app.addOnSortCompletionEvent( 
                   new WFCSortEventDelegate( this.sortCompletionEvent ) );
        m_app.addOnSortProgressEvent( 
                   new WFCSortEventDelegate( this.sortProgressEvent ) );
        initForm(); 
    }
}

Back to Article

Listing Six

public class Example2 extends Frame{
    public void initForm ( )
    {
    /** Download complete source for full example */
    
        this.addWindowListener( 
            new WindowDelegate( this,
            "windowClosing",
            WindowDelegate.CLOSING ) ); 
    }
    public void windowClosing ( WindowEvent e )
    {
        System.exit(1);
    }
}

Back to Article


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.