Complete WindowedApp Solution

The complete Windows version of WindowedApp follows.

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import com.ms.wfc.io.*;
/**
 * 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 Form1 extends Form
{
 public Form1(String[] args)
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm();
 // I added the following call and the
 // next two functions manually
 processArgs(args);
 }
 /**
 * This function processes the input arguments
 * to the program.
 */
 void processArgs(String[] args)
 {
 // if an argument is present…
 if (args.length == 1)
 {
 // open it as a file name
 TextReader tr = new TextReader(args[0]);
 // read a line at a time; add each line
 // to an array of strings
 String s;
 String[] accum = new String[1];
 for (int offset = 0;
 (s = tr.readLine()) != null;
 offset++)
 {
 // if the array is not big enough…
 if (offset >= accum.length)
 {
 // double its size
 accum = expand(accum);
 }
 accum[offset] = s;
 }
 // now store the lines in the
 // text edit area
 edit2.setLines(accum);
 // and save the name of the file
 edit1.setText(args[0]);
 }
 }
 /**
 * Double the length of the array passed.
 * * @param array - the array of Strings to expand
 */
 public static String[] expand(String[] array)
 {
 // allocate an array twice as big
 String[] newArray = new String[2*array.length];
 // null out the array first
 for (int i = 0; i < newArray.length; i++)
 {
 newArray[i] = null;
 }
 // then copy over members from the source array
 for (int i = 0; i < array.length; i++)
 {
 newArray[i] = array[i];
 }
 return newArray;
 }
 /**
 * Form1 overrides dispose so it can clean up the
 * component list.
 */
 public void dispose()
 {
 super.dispose();
 components.dispose();
 }
 /**
 * This method is invoked when the user clicks Cancel.
 */
 private void button2_click(Object source, Event e)
 {
 dispose();
 app.exit();
 }
 /**
 * This method is invoked by the Submit button to
 * save the contents of the text edit window
 * into a file.
 */
 private void button1_click(Object source, Event e)
 {
 String fileName = edit1.getText();
 if (!fileName.equals(""))
 {
 TextWriter tw = new TextWriter(fileName, false);
 // get the contents of the text edit field
 // and save to the disk
 String[] array = edit2.getLines();
 for (int i = 0; i < array.length; i++)
 {
 tw.writeLine(array[i]);
 }
 tw.close();
 }
 }
 /**
 * 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();
 Label fileNameLabel = new Label();
 Edit edit1 = new Edit();
 Label label1 = new Label();
 Edit edit2 = new Edit();
 Button button1 = new Button();
 Button button2 = new Button();
 private void initForm()
 {
 fileNameLabel.setFont(
 new Font("MS Sans Serif", 14.0f,
 FontSize.CHARACTERHEIGHT,
 FontWeight.BOLD,
 false, false, false));
 fileNameLabel.setLocation(new Point(20, 20));
 fileNameLabel.setSize(new Point(80, 20));
 fileNameLabel.setTabIndex(0);
 fileNameLabel.setTabStop(false);
 fileNameLabel.setText("File Name");
 edit1.setAnchor(ControlAnchor.TOPLEFTRIGHT);
 edit1.setLocation(new Point(100, 10));
 edit1.setSize(new Point(180, 20));
 edit1.setTabIndex(1);
 edit1.setText("");
 label1.setFont(
 new Font("MS Sans Serif", 14.0f,
 FontSize.CHARACTERHEIGHT,
 FontWeight.BOLD,
 false, false, false));
 label1.setLocation(new Point(20, 50));
 label1.setSize(new Point(40, 20));
 label1.setTabIndex(2);
 label1.setTabStop(false);
 label1.setText("Text");
 edit2.setAnchor(ControlAnchor.ALL);
 edit2.setLocation(new Point(20, 70));
 edit2.setSize(new Point(260, 160));
 edit2.setTabIndex(3);
 edit2.setText("");
 edit2.setMultiline(true);
 button1.setAnchor(ControlAnchor.BOTTOMLEFT);
 button1.setLocation(new Point(30, 240));
 button1.setSize(new Point(75, 23));
 button1.setTabIndex(4);
 button1.setText("Submit");
 button1.addOnClick(new EventHandler(this.button1_click));
 button2.setAnchor(ControlAnchor.BOTTOMRIGHT);
 button2.setLocation(new Point(200, 240));
 button2.setSize(new Point(75, 23));
 button2.setTabIndex(5);
 button2.setText("Cancel");
 button2.addOnClick(new EventHandler(this.button2_click));
 this.setText("WindowedApp (Windows Version)");
 this.setAcceptButton(button1);
 this.setAutoScaleBaseSize(new Point(5, 13));
 this.setCancelButton(button2);
 this.setClientSize(new Point(292, 273));
 this.setNewControls(new Control[] {
 button2, button1, edit2, label1, edit1, fileNameLabel});
 }
 /**
 * The main entry point for the app. *
 * @param args - Array of parameters passed to the app
 * via the command line.
 */
 public static void main(String args[])
 {
 app.run(new Form1(args));
 }
}


As always in a Visual J Plus Plus app, even a Windows app, control begins with main(). (The main() function is at the bottom of the source file.) This function creates a new instance of our main form class, Form1, passing the input arguments as arguments to the constructor. The class Form1 extends the WFC class Form.

NOTE
Form is the WFC class that most corresponds to the AWT Frame class.

The first statement in Form1 is a call to initForm(), whose code constructs the form you built with the Forms Designer.

Immediately following is a call to the function processArgs(). If an argument is provided to the program when it is first started, processArgs() assumes it is a file name and reads the contents of the file into the multiline Edit control. The details of how it does this are discussed below.

NOTE
The function processArgs() and the helper function immediately following it are the only two functions in this program that I added completely without the help of the Visual J Plus Plus v6 Rapid app Development (RAD) tools. All of the other functions in this program were built either wholly or in part by the Windows app builder or the Forms Designer.

initForm() Method

On the one hand, as a programmer you might not be interested in the contents of the initForm() method at all. After all, you didn't create it and you shouldn't edit it except by means of the Forms Designer. Nevertheless, I think it is important to understand how the code generated by automatic tools like the Forms Designer works. You can learn a lot from the code generated by the experts. In addition, you never know when you might need to do the same coding tasks without the help of the Forms Designer.

Immediately preceding the initForm() method are all of the data members that initForm() references. Unless you change their names in the Properties window, they carry fairly mundane names like edit1, edit2, and so forth. (As you saw with the label fileNameLabel, you are free to change object names to anything meaningful to you.) Since these objects appear with initializers, they are actually constructed prior to initForm() being called.

NOTE
Initializers execute as part of the constructor. They are invoked after the constructor for the base class has been called and before the first statement in the constructor.

You can see that the objects are formatted within initForm() in the order you created them in the Forms Designer. In this example, the first object created was the fileNameLabel object. The first method called on this object is setFont(). This call creates a new font using the parameters provided in the Properties window.

NOTE
It is possible to change the font when using AWT, but the choice of fonts and font properties is very limited.

The initForm() method then sets the location of the label and its size. The class Point is nothing more than a holder for an x and a y value. The tab order is set by setTabIndex(). This is the order in which objects gain focus when the user presses the tab key. Control starts with element 0 and continues to element 1, 2, and so on until wrapping around back to 0 again. Finally, the call to setText() sets the actual label itself.

The remaining objects within the form are initialized following the same pattern. Once all of the objects have been initialized, the following statement is used to attach all of the individual objects to the Form1 object at once:

this.setNewControls(new Control[] {
 button2, button1, edit2, label1, edit1, fileNameLabel});


This statement creates an array of Control objects and initializes that array to references to the controls—button2, button1, edit2, and so on—that we created during the design phase. The resulting array of controls is passed to setNewControls(), which attaches each control to this, the Form1 object.

NOTE
This "allocate array off the heap and initialize" statement is a Visual J Plus Plus extension to C++.

Editing initForm()

Now that I've told you that you shouldn't edit initForm(), let me tell you that it is possible to do. As long as the Forms Designer is open, the area of code including initForm() is darkened and the Visual J Plus Plus Text editor won't let you touch it. However, once you close the Forms Designer you are free to change this function any way you want.

If you are careful and stick with the pattern of calls the Forms Designer has established, the changes you make in the Text editor will be picked up the next time you open the Forms Designer. To prove this point, let's close the Forms Designer. Now scroll into the initForm() method and change a property. I chose to add the following two lines to the edit2 section:

edit2.setAcceptsTab(true);
edit2.setWordWrap(false);


The first statement enables tab processing, which by default is disabled, and the second turns off word wrap.

Try changing something that is visible in the Forms Designer. I chose to change the title of Form1 by editing the call this.setText().

Now reopen the Forms Designer and select the object you edited. You'll notice that your changes are now visible in the Forms Designer. If you made the changes that I did, you'll see that the title of the form has been updated in the Forms Designer and that the acceptsTab and wordWrap properties of the edit2 Edit control in the Properties window have changed.

I wouldn't advise adding any extraneous code within initForm(), because the Designer won't know what to do with it, but if you prefer to edit object properties in the Text editor rather than use the Properties window, feel free.

processArgs() Method

The second method called by the Form1 constructor is the processArgs() method. As the name implies, this method processes the arguments passed to the program. If an argument isn't provided, the method exits without doing anything.

If there is a single argument, processArgs() assumes the argument is the name of a text file to read and passes the name to TextReader, which in turn creates a text reader object to read the input file. This it does by calling readLine() in a loop that executes until readLine() returns a null, indicating that there are no more lines of text to read. As each line is read in, it is added to an array of strings. If there is no more room in the array, the locally-defined method expand() is called to double the size of the array.

Once the input file has been read into an array of strings, this array is written into the edit box by means of the call edit2.setLines(). Finally, the name of the file is written into the file name edit field.

The expand() function doubles the size of the string array passed it. First, it allocates a new array of strings twice the size of the array passed to it. It then copies the contents of the input array into the new array before returning a reference to the new array.

Event Handlers

If you look closely at the objects being initialized in initForms(), you'll see that there is a significant difference between how the two buttons and the rest of the objects are initialized. The two buttons include a call to addOnClick(), passing it something called an EventHandler. To understand this call, you need to know something about event processing under AWT version 1.0, AWT version 1.1, and WFC.

AWT Version 1.0 event handling

The AWT that accompanied the original release of Java had a simple event-handling scheme. When an action occurred, such as a mouse button click or a keystroke, an object of class Event was created. This object was passed to the handleEvent() method of the Component object that was being pointed at or that had focus at the time of the event.

The programmer had the options of handling the event there or letting the default Component.handleEvent() method take care of it. The default method would first try to give someone else a chance by passing the event to whatever Container object the component was attached to. Thus, a Button object would pass the event to the Frame object to which it was attached. This "bubbling up of events" enabled the programmer to handle all events in one central location.

If the event was not handled in any of the handleEvent() methods, Container.handleEvent() would do one of the following:

For example, the following program uses the action() method of the parent frame to detect a button being clicked:

import java.awt.*;
public class Class1 extends Frame
{
 Button okButton;
 Button cancelButton;
 public void init()
 {
 add(okButton = new Button("OK"));
 add(cancelButton = new Button("Cancel"));
 setVisible(true);
 }
 /**
 * Gets called whenever an object within the frame
 * is "activated."
 */
 public boolean action(Event e, Object o)
 {
 // check to see which object was activated
 if (e.target == okButton)
 {
 // handle the OK button here…
 return true; // indicates we've handled event
 }
 if (e.target == cancelButton)
 {
 // handle the Cancel button here…
 return true;
 }
 // return false to indicate we haven't handled
 // the event
 return false;
 }
}


The action() method in the frame class receives action events that occur within the frame. The method must then test which object received the action event. In this case, we are interested only in the two buttons, since these are the only objects we added to the frame.

The problem with this approach is that it isn't very neat. The single action() method can get pretty complicated if there are a lot of components in the frame. Worse yet, it disperses the button logic across the app. Pretty soon the single action() method contains logic for menu items, along with check boxes, radio buttons, and other object types all mixed together.

A more modular means of handling the action event within the button object is as follows:

import java.awt.*;
public class Class1 extends Frame
{
 public void init()
 {
 // TODO: Add initialization code here
 add(new OKButton());
 add(new CancelButton());
 setVisible(true);
 }
}
class OKButton extends Button
{
 OKButton()
 {
 super("OK");
 }
 public boolean action(Event e, Object o)
 {
 // handle the OK button here
 return true; // indicates we've handled event
 }
}
class CancelButton extends Button
{
 CancelButton()
 {
 super("Cancel");
 }
 public boolean action(Event e, Object o)
 {
 // handle the Cancel button here
 return true; // indicates we've handled event
 }
}


Here the action event handler has been moved to the component by creating a separate subclass for each button, with each subclass having its own action() method. This approach is modular in that it keeps each button's event handling code together with the rest of the button's logic and separate from the event handling code of other components. Thus, CancelButton.action() contains the code to handle the Cancel button and nothing more. The primary drawback with this approach is that it ends up creating a lot of subclasses.

NOTE
This event-handling approach that uses the handleEvent() and action() methods is still supported in Visual J Plus Plus v6. In fact, this is the method we used for event processing in . However, both handleEvent() and action() are marked as deprecated, which means that although the method is still present it might be removed in future versions of the language.

AWT 1.1 event delegation

Seeing the benefits of modular event processing, the authors of Java version 1.1 formalized this concept somewhat in a mechanism known as event delegation.

Event delegation uses an event-processing model based on special interfaces known as listeners. For example, the action event can be handled by any class that implements the ActionListener interface. To implement the ActionListener interface, a class has only to provide the function actionPerformed(). Other listener interfaces are provided for other classes of events, each with its own version of the actionPerformed() method to be implemented.

Event delegation gives the programmer increased flexibility. The listener for a given component can be in a separate class. In some cases, multiple components can share the same listener. More often, the listener is an inner class within the component itself, as shown in the following example.

import java.awt.*;
import java.awt.event.*;
public class Class1 extends Frame
{
 public static void main(String[] args)
 {
 (new Class1()).init();
 }
 public void init()
 {
 add("West", new OKButton());
 add("East", new CancelButton());
 setVisible(true);
 }
}
class OKButton extends Button
{
 class OKActionListener implements ActionListener
 {
 public void actionPerformed(ActionEvent ae)
 {
 // handle the OK button action here
 }
 }
 OKButton()
 {
 super("OK");
 // add an object to the list of listeners for
 // an action on this object
 addActionListener(new OKActionListener());
 }
}
class CancelButton extends Button
{
 class CancelActionListener implements ActionListener
 {
 public void actionPerformed(ActionEvent ae)
 {
 // handle the Cancel button action here
 }
 }
 CancelButton()
 {
 super("Cancel");
 addActionListener(new CancelActionListener());
 }
}


Here you can see that the OK and Cancel buttons each define their own inner classes to implement the ActionListener interface. The call to addActionListener() tells AWT to invoke the class's listener when an action occurs on that button. Inner classes were a new feature with version 1.1 of the Java standard, added specifically to support event delegation.

NOTE
A listener doesn't have to be an inner class. Using an inner class keeps all of the component logic together.

Listeners are even more modular than their predecessor. The action processing code can be both bundled up within the button class and at the same time segregated into an inner class.

problems with handleEvent() and event delegation

Both of the event-handling mechanisms we've been discussing have problems. The handleEvent() mechanism, although simple, suffers from numerous deficiencies. First, there is the problem of function complexity that I already mentioned. Second, the event linkage is completely static. That is to say, the decision where a particular event will be processed is made at compile time and can't be changed while the program executes.

Event delegation solves both of these problems. First, it encourages the programmer to handle the event in the object closest to where the event occurred. Second, the action listeners can be added and removed during program execution using the addOnXXX() and removeOnXXX() methods, where XXX is the name of the event. Third, multiple action listeners can be added to the same event, resulting in a sort of multicast capability.

There are several problems with event delegation, however. First, event delegation results in the creation of multiple, often trivially small classes to handle events for the different objects. A large form with a large number of objects requires more code to define listeners than to actually do the work. This situation is somewhat relieved by the addition of inner classes, but not completely. To use event delegation, the Forms Designer would need to create and keep track of numerous small listener classes.

Second, to use event delegation, tools must have access to the source code. This isn't a problem for programmer tools such as the Forms Designer; however, more and more users are looking for fourth-generation and fifth-generation tools that enable dynamic linking of objects by the user.

Delegates

In response to some of the problems with the conventional and event delegation models for event handling, Visual J Plus Plus v6 has introduced a new event-handling mechanism that Microsoft calls delegates.

What is a delegate?

A delegate is a class that can be used to reference an object/method combination. It is easier to explain a delegate by example than with words. The following code, from the DelegateDemo app on the companion CD, declares a delegate called FunctionDelegate, assigns a method to the delegate, and finally, invokes that method through the delegate.

import com.ms.wfc.io.*;
// declare a delegate for a function that accepts two
// ints and returns an int (this actually creates
// a subclass of the class Delegate)
delegate int FunctionDelegate(int a, int b);
public class Class1
{
 // declare an object of type FunctionDelegate
 FunctionDelegate f;
 public static void main (String[] args)
 {
 (new Class1()).test();
 }
 /**
 * This function compares its two arguments, outputs
 * an indication of which is greater, and then returns
 * the greater of the two values. (No consideration is
 * made for equal values.)
 * This function is only intended as a test of
 * FunctionDelegate.
 */
 int targetFunction(int a, int b)
 {
 Text.out.writeLine("A is " +
 ((a > b) ? "greater" : "less") +
 " than B");
 return (a > b) ? a : b;
 }
 Class1()
 {
 // create a new object of class FunctionDelegate
 // with the function targetFunction() and the
 // current object
 f = new FunctionDelegate(this.targetFunction);
 }
 /**
 * This function simply tests FunctionDelegate.
 */
 void test()
 {
 int a = 10;
 int b = 20;
 Text.out.writeLine("A = "
 + a
 + ", B = "
 + b);
 Text.out.writeLine("Invoking delegate");
 int result = f.invoke(a, b);
 Text.out.writeLine("Delegate returned "
 + result);
 }
}


This example defines a delegate named FunctionDelegate, which extends the class Delegate. Class1 includes a data member f of class FunctionDelegate. The constructor for Class1 creates a new FunctionDelegate object with the object/method combination of this.targetFunction().

Let's stop for just a minute and analyze what has happened so far. The keyword delegate is unique to Visual J Plus Plus v6; it isn't a part of the standard Java language and isn't ever likely to be given Sun Microsystem's pronouncements on the subject. (If you care about my opinion, see the sidebar at the end of this section.)

The following assignment creates an object of class FunctionDelegate and initializes it with the object/method combination of this and targetFunction.

f = new FunctionDelegate(this.targetFunction)


The this object is of class Object and the object targetFunction is of class Method. (The class Method was introduced to the Java language by Oracle as part of the language reflection package [java.lang.reflect].)

Notice that the prototype of targetFunction() matches the declaration of FunctionDelegate exactly. In other words, FunctionDelegate was declared to accept methods that take two integers as arguments and return an integer. That's exactly what was passed when the object f was created.

CAUTION
The delegate declaration must exactly match the type of the method passed to the delegate class constructor. (Note, however, that the type of the target object isn't specified.)

Having created a delegate object f, we can invoke the object/method pair pointed at by f using the method invoke(). This is exactly what the function test() does. First, test() defines a couple of integers, a and b. It then invokes the object and method referenced by f and passes them the arguments a and b. The output of this simple test program is shown in Figure 5-8.

Java Click to view at full size.

Screenshot-8. The output of the DelegateDemo program, which declares and then invokes a delegate.

What About the Delegate Keyword?

Personally, I am a little ambivalent about the introduction of the delegate keyword. I've never been a big supporter of religious wars of language purity; but still, it's not good to add new keywords to a language, especially a language designed to be portable like Java. It's actually worse than you might think. The implementation of delegates requires additions to the Java Virtual Machine (VM). Thus, code generated using the delegate keyword won't execute on another vendor's VM.

On the other hand, Oracle isn't above such shenanigans. After all, Sun introduced the inner class feature primarily to make their newly introduced event delegation less objectionable. Further, the Visual J Plus Plus delegate feature does make it easier to write event handlers. Gone is the need to construct an entire class just so you can tie a single function to an event. And of course, there's always the argument that you don't have to use delegates. But WFC uses delegates, so as soon as you adopt WFC you are in Windows territory anyway.

In effect, Microsoft has introduced to the Java language a type-safe, object-oriented way of defining C++ callback functions. Recent C++ converts to Java will love this feature—and Java purists will hate it. I would love to see the delegates feature become part of standard Java, but until it does I remain ambivalent.

Delegates and WFC event handling

WFC uses delegates as a means of registering event handlers. For example, WFC includes a delegate class MouseEventHandler. The Control class prewires the mouse events, such as mouse down, mouse up, and mouse move, to invoke any registered MouseEventHandler objects. An app program registers a MouseEventHandler by calling addOnXxx(), where Xxx represents the type of event. (For every addOnXxx(), there is a corresponding RemoveOnXxx() that removes the handler delegate.)

Returning to our WindowedApp example, let's look at this statement:

button1.addOnClick(new EventHandler(this.button1_click));


This line of code creates an EventHandler delegate object with the current object (this) and the method button1_click() and registers the delegate object (perhaps as one of several) to handle the onClick event emanating from button1.

How is a delegate different from a C callback function?

Visual C++ programmers will immediately recognize delegates as the Java equivalent of a callback function in C. Some operating systems use this callback mechanism to enable a program to register a function to handle a particular event. (The XWindows/Motif system makes significant use of callback functions.)

NOTE
In C one could make the following declaration: void addOnClick(int (*)(int, int));. This declares a method addOnClick() that takes as its argument the address of a function. That function takes two integers and returns an integer. (Presumably this would be the function to handle the onClick event.)

Both callbacks and delegates are an efficient means for handling such events; however, delegates are superior to C callback functions in several ways. First, delegates are type safe. Theoretically, C callback functions are also type safe, but because you can recast a pointer in C, in practice programmers always seem to pass invalid program addresses. Java doesn't allow recasting. The method passed to the delegate's constructor must match the delegate's declaration. In addition, the Delegate class checks to make sure that what it's being passed is actually a method.

Second, callback functions are not inherently object-oriented. For example, Motif makes no provisions for the object half of the object/method pair in its callback mechanism. This is a constant nuisance to the C++ programmer. Delegates are built to store both the object and the method.

Finally, delegates are secure. A less-trusted piece of code can't use a delegate to gain access to more-trusted code. (Trusted and untrusted code types will be discussed in the section on applet security in . All app code is trusted.)

Multithreading

There is still one more aspect of the WindowedApp app that remains unexplained: the last function in the file but the very first to execute, main(). The main() function contains a single statement:

app.run(new Form1(args));


The constructor for Form1 creates the form as edited by the Forms Designer. The resulting Form object is passed to app.run(). This function enters the Windows message dispatch loop, which fields Windows messages and dispatches them by means of the event-handling mechanism described earlier. Control remains in this message dispatch loop until the Form calls app.exit() to terminate the program.

If you are a seasoned Windows programmer, this last paragraph will make perfect sense to you. If it does not, don't panic. I will present a complete discussion of app and multithreading in 10.

Comments