By Dr P.G. Sarang and Kamal Shah
Microsoft Visual J++ 6.0 introduced a new keyword that greatly simplifies event handling in WFC programs: delegate. Because Java doesn't support pointers, the use of delegate can provide functionality similar to function pointers in other languages. In this article, we'll discuss delegates, learn how to use them in event handlers, create custom events, and send messages from one WFC-based application to another.
DelegatesYou declare a delegate in your program using the following syntax:
delegate ResultType Identifier(parameters);
The delegate syntax is similar to a method signature. The Identifier specifies the name for the delegate, and the parameters list the method parameters. The ResultType specifies the result of the method. The VJ6 compiler converts the statement into a class declaration with a name similar to the Identifier. For example, a declaration such as:
delegate float Multiplier(float x, float y);
produces a class named Multiplier. Instantiate this class using the following syntax:
Multiplier mult = new Multiplier(this.MyMulitiplierMethod);
where MyMultiplierMethod is a method defined in your class having the same signature as the one used by the declared delegate. Once the delegate is instantiated, use the invoke method to invoke your function:
float result = mult.invoke(x, y);
This statement invokes the MyMultiplierMethod and returns a float result. Depending on the program context, you may bind a different method to your delegate object at run time. Thus, what you've achieved using delegates is identical to using function pointers in other languages. However, because delegates are converted into classes, they are object-oriented.
Due to the inherent nature of the Java language, delegate declarations are type-safe. The method signature must match the signature of the delegate exactly, and arbitrary casting is not allowed. Delegates are secure. Like function pointers, you cannot execute an un-trusted or more privileged code using delegates.
The full syntax of delegate is as follows:
[Modifiers] [multicast] delegate ResultType Identifier ([FormalParameterList]) [throws]
The optional Modifiers can be one or more of the following keywords: public, protected, private, static, and final. The use of the multicast keyword allows you to invoke multiple methods whenever you call the invoke method on the delegate object. In other words, you can combine delegates into a single delegate, which, when invoked, will invoke all the individual delegates in the invocation list in the order in which they were added. The throws clause allows you to introduce exception handling in your delegate method.
Use of Delegates in Event HandlingAlthough
delegates can be used whenever there is a need for a function pointer,
you'll find they're most useful for event handling in your WFC code. In
fact, when you use the Forms Designer while building WFC applications, the
code designer uses delegates to provide event handling for your form.
For example, if you use the Forms Designer to add an OnClick event handler for a button component placed on a WFC form, the following line of code is added to your class:
button1.addOnClick(new EventHandler(this.button1_click));
In addition to the previous statement, the generated code also adds the following method declaration to your class code:
private void button1_click(Object source, Event e)
{
}
WFC components recognize several types of events. In the Forms Designer, when you select the Events page, the list of events supported by the selected component are displayed. Selecting any event from the list adds code to your class module similar to the one shown previously.
In the previous code, the addOnClick method creates and adds an EventHandler object. The EventHandler itself is declared as a delegate:
public multicast delegate void EventHandler(Object sender, Event e);
The EventHandler delegate specifies the signature for the method to be invoked. The called method receives the source of the event as the first parameter, and the Event object as the second parameter that contains event details. You instantiate the EventHandler delegate by passing the name of the desired method as a parameter. The method must have the same signature as the one defined in the EventHandler delegate. In the previous example, the button1_click method is used as a delegate that gets invoked whenever button1 is clicked.
It's important to note that although we used the Forms Designer to generate the event handling code, we could have written the code ourselves by calling the desired addxxx method and providing the corresponding event handler method. VJ6 provides several addxxx methods for different types of events to be processed.
Sharing a DelegateSometimes, you may wish to call the same event handler for two different events. For example, we provide the same event handler for a toolbar button and a menu item. In such cases, you can assign one delegate to both events. The following lines of code illustrate this:
Show.addOnClick(new EventHandler(show_click));
ShowButton.addOnClick(new EventHandler(show_click));
where Show is a menu item and ShowButton is a toolbar button defined on the form. Both call the same method, show_click, that has the following signature:
private void show_click(Object source, Event e)
{
}
Within the body of the method, you can detect the event source by using its source parameter. If you wish to invoke the same event handler for an additional event - say the mouse-click event of the form - add the following program statement in the init method of your form:
this.addOnClick(new EventHandler(show_click));
The show_click method will now be called whenever you select the menu item, click the toolbar button, or click on the form. The same delegate is thus shared between different events.
Multicast DelegatesA delegate may be declared with the multicast
keyword. A multicast delegate allows multiple methods to be called.
Use the multicast keyword for your delegate declaration if you wish
to call multiple methods while handling a desired event. All such methods
must return void.
VJ6 defines a Delegate class in the com.ms.lang package. The Delegate class provides a static method named combine to create an invocation list of methods you wish to call whenever the delegate is invoked:
Delegate.combine(currentHandler, addHandler);
This statement adds the addHandler delegate to the existing currentHandler delegate. The addHandler delegate will now be invoked as a part of the invocation list maintained by the currentHandler delegate. You may add more delegates to the current delegate by calling the combine method with the desired parameters. Once the desired delegates are combined into a single delegate, calling the invoke method on this newly combined delegate will cause the invocation of all the individual delegates in the order in which they were added.
It's also possible to remove a delegate from the invocation list. The remove method removes the specified delegate from the current invocation list, e.g.:
remove(delegate)
Thus, using these two methods, you can easily control the methods that are called when a certain event occurs.
Timer ExampleWe'll illustrate the use of the delegate object and its combine and remove methods with a practical example. We've constructed a simple WFC application that displays the current time, date, or both to the user. (The example is available for download; see the end of this article for details.)
The application contains three radio buttons (see Figure 1). When the Display Time button is clicked, the current time is displayed in an edit box shown at the bottom. The Display Date button displays the date to the user. Upon clicking the Display Date and Time button, both current date and time are displayed in the edit box. We will dynamically change the delegate used by the timer event.
Figure 1: Our Timer example offers three radio buttons.
In the constructor, the program creates the Timer object and uses its addOnTimer method to add the triggerEvent delegate to the timer event, as shown in
Listing One.timer.addOnTimer(new EventHandler(triggerEvent));
The timer interval is set to one second. Thus, the triggerEvent is called every second. This event handler invokes the currentHandler delegate, which maintains the list of delegates to be invoked. The currentHandler decides whether the time, date, or both are displayed to the user by maintaining an invocation list of delegates to be called. The invocation list for delegates is created and maintained in the radio button's event handler.
We use the addOnClick method of the radio button to add the radioClick delegate to the click event of the radio button. We share the same delegate among all three radio buttons:
this.onlyTime.addOnClick(new EventHandler(radioClick));
this.timeAndDate.addOnClick(new EventHandler(radioClick));
this.onlyDate.addOnClick(new EventHandler(radioClick));
The delegate method radioClick receives the source object and the event details. In this event handler, we first remove the existing delegates from the invocation list by calling the remove method of the Delegate class:
this.currentHandler =
(DateEventHandler)Delegate.remove(currentHandler,removeHandler);
We then add an appropriate delegate to the invocation list depending on the source of the event (which radio button is clicked). For example, if the Display Time radio button is clicked, we create an instance of DateEventDelegate with displayTime as the delegate to be invoked:
this.addOnDateEvent(new DateEventHandler(displayTime));
The addOnDateEvent calls the delegate object's combine method to add the desired delegate to currentHandler:
this.currentHandler =
(DateEventHandler)Delegate.combine(currentHandler,addHandler);
Note that we define the DateEventHandler with the following signature:
public multicast delegate void DateEventHandler();
The displayTime and displayDate methods have the same signature as this delegate. These methods neither take any parameters, nor return any value to the caller. The delegate is declared using the multicast keyword; this permits us to combine the displayTime and displayDate delegates into a single delegate.
If the user clicks on the Display Date and Time radio button to enable the simultaneous display of both time and date, we use the following code to combine the two delegates:
this.addOnDateEvent(new DateEventHandler(displayDate));
this.addOnDateEvent(new DateEventHandler(displayTime));
The displayTime and displayDate methods obtain the current system time, and set the appropriate string variable to be displayed to the user.
The triggerEvent delegate is used by the timer event and is invoked every second. In this event handler, we display the count of timer ticks and the appropriate time and date string to the user. The method also calls the PostMessage method of Win32 API to post an event to another WFC application, which is explained in the following section.
Dispatching EventsSo far, we've seen how to
process events generated within a WFC application using delegates.
Sometimes, you may need to dispatch messages to other running applications
when a certain event occurs. For example, in a data acquisition system,
you may write an independent program for data collection. Whenever this
data collection program receives data, it will dispatch a message to
another application indicating that the data is ready for processing. You
may even send the data to the other application through your message.
Typically, in two Java-based applications, you'll need to use sockets to communicate between them. Using sockets is more complex, but offers additional benefits. In WFC-based applications, using the Win32 API and custom events, you can easily achieve message dispatching and data transfer between two applications.
To illustrate this, we've developed a Receiver application, shown in Listing Two, that receives a user-defined event whenever the data is available for processing. The data itself is received as part of the message.
Our Trigger application, discussed earlier, periodically dispatches a user-defined message to the Receiver application. The message contains the data to be processed by the Receiver. We use the Win32 API for message dispatching between two applications. Our Trigger application locates the Receiver application by calling the FindWindow method of the Win32 API in its constructor, as shown in Listing One:
handle = Win32.FindWindow(null,"Receiver");
The FindWindow method searches the desktop for a window with a caption equal to "Receiver." If the Receiver application is running, a window handle to the Receiver application's main window is returned to the caller. The Trigger application uses this handle to dispatch messages to the Receiver.
We use the Win32 API call PostMessage to dispatch a message:
Win32.PostMessage(handle, Win32.WM_USER+42, 0, timeCount);
The PostMessage method is called in the body of the triggerEvent event handler. As seen earlier, the Trigger application calls the triggerEvent handler every second. We define the message ID as WM_USER+42. This ID is defined in the Win32 class using the J/Direct call builder. The PostMessage method contains two parameters, wParam and lParam, for sending additional message-related information. We use the latter parameter for sending data to the Receiver application. In this case, the data simply consists of the current timer tick value.
At the Receiver end, we process this user-defined message by overriding the wndProc method:
protected void wndProc(Message message)
{
if (message.msg == Win32.WM_USER+42)
{
receiverDelegate.invoke(this, new DataEvent(message.lParam));
}
super.wndProc(message);
}
In wndProc, we check for the current message ID. If the current message ID is WM_USER+42, we process it by invoking the receiverDelegate delegate. All other messages are sent to the super class for processing.
The receiverDelegate variable is of type ReceiverEventHandler, which is defined using the following statement:
public multicast delegate void ReceiverEventHandler(
Object source, DataEvent event);
Unlike other delegates we've seen so far, this delegate makes use of a new event named DataEvent. DataEvent is a custom event defined by us that is invoked whenever the receiverDelegate is invoked.
DataEvent is a user-defined custom event, shown in Listing Three. The DataEvent inherits from the Event class, and declares one private variable for storing the received data. The getData method returns this data item to the caller.
The addOnDataEvent method adds the DataRecd method in the invocation list of the receiverDelegate delegate:
this.addOnDataEvent(new ReceiverEventHandler(DataRecd));
The DataRecd method now gets called whenever receiverDelegate is invoked. As we saw earlier, the receiverDelegate is invoked in wndProc whenever we receive our user-defined message from the Trigger application. The DataRecd method simply calls the getData method of our custom event class to retrieve the data and display it to the user.
ConclusionVJ6 has added new keywords, delegate and
multicast, to Java. This article describes the use of delegates in
Java programs with special emphasis on their use in event handling.
Delegates can be combined dynamically to form a single delegate. The
invocation of such a delegate causes all delegates in the combined list to
be invoked in the order in which they were added to the list. The
delegates can be combined and removed from the invocation list dynamically
at run time.
This article explains how to create and maintain the invocation list of delegates dynamically, with the help of a practical example. This article also describes creating custom events and sending messages between two WFC-based Java applications. Unlike "pure" Java applications, which use sockets for communication, a WFC-based Java application can use Windows messaging and custom events for communication. This is done with the help of the Win32 API. The article describes how to define custom events and send messages between two Java applications.
The example projects referenced in this article are available for download from the VJ Informant Web site at http://www.vjinformant.com/dl/showfile.asp?articleid=ji199905ps.
Dr P.G. Sarang is President and CEO of ABCOM Information Systems Pvt. Ltd., a consulting firm that specializes in Internet programming using Java, JavaScript, and ISAPI, and Windows-based application development using Microsoft Visual Basic, Visual C++, Access, and SQL Server.
Kamal Shah is in his final year of studying computer engineering. He is a very good Java programmer and possesses interest in distributed computing using Java/CORBA. He plans to continue his studies and obtain a Master's degree in Computer Engineering. He can be reached at kamal@abcom.com.
Begin Listing One - Trigger.java//*****************************************************************************
// File: Trigger.java
// This file contains implementation of following classes
// 1. Trigger
// Copyright 1999: ABCOM Information Systems Pvt. Ltd., All rights reserved.
//*****************************************************************************
import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import com.ms.lang.Delegate;
import java.text.DateFormat;
import java.util.*;
//*****************************************************************************
// Class implemented : Trigger
// Derived from : Form
// Implements : None
//*****************************************************************************
/**
* This class can take a variable number of parameters on the command
* line. Program execution begins with the main() method. The class
* constructor is not invoked unless an object of type 'Form1' is
* created in the main() method.
*/
public class Trigger extends Form
{
private int timeCount;
private int handle;
private DateEventHandler currentHandler = null;
private String timeString = null;
private String dateString = null;
private char displayType;
public Trigger()
{
// Required for Visual J++ Forms Designer support.
initForm();
// TODO: Add any constructor code after initForm call.
Timer timer = new Timer();
// Sets the timer trigger to one second.
timer.setInterval(1000);
// Registers a new EventHandler with the timer,
// which calls the method triggerEvent every one second.
timer.addOnTimer(new EventHandler(triggerEvent));
// Initial timer count is set to zero.
timeCount = 0;
// Start the timer.
timer.start();
// The FindWindow method searches for a window named Receiver on
// the Desktop. If the window is found, it assigns the handle of
// that window to the handle variable.
handle = Win32.FindWindow(null,"Receiver");
// Register event handlers with each of the three radio buttons.
// The same method (radioClick) is invoked when any of the
// radio buttons is clicked.
this.onlyTime.addOnClick(new EventHandler(radioClick));
this.timeAndDate.addOnClick(new EventHandler(radioClick));
this.onlyDate.addOnClick(new EventHandler(radioClick));
}
// Delegate handler for radio button click event.
private void radioClick(Object source, Event event)
{
// Removes all the existing delegates.
removeOnDateEvent(currentHandler);
// Check for which button is checked.
if (source == this.onlyTime)
{
displayType = 'T';
// Add a displayTime delegate.
this.addOnDateEvent(new DateEventHandler(displayTime));
}
else if (source == this.onlyDate)
{
displayType = 'D';
// Add a displayDate delegate.
this.addOnDateEvent(new DateEventHandler(displayDate));
}
else if (source == this.timeAndDate)
{
displayType = 'B';
// Add both displayDate and displayTime delegates.
this.addOnDateEvent(new DateEventHandler(displayDate));
this.addOnDateEvent(new DateEventHandler(displayTime));
}
}
// The following method adds the DateEventHandler received
// as a parameter to the current list of DateEventHandlers.
private void addOnDateEvent(DateEventHandler addHandler)
{
this.currentHandler =
(DateEventHandler)Delegate.combine(currentHandler,addHandler);
}
// The following method removes the specified DateEventHandler.
private void removeOnDateEvent(DateEventHandler removeHandler)
{
this.currentHandler =
(DateEventHandler)Delegate.remove(currentHandler,removeHandler);
}
// Delegate used by DateEventHandler.
private void displayTime()
{
// Gets the current time from the System in milliseconds.
long time = System.currentTimeMillis();
// Gets a time instance of date format.
DateFormat dateFormat = DateFormat.getTimeInstance();
// Gets the default time zone of the system and assigns it
// to the dateFormat's time zone.
dateFormat.setTimeZone(TimeZone.getDefault());
// Assigns the formatted time to timeString.
timeString = new String(dateFormat.format(new Date(time)));
}
// Delegate used by DateEventHandler.
private void displayDate()
{
// Gets the current time from the System in milliseconds.
long time = System.currentTimeMillis();
// Gets a date instance of date format.
DateFormat dateFormat = DateFormat.getDateInstance();
dateFormat.setTimeZone(TimeZone.getDefault());
// Assigns the formatted date to timeString.
dateString = new String(dateFormat.format(new Date(time)));
}
// Delegate for Timer event.
// This method is called every one second.
private void triggerEvent(Object source, Event event)
{
// Sets the timer tick value.
this.timeEdit.setText(""+timeCount);
// A message is sent to the "Receiver" window using the handle.
// It also sends the timer tick value as data to the Receiver.
Win32.PostMessage(handle,Win32.WM_USER+42,0,timeCount);
// Update the timer.
++timeCount;
// Invoke the current handler and all its methods from
// the invocation list.
if (currentHandler != null)
this.currentHandler.invoke();
// Depending on the "display Type" the
// timeDisplay edit box is updated.
switch (displayType)
{
case 'T':
this.timeDisplay.setText(timeString);
break;
case 'D':
this.timeDisplay.setText(dateString);
break;
case 'B':
this.timeDisplay.setText(dateString + " " + timeString);
break;
}
}
/**
* Form1 overrides dispose so it can clean up the
* component list.
*/
public void dispose()
{
super.dispose();
components.dispose();
}
/**
* NOTE: The following code is required by the Visual J++ Forms
* Designer. It can be modified using the form editor. Do not
* modify it using the code editor.
*/
Container components = new Container();
Label timeLabel = new Label();
Edit timeEdit = new Edit();
RadioButton onlyTime = new RadioButton();
RadioButton timeAndDate = new RadioButton();
Edit timeDisplay = new Edit();
RadioButton onlyDate = new RadioButton();
private void initForm()
{
this.setText("Trigger");
this.setVisible(false);
this.setAutoScaleBaseSize(13);
this.setBorderStyle(FormBorderStyle.FIXED_3D);
this.setClientSize(new Point(181, 237));
this.setMaximizeBox(false);
this.setStartPosition(FormStartPosition.CENTER_SCREEN);
timeLabel.setLocation(new Point(24, 24));
timeLabel.setSize(new Point(100, 16));
timeLabel.setTabIndex(0);
timeLabel.setTabStop(false);
timeLabel.setText("Timer Tick #");
timeEdit.setEnabled(false);
timeEdit.setLocation(new Point(24, 48));
timeEdit.setSize(new Point(100, 20));
timeEdit.setTabIndex(1);
timeEdit.setText("0");
onlyTime.setLocation(new Point(24, 88));
onlyTime.setSize(new Point(128, 25));
onlyTime.setTabIndex(2);
onlyTime.setText("Display Time");
timeAndDate.setLocation(new Point(24, 136));
timeAndDate.setSize(new Point(152, 25));
timeAndDate.setTabIndex(3);
timeAndDate.setText("Display Date and Time");
timeDisplay.setEnabled(false);
timeDisplay.setLocation(new Point(16, 176));
timeDisplay.setSize(new Point(152, 20));
timeDisplay.setTabIndex(4);
timeDisplay.setText("");
onlyDate.setLocation(new Point(24, 112));
onlyDate.setSize(new Point(128, 25));
onlyDate.setTabIndex(5);
onlyDate.setText("Display Date");
this.setNewControls(new Control[] {
onlyDate,
timeDisplay,
timeAndDate,
onlyTime,
timeEdit,
timeLabel});
}
/**
* The main entry point for the application.
*
* @param args Array of parameters passed to the application
* via the command line.
*/
public static void main(String args[])
{
Application.run(new Trigger());
}
}
End Listing One
//*****************************************************************************
// File: Receiver.java
// This file contains implementation of following classes:
// 1. Receiver
// copyright 1999: ABCOM Information Systems Pvt. Ltd., All rights reserved.
//*****************************************************************************
import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import com.ms.lang.Delegate;
//*****************************************************************************
// Class implemented : Receiver
// Derived from : Form
// Implements : None
//*****************************************************************************
/**
* This class can take a variable number of parameters on the command
* line. Program execution begins with the main() method. The class
* constructor is not invoked unless an object of type 'Form1' is
* created in the main() method.
*/
public class Receiver extends Form
{
// Initializing the ReceiverEventHandler to null.
private ReceiverEventHandler receiverDelegate = null;
public Receiver()
{
// Required for Visual J++ Forms Designer support.
initForm();
// Initializing the receiverDelegate so it calls the DataRecd
// method when the receiverDelegate is invoked.
this.addOnDataEvent(new ReceiverEventHandler(DataRecd));
// TODO: Add any constructor code after initForm call.
}
// Adds the specified delegate to the receiverDelegate.
private void addOnDataEvent(ReceiverEventHandler handler)
{
receiverDelegate =
(ReceiverEventHandler)Delegate.combine(receiverDelegate,handler);
}
// delegate for ReceiverEventHandler. This delegate is called
// whenever the application receives the message notification
// that the data is ready for processing. DataEvent is the
// custom event object.
private void DataRecd(Object source, DataEvent data)
{
// Update the timeEdit Edit box.
this.timeEdit.setText("" + data.getData());
}
// wndProc is overridden for processing user defined messages.
protected void wndProc(Message message)
{
// Checks for message ID WM_USER+42.
if(message.msg == Win32.WM_USER+42)
{
// Invokes the delegate. A DataEvent object is constructed
// and passed as a parameter to the delegate.
receiverDelegate.invoke(this, new DataEvent(message.lParam));
}
// Pass on the unhandled messages to super class.
super.wndProc(message);
}
/**
* Form1 overrides dispose so it can clean up the
* component list.
*/
public void dispose()
{
super.dispose();
components.dispose();
}
/**
* NOTE: The following code is required by the Visual J++ Forms
* Designer. It can be modified using the form editor. Do not
* modify it using the code editor.
*/
Container components = new Container();
Edit timeEdit = new Edit();
Label timeLabel = new Label();
private void initForm()
{
this.setLocation(new Point(400, 0));
this.setText("Receiver");
this.setAutoScaleBaseSize(16);
this.setClientSize(new Point(170, 96));
this.setStartPosition(FormStartPosition.MANUAL);
timeEdit.setEnabled(false);
timeEdit.setLocation(new Point(24, 40));
timeEdit.setSize(new Point(100, 23));
timeEdit.setTabIndex(0);
timeEdit.setText("");
timeLabel.setLocation(new Point(24, 16));
timeLabel.setSize(new Point(100, 16));
timeLabel.setTabIndex(1);
timeLabel.setTabStop(false);
timeLabel.setText("Timer");
this.setNewControls(new Control[] {
timeLabel,
timeEdit});
}
/**
* The main entry point for the application.
*
* @param args Array of parameters passed to the application
* via the command line.
*/
public static void main(String args[])
{
Application.run(new Receiver());
}
}//*****************************************************************************
// File : DataEvent.java
// This file contains implementation of following classes
// 1. TimerEvent
// copyright 1999: ABCOM Information Systems Pvt. Ltd., All rights reserved.
//*****************************************************************************
import com.ms.wfc.core.*;
//*****************************************************************************
// Class implemented : DataEvent
// Derived from : Event
// Implements : None
// Description : Creates a custom event
//*****************************************************************************
public class DataEvent extends Event
{
private int data;
public DataEvent(int data)
{
// Initialize the data variable.
this.data = data;
}
// Accessor method.
public int getData()
{
return data;
}
}
End Listing Three
|