Java keeps getting better at handling those bread-and-butter windowing tasks that once required C or C++ under Windows.
Introduction
Java promises nothing less than the holy grail for those who develop code for multiple platforms. One set of object-oriented source code can provide cross-platform portability with a graphical user interface. What Java has delivered thus far, however, may fall somewhat short of this lofty goal. Byte-code interpreters run much slower than compiled code and some user interface components demonstrate sometimes quirky behavior. Nevertheless, Java is getting better and it presents intriguing possibilities, particularly for C++ programmers who will find many things familiar in this new language.
This article describes a Java implementation of a device that I find handy. I use it often for quickly setting up a user interface for small prototype application programs. The device is a list dialog that handles lists of data objects, each of which knows how to update its own value through a dialog that is specific for that data type. For example, the article defines object classes representing numeric float data, Boolean yes-no choices, and file names, each with its appropriate dialog. The idea easily extends to other data types, such as color choices. New input items can be added to the dialog simply by adding additional items to the list. This is particularly useful in Java, where the layout interface approach of the AWT (Abstract Window Toolkit) complicates the process of designing and modifying dialogs composed of many different components.
The code accompanying this article (available from CUJ online sources) implements a complete Java application, using a Frame window with a menu to call up the list dialog and its associated data-object dialogs. The code was developed using Borland's JBuilder Standard, version 2.0, using version 1.1.6 of the JDK (Java Development Kit). I have also tested the code with Asymetrix Supercede 2.0, which uses JDK 1.1. JDK 1.1, or later, is needed to obtain proper dialog behavior, as discussed below. Also, JDK 1.1 introduced significant changes in the AWT classes, with many of the old variable names and methods deprecated (though still retaining functionality).
The code in this article uses the new names wherever possible. For this reason, you need JDK 1.1 or later to compile and run the code listed here. The code uses only standard Java and AWT classes, with no classes from any other libraries or toolkits. Also, I do not make use of any of the "visual" development capabilities. You should be able to produce a working version of this code with a text editor and a copy of the JDK, version 1.1 or later.
The ideas in this article are similar to a C/C++ Windows implementation that I presented in the September 1995 issue of C/C++ Users Journal, in the article "Data Object List Dialog for Windows." That article also appears as a chapter in the book, Windows NT Programming in Practice, R&D Books, 1997. The present article will point out similarities and differences between the Java approach and the C/C++ Windows implementation discussed in the earlier article.
Dialog Classes
Dialogs are a cornerstone of any good GUI (Graphical User Interface). If you know how to build dialogs for input and you can write graphics output to a window, you are well on your way to being able to build complete GUI applications. Incredibly, dialogs were one of the weakest aspects of early versions of the Java AWT. The problem was that these early AWT dialog classes lacked modal behavior.
From the user's point of view, modal behavior is not that big a deal. It simply means that no other windows or dialogs in the application can be accessed until the open modal dialog has been closed. However, from the programmer's point of view, modal behavior is more important. It means that program execution stops and waits while the modal dialog is open. If you have a line of code that displays and runs the modal dialog, then you can depend on the next line of code having available to it the latest user input from that dialog.
In earlier Java versions, program execution continued even while the "modal" dialog was open. This proved to be particularly disastrous for the application discussed in this article, which depends on dialogs delivering their data at very specific points in the code. Fortunately, modal dialog behavior was corrected in JDK 1.1.
The AWT provides a Dialog class, which produces a dialog window capable of taking input from the user. This basic class does not provide much functionality, other than defining message-capturing capability and, as of JDK 1.1, providing true modal behavior as an option. Class Standard_dlg, shown in Standard_dlg.java (Listing 1), extends the Dialog class by providing some additional features that are useful for every dialog, such as OK and Cancel buttons with appropriately defined responses.
As of JDK 1.1, component actions such as pressing a button are handled by means of ActionListener classes. This is a somewhat more complicated approach than earlier versions, which handled actions directly with the action class method. The Standard_dlg class uses the methods ok_response and cancel_response to isolate the response actions to the OK and Cancel buttons. Separate ActionListener classes then call these methods. Note that the dialog itself is passed as an argument to each ActionListener class to enable access to these methods. C++ programmers will recognize the use of this as an argument, although here it represents an object rather than a pointer. In this way, descendant dialog classes do not need to redefine their own ActionListener classes. They need only override the ok_response and cancel_response methods.
ActionListener classes together with their ActionPerformed methods play a role similar to callback functions in C/C++ for Windows and X-Windows. However, C/C++ callback functions typically need to be global or static functions. They cannot, for example, be member functions of C++ classes. Java has the advantage of allowing any object type as an argument in an ActionListener constructor. Using the dialog itself as an argument allows access to the dialog's own class methods as "callback functions."
Listing 2, Textfield_dlg.java, shows the code for the Textfield_dlg class, which adds an AWT Textfield control for input of text string data. A Textfield is an AWT object that allows the editing of a single line of text. The String variable value_str holds the text that this dialog controls. Descendant dialog classes use value_str to hold the display string corresponding to the data value that they control. A dialog should update the value under its control only when the user hits the OK button. If the user exits the dialog via the Cancel button, then the dialog should revert to the previously stored value.
Textfield_dlg accomplishes this behavior by overriding the ok_response method to update value_str via Textfield's getText method and overriding the cancel_response method to restore value_str to the display field (so that it, rather than the user-entered string, will be displayed the next time the dialog is opened) using Textfield's setText method. Note that there is no need to define new ActionListener classes.
Listing 2 also shows the code for the Float_dlg class, which extends Textfield_dlg to extract numeric float values from the input string. This dialog controls the float variable value. Java makes it very easy to display a numeric value as a String, using the toString method, and to extract a numeric value from a String representation, via the valueOf method. This raises the possibility of non-numeric exception errors due to inappropriate user input. I discuss the handling of these errors below.
The Yes_no_radio_dlg class (Listing 3, Yes_no_radio_dlg.java) implements a two-choice Checkbox group as a radio-button control. Grouping the Checkbox controls in a CheckboxGroup implements standard radio-button behavior exactly one Checkbox can be selected at any one time. The control variable for this dialog is the Boolean variable answer_is_yes. The ok_response method retrieves the selected Checkbox and sets answer_is_yes to either true or false, depending on whether the yes_box or no_box has been selected. The cancel_response method restores yes_box and no_box to their previous states, regardless of the user's actions.
Layouts vs. Resources
The dialog code shown here introduces the concept of layout managers, one of the primary differences between the AWT and C/C++ Windows development. Windows developers are accustomed to working with resources to define the appearance of components such as dialogs. However, the cross-platform nature of Java requires a more adaptable approach. Layout managers automatically arrange the components of a user interface container (such as a window or dialog) according to a set of rules specific for that particular manager. There are a number of pre-defined layout managers, and you can also define your own custom versions.
Dialogs use the BorderLayout interface by default. We will also briefly encounter FlowLayout and GridLayout in this article. BorderLayout specifies five regions of the container: North, South, East, West, and Center. For example, North is a good place to position the dialog text label, while the Center region naturally accommodates input controls such as text fields and checkboxes.
To get a little more precision in the placement of components within these regions, it is useful to first define an AWT Panel object to contain the component or components. For example, a single Panel contains both the OK and Cancel buttons in the South region of the dialog. Panels use the FlowLayout manager as their default layout, which simply inserts new components from left to right, like a text editor. GridLayout, as the name implies, arranges components in a grid consisting of a specified number of rows and columns. The Yes_no_radio_dlg class uses a simple 2X1 grid to position its two Checkbox controls in a single column.
The visual Java development tools, such as JBuilder, Microsoft's Visual J++, and Symantec's Visual Café, all have some type of resource-oriented user interface design capability. While it may appear that you are using resources similar to those used by Windows, the output of these visual development tools is Java code, sometimes proprietary and not always portable to non-Windows platforms, based on some type of layout manager.
Exception Handling
The Float_dlg class provides an example of exception handling in Java. Because we are dealing with user input here, one of the things that can go wrong is that the user may try to enter non-numeric data in the input field. As it turns out, Java has excellent exception-handling capabilities that are very easy to implement. Even without specific exception-handling code, the program will not crash if the user enters invalid data. Java will note the exception in the console window, and the dialog will continue to display.
The problem is that once the user hits OK, the invalid data has made its way into the display field (although the float control field value remains uncorrupted) and will remain there unless we clear it out. We have no way of knowing that the data is not valid unless we handle the exception ourselves. Fortunately, as I mentioned, Java makes it very easy to do this.
Like C++, Java implements try blocks and catch blocks for handling exceptions. Simply enclose the call to valueOf inside a try block. Immediately following the try block, insert a catch block that includes code to recognize and handle the exception. In this case, the exception is NumberFormatException. (If you don't know the name of the exception, run the program without exception handling and Java will identify the exception in the console window when it occurs.) In response, the catch block simply prints the offending input text to the console window, and restores the previous value_str to the Textfield. (A more civilized application might inform the user via a message dialog rather than the console window.)
If the input data is valid, then the inherited ok_response will load the display string into value_str. In addition to checking for non-numeric input, you may also want to include allowable maximum and minimum values in the Float_object class and check for those as well. The ability to screen data at the point of user entry is one of the advantages of using dialogs for input.
Data Objects
The data object classes shown in Listing 4, Data_object.java, tie data values to dialogs suitable for updating those values. So, for example, Float_object is a class that contains a float variable value and a method get_new_value that executes a Float_dlg dialog. These data object classes are derived from the abstract Data_object class, which does not handle any specific data type, and whose functionality is limited to building a display string from a description string and a value string.
Each of the derived data object classes contains a data-specific get_new_value method. In my C++ Windows implementation, get_new_value calls a stand-alone function which instantiates, executes, and deletes the appropriate dialog class. Java does not allow stand-alone functions. Rather, each function call must be a method belonging to an instantiated object class. So here the get_new_value method directly instantiates, executes, and disposes of the appropriate dialog object class.
Note that each dialog class requires a parent Frame object. Rather than tie the data object itself to a particular Frame object, which may not be known when the data object is created or may change during the application execution, I assign the Frame object parent as a parameter in the get_new_value method.
In addition to Float_object, I define two additional data-object types for the example of this article:
- Boolean_object controls a Boolean data value, with get_new_value calling Yes_no_radio_dlg to update this value.
- Filename_object updates a String variable containing a file name. The get_new_value method for this class calls the built-in AWT class FileDialog, which implements the host platform's standard file browser.
You can add to these data object types with classes of your own. For example, you could use the Textfield_dlg class to define a String data object. In my C++ Windows implementation, I define a color object that uses the standard Windows ChooseColor dialog. The Java AWT does not have its own color chooser dialog class, however it is not difficult to find examples of such a dialog in the various Java information sources.
The final data-object class defined in Listing 4 is the general purpose Multiple_data_object class. It typecasts to each of the previously defined data-object classes, depending on which of several constructors is used. The advantage of using this class is that you don't need to know what the data type is when calling its get_new_value method. The Multiple_data_object get_new_value method uses the stored data type to index into a switch statement that calls the appropriate data-specific get_new_value. This is handy when manipulating lists of data objects of different types, which is exactly what the list dialog defined in the next section does.
List Dialog
The AWT List class is a control class that responds to user input for scrolling through and selecting items from an array of String objects. The List_box_dlg class (Listing 5, List_box_dlg.java) inserts a List control into a Standard_dlg dialog. To facilitate handling arrays of objects more general than strings, I define the Object_list class, shown in Listing 6, Object_list.java. In this article, I have need only for methods which add objects to the list (add_item) and return a specified object from the list (at). Space limitations preclude a more generally useful version which would also provide delete and insert functionality.
Object_list is the mechanism by which lists of objects are introduced into the dialog. List_box_dlg loads the String representation of each item in the Object_list into the List control. The List class has some useful features, including the automatic addition of vertical and (as of JDK 1.1.6) horizontal scroll bars as needed. The getSelectedIndex method retrieves highlighted items selected with a single mouse click. Double-click selections, however, require a new ActionListener.
The Data_list_dlg extends the functionality of List_box_dlg by manipulating an array of objects of the class Multiple_data_object. The get_item_string method is overridden to return the string obtained from get_dislay_str, which shows a description and the latest value of the data object. I want Data_list_dlg to respond to a double-click action by calling the selected data object's get_new_value method, which will display a dialog for updating the data object's value. For this, I need to define a new ActionListener which captures the List's double-click actions. After get_new_value has executed, replaceItem updates the display string in the list. The list dialog remains open until the user hits OK or Cancel so that other items in the list may be updated.
Application Frame Example
The example in this section shows how easy it is to use the data-list dialog as an application user interface. The application launches a Frame class window that includes a menu with two items, File and Edit. The File menu includes the Exit item, while the Edit menu has a single Setup... item that summons the list dialog. Figure 1 shows the frame-window application, with the list dialog and an example of the yes-no radio dialog which has been opened for updating one of the list items. The frame window itself displays the index of the most recently selected list item and its contents. Doing this in a platform-independent way actually constitutes more code than is required for setting up the list dialog.
Listing 7, App_Frame_with_dlg.java, shows the code for the App_Frame_with_dlg class that implements this example. The list dialog interface is set up by first defining a new Object_list object and then adding new Multiple_data_object objects to it. Note that the constructor call for Multiple_data_object determines the data type and its initial value. You can introduce additional items merely by adding more items to the list. The dialog layout does not change, and no new controls need be defined. The dispose method releases all of the resources used by the frame and also destroys all windows owned by the frame, such as the dialogs that we have been using. Finally, Listing 8, dlg_app.java, shows the small amount of code required to actually run App_Frame_with_dlg as an application.
Summary
This brief discussion has shown how to develop a few simple dialogs in Java and how to tie them together in a list dialog that provides a convenient device for user input of varied data types. Along the way, I have compared the Java development process with that of development in C/C++ for Windows. From a code point of view, for user interface development, Java and the AWT present an attractive alternative to C/C++ and the Windows API. As a bonus, you get multiple-platform portability. While early implementations of Java applications lack the sophistication of their Windows counterparts, improvements in Java continue to bring it closer to being a viable competitor to C/C++/Windows for GUI applications.
Steve Welstead is senior scientist with XonTech Incorporated in Huntsville, Alabama, and an adjunct associate professor of mathematics at the University of Alabama in Huntsville. He does all of his own C/C++ and Java programming for research investigations in image and signal processing. He has contributed three previous articles to C/C++ Users Journal. He has written one book that uses C/C++ code, and is currently completing Fractal and Wavelet Image Compression Techniques, to be published by SPIE Publications in the summer of 1999.