Volume 5 - Number 5 (September, 1990)

Creating a Multipage Data-Entry and Reporting Library for Windows[TM] Version 3.0

Kevin Welch

Many traditional character-based applications that are ported to the Microsoft WindowsÔ graphical environment require validated data entry and reporting more complex than what Windows provides. These data-handling tasks, which are commonplace in corporate computing, are time consuming and tedious to implement in Windows1, typically requiring a great deal of programming.

This article, the first of two parts, addresses the problem by presenting a tool that can be easily adapted to a variety of data-entry and reporting situations. It shows how to create a dynamic-link library, FORM.DLL, that supports multipage data entry and reporting using a series of linked dialog box and report templates specified in a resource file. Using this DLL, you can create complex multipage data-entry forms and reports without a single line of C programming.

Design

The initial design philosophy behind the library is to encapsulate a single, multipage data-entry form and report into one DLL. Then, any application desiring to use a specific form or report only needs to load a particular library and call the appropriate editing or reporting function. An additional advantage of this architecture is that applications could dynamically link to specific forms and reports based on user input.

The second design philosophy is to separate the supporting code from form and report definitions. This process is absolutely essential if new forms and reports are to be created quickly and easily. As a result of this design goal, all data-entry form and report specifications need to be extracted by the program from a data file associated with the library. For ease of implementation, this could best be accomplished by storing the form and report specifications in the resource file that is associated with the DLL.

The third design philosophy is to make using the library as simple as possible for the host application. Based on this goal, the library needs to contain only two entry points-one for data entry and one for generating formatted reports.

Now that you know the design philosophies behind the library, you can focus on the two entry points. The first entry point in the library, FormEdit, provides formatted data entry; it is defined as follows:

HANDLE FormEdit( hWndParent, hData );

The parameters and return values of this function are defined below.

hWndParent A handle to the parent window of the data-entry dialog box. This dialog box would be automatically centered inside the parent window using this handle.

hData A handle to a global block of memory, which contains formatted text that defines the data to be associated with each entry field. Each entry should be terminated with a single NULL character, and the last entry by an extra NULL character. The data block should be formatted in the following manner:

FIELDNAME:DATAVALUE <NULL>

o

o

o

FIELDNAME:DATAVALUE <NULL>

<NULL>

In cases where a new form is being entered, this data block should contain default values for all required fields. If no fields are required, then the handle provided should reference a data block consisting of a single NULL character. Not all fields need to be specified; the system will automatically assume that the unspecified fields are not defined.

The actual association of data values to specific data-entry fields in each form is defined by cross referencing the field name to a specific control ID. This cross referencing is defined by a table defined in the resource file associated with the library. There is more on this later in the article.

<return value> The value returned by this function should be a handle to the updated data block. If a NULL handle is returned, then it can be assumed that the user canceled data entry.

Calling FormEdit causes a dialog box (see Figure 1) to be displayed. This dialog box contains a data-entry area (inside which the actual data-entry form is displayed), a field comment area, push buttons that manage the data-entry process, and a page-selection area to use with multipage forms.

When this dialog box is initially displayed, the caption automatically indicates the data-entry form in use and the currently selected page. The data-entry area contains various fields and data values associated with the first page of the multipage form. When developing each form, the specification and arrangement of the various controls used to display data inside each page is performed using the dialog editor provided with the Windows Software Development Kit. Each dialog box you define should be of identical dimensions. It can contain any combination of Windows controls you desire. When you have specified each dialog box page (remembering that what you define will fit in the data entry area), you can integrate the template into the resource file associated with the library.

To assist the user in selecting a particular data-entry page, the currently selected page number is displayed in the lower-right corner of the main dialog box. Rather than developing a new control for this purpose, the installable Page control described in "Extending the WindowsÔ 3.0 Interface With Installable Custom Controls," MSJ (Vol. 5 No. 4) will be used. With this control, the currently selected data-entry page is indicated by a reverse-video page icon. If desired, the user can select a different data-entry page by clicking the mouse inside another page icon or by using the keyboard cursor keys to move the selector to a different page.

When the user enters data inside the form, the field comment area in the lower-left corner of the main dialog box contains a short description of the currently active data field. Each time the user presses the Tab key, the input focus moves from field to field and a new comment is displayed in the field comment area. If the user presses Tab from the last field in the form, the next page is automatically displayed. Similarly, pressing Shift-Tab in the first field of the form displays the previous page.

Each control in the form is subclassed to facilitate the use of the Page Down and Page Up keys for moving to the next and previous data entry forms. This assists the user in rapid data entry. Now that the data-entry functionality has been defined, the reporting portion of the library can be specified.

The second function in the library will create a formatted report using the data provided:

HANDLE FormPrint( hWndParent, hData );

The parameters and return values of this function are almost identical to that of the FormEdit function. The only difference is that if a NULL handle is returned, you can assume that the user canceled the report-generation process.

Calling this function generates a formatted report on the default output device using the data provided. The report will be formatted according to the report specifications defined in the resource file associated with the library. While the report is being printed, a dialog box is displayed. Besides indicating that a report is being generated, this dialog box allows the user to cancel the report.

Now that the entry points in the library have been defined, you can examine the mechanisms used to specify each form and report. As already mentioned, to facilitate the creation of new multipage data-entry forms and reports, the dialog box and report specifications are defined in the resource file associated with the library. Because of this, a new form or report can be created merely by changing the RC file and recompiling it with the library, using a different name for the new DLL.

The specification of the resource file associated with the library assumes the creation of an include file (FORM.H) that defines the identifiers used throughout the RC file. These identifiers are specified so that each field name in the form or report is assigned a unique number starting at 0100H, regardless of which data-entry page they appear in. The identifiers in the range 0000H to 00FFH are reserved by the library for internal use.

An example of the RC file required by FORM.DLL is shown in Figure 2. The library used this file to produce the three page data-entry form in Figure 1. (Assume all the IDn_FIELD values are defined in an associated FORM.H.)

The FORM RCDATA portion of the RC file is used to specify the number of pages in the data-entry form and the main title of the form. For example, if a value of 3 is specified for the number of pages, the library assumes that three dialog boxes of identical size will be defined using the names Page1, Page2, and Page3.

The REPORT RCDATA portion of the RC file is used to specify the report to be associated with the entire data-entry form. The specified data consists of a series of NULL-terminated text lines. Embedded inside each line can be C-like formatting statements in the following syntax:

%FMTSPEC = % [alignflag] [ [width] [.precision]]FIELDNAME

alignflag text alignment flags (in this implementation, limited to left and right justification)

width minimum number of characters to output

precision maximum number of characters to output

(Note: in this implementation, you are limited to string data).

Using these embedded formatting statements, the library will create the report using the field names specified when defining specific data values. Additionally, linefeed and carriage return characters will be automatically appended, and page breaks will be inserted as required.

To provide additional flexibility when creating reports, several predefined field names will be provided. In this implementation, it is useful to have the following field names:

LINENUMBER = current line number

PAGENUMBER = current page number

Finally, to provide greater reporting flexibility, it is useful to be able to specify page breaks and simple headers or footers. This will be accomplished using meta commands located in the first column:

%FORMFEED

%HEADER [header text including formatted data fields]

%FOOTER [footer text including formatted data fields]

The STRINGTABLE portion of the RC file is used to specify the relationship between each field name, the internal identifier, and the associated field comment text. Whenever a particular data entry form is displayed, the string associated with each internal dialog box identifier is retrieved and the corresponding field name is defined. The data block that is provided during the FormEdit call is then searched for a matching entry. If one is found, the associated data value is then displayed inside the related control (see Figure 3).

Additionally, when the user enters data into a form, a comment is always displayed at the bottom of the main dialog box that describes the field that has the input focus. This comment, defined in the string table, can be used to indicate whether the field is required, to indicate what data format is expected, or to elaborate on the exact meaning of the information being entered.

The remainder of the RC file contains dialog box definitions relating to each page of the multipage data entry form. Each definition is named Page1, Page2, and so on, up to the number of pages specified in the FORM RCDATA portion of the RC file. The caption associated with each dialog box is appended to the title of the main dialog box. This lets you define a title for the entire form and for each data-entry page.

When creating each data-entry dialog box page with the dialog editor, the only limitations are that each page have the same dimensions and that each control use an identifier that is defined in the FORM.H include file and referenced in the string table portion of the RC file.

Form Library

Now that the structure and contents of the form library have been defined, you can investigate the implementation. In this article you will implement the basic library with all associated entry points, but the actual mechanics will be left for the next installment.

Like most other DLLs, the form library is constructed from the following kinds of files (see Figure 4):

FORM make file

FORM.DEF module definition file

FORM.H include file

FORM.RC resource file

FORM0.ASM entry point module

FORM1.C initialization module

FORM2.C form edit module

FORM3.C form print module

The make file, FORM, is almost identical to that used by any Windows application except that it uses the -Aw and -D_WINDLL compile flags. These flags instruct the C compiler to generate code in a DLL format with SS != DS.

FORM.DEF is the module definition file. This file defines the module as a library, imports the page selection control initialization function, and exports the various form library entry points. Because the form library uses the page selection control, you need to have the page control DLL with its associated import library present.

The FORM.H file used for the first version of FORM.DLL does not contain field identifier definitions, but it does have prototypes for the two library entry points, FormEdit and FormPrint.

In this implementation, FORM.RC serves as a placeholder. In the next article the contents of this file will be expanded to contain the form, report, string table, and dialog box definitions.

FORM0.ASM is the assembly language entry point to the DLL. This module calls the FormInit function contained in FORM1.C, which initializes the library.

The FORM2.C module contains the FormEdit function. For now, this function displays a message box that indicates the form is being edited, and returns a handle to the formatted data block provided.

The last module, FORM3.C, contains the FormPrint function. Almost identical to the FormEdit function, it also displays a message box to indicate a report is being generated.

Form Viewer

In order for this form library to be tested, you need to construct an application that lets you load an arbitrary library and test it with manually entered data.

The form viewer is such an application (see Figure 5). With it you can manually load a form library, enter a block of formatted data, and send the formatted data to either the FormEdit or FormPrint functions. Figure 6 shows the relationship between the Viewer and a library being tested.

The form viewer is constructed from the following files (see Figure 7):

VIEWER make file

VIEWER.DEF module definition file

VIEWER.H include file

VIEWER.RC resource file

VIEWER.C initialization module

As with most other include files, VIEWER.H contains the definitions associated with the application. Of these definitions, note the specification of two property list values used by the Viewer window to store a child window handle and the handle to the active form library:

#define VIEWER_HWNDEDIT MAKEINTRESOURCE(100)

#define VIEWER_HFORMLIB MAKEINTRESOURCE(101)

Also note the definition of the ordinal numbers used to access the various form library entry points:

#define FORM_INIT MAKEINTRESOURCE(2)

#define FORM_EDIT MAKEINTRESOURCE(3)

#define FORM_PRINT MAKEINTRESOURCE(4)

Finally, note the form FormEdit and FormPrint function prototypes. Since these two functions are indirectly referenced by the Viewer application, the two prototypes are defined using function pointers:

typedef HANDLE (FAR PASCAL * LPEDITFN) ( HWND, HANDLE );

typedef HANDLE (FAR PASCAL * LPPRINTFN) ( HWND, HANDLE );

VIEWER.C contains the code associated with the application. Of this, three functions need mentioning. The WinMain function is responsible for the definition and registration of the Viewer window class. Note that the background brush is NULL. Because the ViewerWndFn creates a child edit window inside its client area, the window background does not need to be updated.

Once the Viewer window class has been registered, the WinMain function will create the Viewer window and retrieve/dispatch all associated messages. This continues until the Viewer window is destroyed.

The ViewerWndFn function is responsible for a number of tasks. When the window is initially created, it defines the window property list and creates a child edit window inside its client area. The resulting child window handle is saved using one of the property list values and is used when the window is resized or a formatted data block is retrieved.

When the user selects the Form Open... menu option, ViewerWndFn calls OpenDlgFn. This brings up a dialog box in which the user can specify which form library to use.

When the user selects the Form Edit... or Form Print... menu option, the ViewerWndFn is responsible for dynamically calling the appropriate form library function. The process of dynamically calling a library function is accomplished as follows:

hForm = GetProp(hWnd,

VIEWER_HFORMLIB);

if ( hForm )

{

lpEditFn =

GetProcAddress(hForm,

FORM_EDIT);

(*lpEditFn)(hWnd, hData);

}

Finally, when the viewer window is destroyed, the ViewerWndFn is responsible for releasing the current form library (if present) and removing all associated window properties.

The OpenDlgFn function centers the dialog box when displayed and loads the library specified by the user. If the load library operation is successful, the previous library (if present) is released and the viewer window caption is updated.

Once you have created all the Viewer files and compiled them, you can start using the Viewer with the Form library described in the previous section. Although you won't be able to start editing data or generating reports in this installment, you will at least be able to load the library. With the code that accompanies the next installment, the Viewer will be fully operable.

Conclusion

Now that the basic framework has been defined for the Form Library and the Viewer application is in place, you can start implementing the required internals. The next installment will focus on these internals, creating a complete library that you can easily adapt to your needs. u