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 data source 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}")
Procedure COMBOBOX1_OnLeftMouseUp(flags,
col, row)
*--> enter a field into
our document
form.editor1.value = form.editor1.value
+ "<FN-" + trim(this.value) + ">"
form.editor1.setfocus()
form.editor1.keyboard("{Ctrl+PgDn}")
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
*--> by using the variable,
we can make decissions later in the routine
lPrn = chooseprinter()
*--> if printer was selected
- open a channel to the printer
if lPrn
set printer
on
endif
*--> start at the beginning
go top
*--> if this is a report
instead of a letter, we need to be able
*--> to format the report
so we open a second copy of the table
*--> to use in calculating
the spacing required for a columnar report
if form.radiobutton2.value
cFile2 = trim(form.entryfield1.value)
use &cFile2
again in select() alias copy2
endif
*--> redundant but secure
select 1
*--> loop through table
do while .not. eof()
*--> create
the beginning style based on the editor's default style
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 namesenddo
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