Creating a Data-Entry and Reporting Library for Windows[TM] 3.0: Part II

Kevin P. Welch

The first article in this two-part series framed a multipage data-entry and reporting library for the Microsoft WindowsÔ graphical environment. This installment explores the actual form library, creates a sample form, and demonstrates how it can be used with the form viewer.

The form library was written so that multipage data-entry forms and reports could be created without C programming. A single multipage data-entry form and report was encapsulated into one dynamic-link library (DLL). Then any application using this form simply loads the library and calls the appropri--ate editing or reporting API function. Since the actual specification of the data-entry form and report is completely contained in the resource file (RC) associated with the library, no programming is required when you create a new data-entry form or report.

A form viewer application is also presented. This application enables you to load a form library and experiment with data-entry or reporting functions.

Sample Library

To build the combined form viewer and data-entry library, you need to compile the list of files in Figure 1. Then you can experiment. When you run the form viewer application (VIEWER.EXE) using either the Program Manager or the MS-DOS Executive, the window in Figure 2 is displayed.

The client area of the form viewer window is initially empty. Before you can start entering data you need to load a form library (in this case FORM.DLL). To do this, select the Form Open menu option, and the dialog box shown in Figure 3 is displayed. In this dialog box you enter the name of the form library you wish to use. When you press the OK button, the library is automatically loaded, the caption of the viewer window is updated to indicate the currently active library, and the input focus is set to the client area of the viewer window.

The client area is actually a multiline edit control into which you can type information to be used by the form library. As explained in the first installment, each line in the data block should be in the following format:

<FIELDNAME> : <DATAVALUE>

The form library example presented in this article defines a product order data-entry form (something that many readers might need). This library supports a three-page data-entry form and simple report that contains the field names shown in Figure 4.

Based on this list, you could enter information into the form viewer like this:

NAME: Audrey Hepburn

COMPANY: Tiffany's

PRODUCT: Diamonds

When you have entered a few field names and data values, the client area of the form viewer should look something like that shown in Figure 5.

Now that you have entered some initial information, bring up the first page of the data-entry form by selecting Edit under the Form menu. Page1 of the product order form is then created with the information you entered displayed in the appropriate fields. The cursor is automatically positioned inside the first data entry field and the field is described in the lower-left corner of the dialog box.

You can move from field to field by pressing Tab (note the change in the field description) while entering data. You can select a different page by pressing the Page Down key, or by clicking the mouse inside one of the page selection icons located in the lower-right corner of the dialog box (see Figure 6).

When you are finished editing, save the information by pressing the Save button. This creates an updated data block; the resulting changes will be displayed inside the form viewer client area.

Similarly, you can produce a report based on the information inside the form viewer by selecting Print under the Form menu. A print dialog box is displayed while a report is sent to the default output device. To abort the printing process, press the Cancel button inside the print dialog box (see Figure 7).

Form Library

Now that you understand the operation of the form viewer and a sample data-entry library, you can take a more detailed look at the way a form library operates.

As described in the previous article, the specification of the form and report is defined in the RC file associated with the library using two entry points; FormEdit is used to edit data, and FormPrint is used to generate reports.

HANDLE FormEdit( hWndParent, hData );

HANDLE FormPrint( hWndParent, hData );

The code that supports these functions is divided into five modules. The first, FORM0.ASM, defines the assembly-language entry point to the library. FORM1.C contains functions that are called when the library is loaded (see Figure 8). FORM2.C contains the FormEdit function and most of the code to display and manage a data-entry form. The next module, FORM3.C, contains the FormPrint function and all the associated code to create a report. The last module, FORM4.C, defines utility and support functions that are used throughout the library.

Although these modules contain a great deal of source code, they can be easily understood by tracing the flow of operations when the library is first loaded, when a form is edited, and finally when the data is printed. Figure 9 shows the hierarchy of functions in the forms library; Figure 10 lists all the functions on a file-by-file basis.

Initialization

The first time the library is loaded, the LibEntry function defined in FORM0.ASM is called. LibEntry calls the FormInit function (defined in FORM1.C) with the current library instance handle as the only parameter.

If this is the first instance of the library (determined by the current and previous library instance handles), the FormInit function will initialize the global FormInfo data structure. This data structure contains the form name, the number of data-entry pages, the library instance handle, and other global data. Some of this information is extracted from the FORM RCDATA section of the resource file associated with the library. This is done by using a sequence of FindResource, LoadResource, and LockResource function calls. As is the case throughout the rest of the library, whenever an error situation is encountered, a warning message is displayed listing the module and line number where the error occurred.

Data Entry

The most complex operation supported by the form library is data entry. When the FormEdit function in FORM2.C is called, it extracts the dialog box template named Page1 from the resource file. From this template the dimensions of the main data-entry dialog box are calculated. Then the dimensions are passed to the system as part of a new, dynamically defined dialog box template via a DialogBoxIndirect call. Before DialogBoxIndirect is called, a system keyboard hook function is installed. This hook function intercepts the Page Down and Page Up keystrokes that move the user to the next or previous data entry pages.

All of the messages relating to the dialog box

that are created during the DialogBoxIndirect call are handled by FormEditDlgFn. This function simply dispatches those messages of interest to a series of utility functions. A WM_INITDIALOG message is received by FormEditDlgFn when the dialog box is first created, resulting in a call to the EditInit function. EditInit retrieves the dimensions of the data-entry dialog box and creates the supporting controls that surround the data-entry form pages defined in the resource file. The actual size and location of these child windows is calculated using the base dialog units value, which is found using the GetDialogBaseUnits function.

Once the supporting controls have been dynamically created, the EditInit function extracts each dialog box and control template by successive calls to the GetDlg and GetCtl functions. In the process, each control is created (based on the extracted template), immediately hidden, its window handle appended to the dialog box property list, and the initial data value defined.

The initial data value is defined in the following manner. First the field name associated with the control is retrieved from the string table via the GetCtlName function and the control ID. If the control or field name is successfully retrieved, the data associated with the field extracted (by the GetCltData function) from the global formatted data block is passed to the FormEdit function. If data exists, it is passed to the SetCtlData function, which performs the actions necessary to initialize the control. Although in this implementation the SetCtlData function supports only Button and Edit control classes, it could be easily extended to support other classes.

All dialog box controls are hidden while the data entry fields are created and defined. Only when the entire process is completed does the EditInit function display the contents of the first page. The display is accomplished by calling the EditDisplay function, which hides all controls associated with the previous data entry page (initially, none), and displays all controls associated with the new data entry page.

The next action performed by the FormEditDlgFn occurs when the WM_CTLCOLOR and WM_USER messages are received. After the messages are received, the EditControl function is called. This function defines the appropriate control color (ignored in this implementation) and updates the comment field associated with each control by using the GetCtlComment function.

Next, FormEditDlgFn calls the EditCommand function whenever a WM_COMMAND message is received. This function either saves the dialog box contents (using the GetDlgData function), prints the current form (using the GetDlgData and FormPrint functions), cancels data entry, or displays a new dialog box page.

The GetDlgData function (defined in FORM4.C) requires further explanation because it processes each dialog box item, retrieving the actual data associated with each control by calling the GetDlgCtlData function. Like the SetCltData function, in this implementation the GetDlgCtlData function supports only the Button and Edit control classes. Apart from this, the most complex thing the GetDlgCtlData function does is the dynamic reallocation of the resulting global data block into 32-byte chunks as data is retrieved from each control.

When the WM_DESTROY message is received, FormEditDlgFn calls the EditDestroy function, which cleans up the dialog box property lists and releases all associated global data blocks.

Report Generation

Report generation from within a data-entry form or from an application is performed when the FormPrint function (contained in FORM3.C) is called. This function is interesting because it generates the desired report while checking to see if the user wishes to cancel the entire process.

Prior to printing, the FormPrint function extracts the report template from the library resource file and dynamically creates the report status dialog box. As long as this status dialog box remains visible, the FormPrint function assumes that the user wishes to continue printing. If the user presses the Cancel button, the status dialog box is destroyed. Periodically during the printing process the FormPrint function checks to see if the status dialog box is still visible. If it has been destroyed, the printing process is terminated.

The actual output of each report line is accomplished by successive calls to the PrintOutput function. Using the GetCtlData function, this function parses the output specification string to extract the desired field data values from the global data block provided. If a requested field is undefined, a NULL value is automatically substituted.

Conclusion

Using a text editor and the Dialog Editor in the Microsoft Windows Software Development Kit, you can use the basic library structure presented here to alter FORM.H, FORM.RC, and create new forms and reports quickly and easily. Enhancements you might consider include the support of extended data types such as dates and numbers; additional controls such as combo boxes, list boxes, and scroll bars; enhanced reporting and data formatting; and on-line help within each form library using the Windows help system. The most ambitious upgrade would probably be to separate the resource portion of the library from the actual code by parsing a RES file. Whatever your refinements, a little customization should make FORM.DLL an important part of your Windows toolbox.

Form Viewer (described in MSJ, Vol. 5, No. 5)

VIEWER Make file

VIEWER.DEF Module definition file

VIEWER.H Header file

VIEWER.RC Resource file

VIEWER.ICO Icon

VIEWER.C Source code Page Selection Control (described in MSJ, Vol. 5, No. 4) CONTROL.H Installable control header file PAGE Make file

PAGE.DEF Module definition file

PAGE.H Public header file

PAGE.D Private header file

PAGE.RC Resource file

PAGE0.ASM Entry point module

PAGE1.C Intialization module

PAGE2.C Message processing module

PAGE3.C Style dialog box module

PAGE4.C Information module

PAGE5.C Style flags module Form Library FORM Make file

FORM.DEF Module definition file

FORM.H Public header file*

FORM.D Private header file

FORM.RC Resource file*

FORM0.ASM Entry point module

FORM1.C Initialization module

FORM2.C Form editing module

FORM3.C Form printing module

FORM4.C Misc. utility functions *Only these files need changing when creating a new form library. You will also need the following software development tools: Microsoft C 5.1 or 6.0

Microsoft Macro Assembler

Microsoft Windows Software Development Kit 3.0

You can then create the form viewer and data-entry library by entering the following commands: MAKE VIEWER

MAKE PAGE

MAKE FORM The result will be the following executables: VIEWER.EXE Form viewer application

FORM.DLL Sample form library

PAGE.DLL Page selection control (must be in your path)

Figure 4. Form Library Field Names

NAME Customer name {first,initial,last}

COMPANY Company name {optional}

ADDRESS Company or customer street address

CITY Company or customer city

STATE Company or customer state

ZIPCODE Company or customer postal code PRODUCT Name and version of product

SHIPVIA Product shipment method

PAYMETHOD Product payment method

CARDNUM Credit card number {if used}

EXPDATE Credit card expiration date {if used} OPTION1 5.25 inch diskette option {TRUE or FALSE}

OPTION2 3.5 inch diskette option {TRUE or FALSE}

FRIEND Customer heard about product from a friend {TRUE or FALSE}

AD Customer heard about product from an ad {TRUE or FALSE}

SHOW Customer heard about product at a tradeshow {TRUE or FALSE}

DEALER Customer heard about product from a dealer {TRUE or FALSE}

REVIEW Customer heard about product in a review {TRUE or FALSE}

ARTICLE Customer heard about product in an article {TRUE or FALSE}

COMMENT Misc. comments regarding customer order

Figure 10. Catalog of Form Library Functions

FORM0.ASM: Entry point module

LibEntry

FORM1.C: Initialization module FormInit( hInstance ) : HANDLE WEP( bExit ) : VOID FORM2.C: Form editing module FormEdit( hWndParent, hData ) : HANDLE FormEditDlgFn( hDlg, wMessage, wParam, lParam ) : BOOL EditInit( hDlg, wParam, lParam ) : BOOL EditControl( hDlg, wParam, lParam ) : BOOL EditCommand( hDlg, wParam, lParam ) : BOOL EditDisplay( hDlg, wNewPage ) : VOID EditDestroy( hDlg, wParam, lParam ) : BOOL FORM3.C: Form printing module FormPrint( hWndParent, hData ) : HANDLE FormPrintDlgFn( hDlg, wMessage, wParam, lParam ) : BOOL PrintInit( hDlg, wParam, lParam ) : BOOL PrintCommand( hDlg, wParam, lParam ) : BOOL PrintOutput( hPrnDC, hData, lpszPattern, wPage, wLine, wPixels ): BOOL PrintDestroy( hDlg, wParam, lParam ) : BOOL FORM4.C: Misc. utility functions GetDlg( lpResData, lpDlgBox ) : BOOL GetDlgData( hDlg ) : HANDLE GetDlgCtlData( hDlg, hWndCtl, lpszData, wMaxData ) : BOOL GetCtl( lpDlgData, wControl, lpDlgCtl ) : BOOL GetCtlName( hInstance, wCtlID, lpszCtlName, wMaxCtlName ) : BOOL GetCtlData( hData, lpszCtlName, lpszData, wMaxData ) : BOOL GetCtlComment( hInstance, wCtlID, lpszComment, wMaxComment ) : BOOL SetCtlData( hDlg, hWndCtl, lpDlgCtl, lpszData ) : BOOL StringMatch( lpszString, lpszPattern ) : BOOL StringCat( lpszDest, lpszSrce, wMaxDest ) : VOID StringCopy( lpszDest, lpszSrce, wMaxDest ) : VOID StringJoin( lpszDest, lpszSrce, wMaxDest ) : VOID GetPrinterDC() : HDC CenterPopup( hWnd, hWndParent ) : VOID FormMsgHookFn( wContext, wCode, lData ) : WORD Warning( hWndParent, lpszMessage, lpszFile, wLine ) : VOID