Both of these capabilities are too often ignored or forgotten, perhaps because they are so misunderstood. The following example combines these two capabilities to provide a very simple example of a combination mail merge / report generator. Included is the source code for the form itself along with a 3 record demo data table.
In my case, I have a few experienced users who like this form and use it in its present condition. One of those long-term projects pending is to flesh it out, hide the formatting codes, and combine it with another homespun query builder that creates a temp table for use by this print form.
The segments of code that bear closer examination are included here with expanded remarks and explanations.
After a data table is selected,
the datasource for
a combobox is set to structure.
This gives us a list of all the fields in the selected table for inclusion
into our printout.
Procedure PUSHBUTTON1_OnClick *--> get the table we are going to use cFile = getfile("*.dbf") form.entryfield1.value = cFile *--> set the form's VIEW property to the table selected form.view = "&cFile" *--> now we can turn on the other controls |
|
Note the setting of the combobox’s
dataSource AFTER the table
is opened/changed. This automatically fills the combobox’s data
source with a fresh array of field names.
form.combobox1.datasource = "structure" form.combobox1.enabled = .t. |
|
The next two procedures simply
get a string as the result of selecting a new font or picking a field name
in the combobox.
Procedure PUSHBUTTON4_OnClick *--> get the font we are going to use next cStyle = getfont() *--> place it in the document form.editor1.value = form.editor1.value + "<FT-" + trim(cStyle) + ">" form.editor1.setfocus() form.editor1.keyboard("{ctrl+PgDn}") return Procedure COMBOBOX1_OnLeftMouseUp(flags, col,
row)
|
|
And the work is actually done in
the onClick() routine
of the Print button.
Procedure PUSHBUTTON7_OnClick *--> make sure we have a file to use if isblank(form.entryfield2.value) msgbox("Enter a text file name","ERROR",0+16) return else *--> save the document cFile1 = trim(form.entryfield2.value) |
|
The first of the low level functions
we use does two things. It gives us a “Handle” for the file we are going
to use, and it creates a file — overwriting any existing file by the same
name.
nhandle = fcreate("&cFile1") |
|
So we now have a handle (nHandle)
on a file that we can use in the future to reference this particular file.
And we write the contents of the form’s editor to that file.
fwrite(nHandle,form.editor1.value) |
|
And then we close the file (which
destroys the reference contained in our variable
nHandle)
fclose(nHandle) endif *--> get logical for printing
|
|
Streaming output can be formatted
with any available font, or font characteristic — this data is placed as
the parameter for the STYLE clause
of the ? Command.
cStyle = form.editor1.FontName + "," + ltrim(rtrim(str(form.editor1.FontSize))) *--> open the text file and set the pointer at the beginning nHandle = fopen("&cFile1") *--> read the file line by line do while .not. feof(nHandle) |
|
To read a complete line from the
text file you can use fGets or
fRead — the main difference
is that while fRead will
read the complete line (or any specified portion of it), it does not return
the end-of-line marker while fGets does.
*--> fGets includes the end-of-line marker *--> so we get the hard returns cStr = fGets(nHandle) *--> we are going to print a new line so *--> we start with a single print statement |
|
Streaming output has changed a
bit from the old DOS days — ? and
?? are still there, but
??? is problematic and
pretty much useless. You can set up a generic print driver in Windows,
but the codes sent by the old ??? command
are quite often rejected/ignored/garbled when passed through the Windows
Print Manager.
*--> we are going to print a new line so *--> we start with a single print statement ? |
|
The next code segment is a simple
parsing of the string looking for the delimiters used to identify the font
or field name included in the file, and printing the segment(s) of string
left over.
*--> parse the string for codes/field names if "<" $ cStr do while "<" $ cStr && might be more than one code or field *--> we get the location of the first set of delimiters nStart = at("<",cStr) nFinish = at(">",cStr) *--> and then get the code itself cCode = substr(cStr,nStart,(nFinish-nStart)+ 1) *--> strip off the delimiters cCode = substr(cCode,2,len(cCode) -2) *--> print the portion that came before the code cPrint = left(cStr,nStart -1) ?? cPrint style "&cStyle" *--> determine if the code is for a font change or a field name *--> The plan is to add some more functionality in the form of *--> report headers and column spacing so we are using two "if" *--> statements at this time if left(cCode,3) = "FT-" && a font name *--> change the style variable cStyle = right(cCode,len(cCode) - 3) endif if left(cCode,3) = "FN" && a field name cCode = right(cCode,len(cCode) - 3) *--> using macro substitution we can place the fields content *--> into a variable com = "cTest = " + cCode &com *--> test the value for type cVar = type(cCode) *--> process the value depending on type *--> at this time we are keeping the list limited to Character, *--> Numeric, date and logical. do case case cVar = "C" *--> character field if form.radiobutton2.value && are we printing a report *--> use the copy of the table select 2 go top *--> determine how wide the maximum value is calculate max(len(trim(&cCode))) to nSize select 1 *--> back to the working copy of the table cPrint = left(cTest + space(nSize + 3),nSize + 3) else cPrint = trim(cTest) endif case (cVar = "N") .or. (cVar = "F") *--> numeric/float value if form.radiobutton2.value && are we doing a report *--> size the field and pad it cPrint = str(cTest,10,2," ") else *--> trim the value for readability cPrint = ltrim(rtrim(str(cTest,10,2))) endif case cVar = "D" *--> date value *--> you could use one of the functions in the dUFLP *--> to format the date instead of the plain vanilla *--> dTOC function used here. cPrint = dtoc(cTest) case cVar = "L" *--> a boolean field that defaults to FALSE for a NULL value cPrint = iif(cTest,"True","False") otherwise *--> if it doesn't fit in the above choices, don't try *--> and print it cPrint = "" endcase *--> print the field's value ?? cPrint style "&cStyle" endif *--> cut off the used code and check the string again *--> for another code cStr= right(cStr,len(cStr)-nFinish) enddo *--> print whatever is left of the string ?? cStr style "&cStyle" else *--> no codes in the string, so just print it ?? cStr style "&cStyle" endif *--> go back and get another string to process enddo && while .not. Feof() *--> close the text file fClose(nHandle) *--> if a letter instead of a report, eject the page *--> but only if the printer is on if form.radiobutton1.value .and. lPrn eject endif *--> get the next record in the data table and *--> do it all over again skip enddo |
|