Using J/Direct to Implement Idle Event Processing

You can use J/Direct to implement an efficient idle event handler. All Windows events pass through a message queue in the form of Windows messages. When the message queue is empty, Visual J Plus Plus v6 calls the idle event handler to perform whatever background processing the app requires. During the time that the event handler is running, the app can't read a message from the message queue, which makes the app unresponsive to user input. Therefore, it is important that most idle event handlers execute for only a short time before giving up control.

However, if the idle event handler could peek into the message queue, it could retain control as long as no message were waiting in the queue to be processed. As soon as the event handler detected a message, it could give up control to allow the system to pick up the message and process it. Once all messages had been processed, Visual J Plus Plus v6 would pass control back to the event handler.

In fact, the Win32 API function PeekMessage() enables a calling function to check whether the Windows message queue is empty. J/Direct gives us access to this PeekMessage() function.

NOTE
Here is a case where no Visual J Plus Plus v6 library method exists that is the equivalent to the Win32 API function.

Problem

Let's create a new prime number generator to demonstrate the PeekMessage() API background processing method. We'll call this version IdleEvent2. This app will have an edit box to display prime numbers as it calculates them. In addition, there will be Start and Stop buttons to turn background processing on and off. Finally, to demonstrate that the app's performance is not affected by the idle processing, we'll outfit the IdleEvent2 app with a simple drawing function.

Our Previous Solution


Back in , we created an app called IdleEvent1 that uses the idle event to perform background processing. That app performs idle-time processing for a certain length of time, and then gives up control to allow other events to be processed. If the time the idle event handler method spends executing is not kept short, the app execution becomes jerky.

The problem with that app is that as soon as the event handler returns WFC assumes that the app is done. Thus, IdleEvent1 has to generate some event for the program to process to prod WFC into calling the idle event handler again. IdleEvent1 sets a WFC timer to send a timer event every 100 milliseconds. I remarked at the time this example was discussed that there had to be a better way. The PeekMessage() Win32 API method is that better way.

Forms Designer Work

Choose New Project from the Visual J Plus Plus v6 File menu, and create a new Windows app. Name the new project IdleEvent2. Use the Project Explorer to examine the new project.

Open Form1.java in the Forms Designer. Create the same form display that we created in the IdleEvent1 app in . You can see the final display in Figure 11-6. The idle process will output prime numbers in the edit box at the top of the form while the user draws in the panel at the bottom.

Code for Form1

The following code represents the Form1.java code.

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import com.ms.wfc.util.*;
/**
 * This class demonstrates the use of idle processing using
 * the idle process.
 */
public class Form1 extends Form implements IdleParent
{
 // define holder for the idle process handler
 IdleProcess idle;
 public Form1()
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm(); // create an idle process
 idle = new IdleProcess(this, this);
 }
 /**
 * Set the current prime in the current app.
 */
 public void setPrime(int numberOfPrimes,
 long prime)
 {
 outputEdit.setText(toString(numberOfPrimes) +
 "-" +
 toString(prime));
 }
 /**
 * Convert a number into a string with commas.
 */
 private String toString(long value)
 {
 // convert the long into a string
 String s = Long.toString(value);
 // now add commas to that string
 StringBuffer sb = new StringBuffer(20);
 int offset = 0;
 for(int length = s.length();
 length > 0;
 length--)
 {
 // every 3 digits from the right side…
 if ((length % 3) == 0)
 {
 // but not at the beginning…
 if (offset != 0)
 {
 // put a comma
 sb.append(',');
 }
 }
 // now add another digit
 sb.append(s.charAt(offset++));
 }
 return sb.toString();
 }
 /**
 * Form1 overrides dispose so it can clean up the
 * component list.
 */
 public void dispose()
 {
 super.dispose();
 }
 /**
 * Turn idle processing on.
 */
 private void button1_click(Object source, Event e)
 {
 idle.setActive(true);
 }
 /**
 * Turn idle processing off.
 */
 private void button2_click(Object source, Event e)
 {
 idle.setActive(false);
 }
 /**
 * Allow the user to draw within the paint panel while
 * the background onIdle processor continues to operate.
 */
 List squiggles = new List();
 List squiggle = null;
 private void paintPanel_mouseDown(Object source, MouseEvent e)
 {
 // if the right mouse button is clicked…
 if ((e.button & MouseButton.RIGHT) != 0)
 {
 // clear the list of squiggles and trash
 // the current squiggle
 squiggles.setSize(0);
 squiggle = null;
 // now repaint the blank screen
 paintPanel.invalidate();
 }
 // if the left mouse button…
 if ((e.button & MouseButton.LEFT) != 0)
 {
 // create a new squiggle
 if (squiggle == null)
 {
 squiggle = new List();
 }
 }
 }
 /**
 * As the mouse moves along with the left button held down,
 * add each reported point to the current squiggle.
 */
 private void paintPanel_mouseMove(Object source, MouseEvent e)
 {
 if ((e.button & MouseButton.LEFT) != 0)
 {
 squiggle.addItem(new Point(e.x, e.y));
 paintPanel.invalidate();
 }
 }
 /**
 * When the user releases the mouse button, save
 * the current squiggle in the list of squiggles.
 */
 private void paintPanel_mouseUp(Object source, MouseEvent e)
 {
 // if there is a current squiggle…
 if (squiggle != null)
 {
 // add it to the squiggle list and
 // start the current squiggle over
 squiggles.addItem(squiggle);
 squiggle = null;
 }
 }
 /**
 * Paint the squiggles the user has created so far.
 */
 private void paintPanel_paint(Object source, PaintEvent e)
 {
 Graphics g = e.graphics;
 // draw the current squiggle
 if (squiggle != null)
 {
 drawSquiggle(g, squiggle);
 }
 // iterate through previously saved squiggles,
 // drawing each one
 IEnumerator iter = squiggles.getItemEnumerator();
 while (iter.hasMoreItems())
 {
 List list = (List)iter.nextItem();
 drawSquiggle(g, list);
 }
 }
 /**
 * Draw a squiggle. A squiggle is a list of point segments.
 */
 private static void drawSquiggle(Graphics g, List squiggle)
 {
 try
 {
 // get the list of all squiggles
 Object[] list = squiggle.getAllItems();
 int length = list.length;
 // draw from each point to the next
 Point previous = (Point)list[0]; for (int i = 1; i < length; i++)
 {
 Point current = (Point)list[i];
 g.drawLine(previous, current);
 previous = current;
 }
 }
 catch(Exception e)
 {
 }
 }
 /**
 * NOTE: The following code is required by the Visual J Plus Plus form
 * designer. It can be modified using the form editor. Do not
 * modify it using the code editor.
 */
 Container components = new Container();
 Edit outputEdit = new Edit();
 Label label1 = new Label();
 Button button1 = new Button();
 Button button2 = new Button();
 Panel paintPanel = new Panel();
 private void initForm()
 {
 // …generated by Forms Designer…
 }
 /**
 * The main entry point for the app. */
 public static void main(String args[])
 {
 app.run(new Form1());
 }
}


The majority of this code is dedicated to user drawing. The mouseDown event handler is the paintPanel_mouseDown() method. When the right mouse button is clicked while the mouse pointer is in the drawing panel, paintPanel_mouseDown() clears the drawing panel. When the left mouse button is clicked, paintPanel_mouseDown() creates a squiggle object that will contain a list of mouse locations. The mouseMove event handler continues to add mouse locations to the current squiggle object as long as the left mouse button is held down. The mouseUp event handler terminates the squiggle, and adds the squiggle object to a container of squiggle objects. The paintPanel_paint() method draws the squiggles one at a time, by drawing a line between each consecutive point in a squiggle.

The Form1() constructor creates an object of the user-defined class IdleProcess, which we'll look at in a moment. This object represents the background processing element. In addition, the Form1 class contains a setPrime() method that displays the prime number data in the outputEdit object. The setPrime() method is required because Form1 implements the IdleParent interface.

The IdleParent interface is defined as follows:

public interface IdleParent
{
 // the following method is called
 // to report a change in the current
 // prime number
 public void setPrime(int numberOfPrimes,
 long prime);
}


A class that implements the IdleParent interface accepts a prime number along with the number of primes detected to date via the setPrime() method.

Code for the J/Direct Interface

Before we can write the IdleProcess background process, we'll need access to the PeekMessage() Win32 API function.

Open the J/Direct Call Builder (choose Other Windows from the View menu). Clear the Structs and Constants check boxes, leaving only the Methods check box selected. Scroll down through the list of API functions. Select PeekMessage and choose Copy To Target.

The J/Direct Call Builder creates the Win32.java file. Edit the file to look like the following code:

public class Win32
{
 /**
 * Peek into the message queue to see if there's anything to do.
 * @dll.import("USER32",auto) */
 public static native boolean PeekMessage(com.ms.win32.MSG lpMsg,
 int hWnd,
 int wMsgFilterMin,
 int wMsgFilterMax,
 int wRemoveMsg);
 public static final int PM_NOREMOVE = 0x0000;
 public static final int PM_NOYIELD = 0x0002;
}


The PeekMessage() function takes a number of arguments. The first argument is the message that's at the top of the event queue. IdleEvent2 doesn't care what that message is; all it cares about is whether there is one. The hWnd argument is the window handle for the main form. (We haven't discussed window handles so far, and we won't now. Suffice it to say that the Form class provides a getHandle() method to retrieve the form's window handle.) The min filter and max filter arguments enable the program to look for a particular message or range of messages. Setting both to 0 indicates that the program is interested in all messages. The last flag indicates whether the message should be removed from the queue. (For this program it shouldn't, so the flag will pass PM_NOREMOVE).

Code for the IdleProcess Class

We can now create the background process class, which we'll call IdleProcess. Choose Add Item from the Project menu. From the list of available items, select Class. Enter the name IdleProcess, and choose Open.

Update the IdleProcess class to implement the onIdle background processing so the code looks as follows:

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
/**
 * Implement processing in the background by attaching
 * to the idle event.
 */
public class IdleProcess
{
 // parent object
 IdleParent parent;
 // variable necessary to use the PeekMessage call:
 // window handle of the parent form
 int hWnd;
 // a dummy message
 com.ms.win32.MSG dummyMsg = new com.ms.win32.MSG();
 // idle event handler
 EventHandler idleHandler;
 // note whether idle loop is active or not
 boolean active = false;
 // number of primes found and the last
 // candidate for 'primehood'
 int numberOfPrimes = 0;
 int lastCandidate = 0;
 int lastPrime = 0;
 /**
 * Create an idle process that reports back
 * to the parent through the IdleParent interface.
 */
 public IdleProcess(IdleParent parent, Form form)
 {
 // save the parent
 this.parent = parent;
 // assign an onIdle event handler
 app.addOnIdle(new EventHandler(this.onIdle));
 // save the window handle of the parent form
 hWnd = form.getHandle();
 }
 /**
 * Turn the idle process on and off.
 */
 public void setActive(boolean active)
 {
 this.active = active;
 }
 /**
 * Invoked when the system is idle.
 */
 private void onIdle(Object sender, Event e)
 {
 // if we're active…
 if (active)
 {
 // work until the system has something to do
 while(!Win32.PeekMessage(dummyMsg, hWnd, 0, 0, Win32.PM_NOREMOVE | Win32.PM_NOYIELD))
 {
 // try the next number
 if (isPrime(++lastCandidate))
 {
 // it's prime, so count it
 numberOfPrimes++;
 lastPrime = lastCandidate;
 // report the results back to parent
 parent.setPrime(numberOfPrimes,
 lastPrime);
 }
 }
 }
 }
 /**
 * Test next number for prime.
 */
 private boolean isPrime(long number)
 {
 // …same as in earlier prime number processes…
 }
}


We've added two data members to the IdleProcess class in IdleEvent2 that were not in IdleEvent1: hWnd, which holds the form's window handle, and dummyMsg, which will receive the message from the PeekMessage() method. The data type of dummyMsg is chosen to match the prototype for PeekMessage() in Win32.java. The constructor for com.ms.win32.MSG() creates a message object to store the message that is read.

The hWnd data member is initialized in the IdleProcess() constructor with the window handle of the current form, by invoking the form.getHandle() method. (Again, it doesn't matter what a window handle is, because we won't use it for anything except to call PeekMessage().)

The IdleProcess() constructor assigns the onIdle() method to handle the idle event. The button1_onClick() and button2_onClick() methods use the IdleProcess.setActive() method to turn background idle processing on and off.

The IdleProcess.onIdle() method starts by checking the active flag. If the active flag is set to false, meaning that background processing has been turned off, onIdle() returns immediately without taking any action. If active is set to true, onIdle() enters a loop to constantly check the Windows message queue.

The onIdle() method continues to loop as long as PeekMessage() returns false, which indicates that the message queue is empty. The two flags passed to PeekMessage() that were copied from the windows.h file—Win32.PM_NOREMOVE and Win32.PM_NOYIELD—indicate that the program is not to remove any message it finds in the queue nor yield control to some other thread as a result of the call. As soon as PeekMessage() returns true, onIdle() exits to allow the message to be processed. Within the loop, onIdle() calculates prime numbers and displays them using the setPrime() method provided by the parent class.

Results

Screenshot-6 shows the IdleEvent2 app in action. IdleEvent2 runs about the same as IdleEvent1, but is considerably faster. In addition, the user drawing panel in

Screenshot

Screenshot-6. The IdleEvent2 app supports user drawing while calculating prime numbers during idle time.

IdleEvent2 updates more rapidly, resulting in smoother squiggles that update morerapidly. This is because the amount of time spent calculating prime numbers in the onIdle() event handler is synchronized with external activity.

CAUTION
There is a certain danger in this approach. Since onIdle() doesn't yield control until a message appears in the Windows message queue, if Visual J Plus Plus v6 or any other part of your program defines a second idle event handler, the second handler will never get control.
Comments