Chapter 10 : Printing
Microsoft FoxPro Support Engineers
Microsoft Corporation
Created: December 15, 1993
Abstract
This section discusses printing in Microsoft FoxPro for Windows version 2.5x, and contains the following topics:
·	Introduction
·	Printing to a File
·	Changing the Printer Font
·	Programmatically Changing Window's Default Printer
·	Bypassing Window’s Printer Drivers
·	Troubleshooting Printing Problems
·	Additional Information
Introduction
To print a document or other data in FoxPro for Windows, you must have a printer properly connected to a serial or parallel port (for a stand-alone PC) or properly connected to the network. Printing through a switch-box is not supported by Windows. You must also have a Windows printer driver file (.DRV) installed and configured for your printer. FoxPro will use the default printer specified in Control Panel, Printers. 
Printing to a File
Several FoxPro commands support a TO FILE clause. With the following commands, FoxPro prints to the destination in character mode.
·	LIST TO FILE <destination>
·	DISPLAY TO FILE <destination>
·	DIR TO FILE <destination>
·	TYPE TO FILE <destination>
·	SELECT TO FILE <destination>
TIP:     You can print character-based output to your printer quickly by using the command LIST TO FILE LPT1.DOS. When Windows prints to a file named LPT1.DOS, MS-DOS interprets the request to open the file LPT1.DOS as a request to send data to the printer port, and it sends the data out through its own printing handler. Because Windows thinks that LPT1.DOS is an MS-DOS file, the Print Manager is not used.
It is also possible to print to a file by using the SET PRINTER TO command. The following command will send output to the specified filename in character mode
SET PRINTER TO C:\TEST.TXT 
If using the REPORT FORM or LABEL FORM command and directing output to file, it is recommended that you use the Generic / Text Only printer driver. This printer driver can be installed in Control Panel, Printers.
NOTE:     If using the Generic / Text Only printer driver with a REPORT FORM, make sure that there is no bold text. Bold text will be replicated several times in the output file because there are no carriage returns in the text lines. (This is how the Generic /Text Only printer driver simulates bold print.)
Changing the Printer Font 
For commands such as LIST STRUCTURE, it is possible to change the printer font from FoxPrint (the default) to another font by using the SET PRINTER FONT command,
SET PRINTER FONT <expC1> [,<expN1>]
where <expC1> is the font name, <expN1> is the font size in points (default is 10 points). Once you issue this command, any subsequent printed output to your printer will use that font definition (unless overridden by a FONT/STYLE clause that some commands offer). An example of this command is:
SET PRINTER FONT ‘Arial’,12
LIST STATUS TO PRINT
If no SET PRINTER FONT is in effect, FoxPro uses FoxPrint, 10 point.
Programmatically Changing Window's Default Printer
It is possible to change the Windows default printer programmatically within FoxPro. This involves making calls to the Windows API. (See sample code below) Two other, more simplistic approaches to change the printer would be:
·	Using the RUN /n CONTROL PRINTERS command
·	Using the PROMPT clause offered by some commands
Below is sample code to change the default printer programmatically. The following is a explanation of the three main procedures: LISTPRINT, GETPRINT, and PUTPRINT. 
·	LISTPRINT displays on the screen all the printers in the WIN.INI file, the number of printers, and the default printer.
·	The GETPRINT procedure is called by LISTPRINT and gets a list of available printers, the number of printers, and the default printer in the WIN.INI file. The parameters are as follows:
·	Printer list (Array)
·	Number of printers in WIN.INI file (Numeric)
·	Default printer in the WIN.INI file (Character)
·	The PUTPRINT procedure sets the default printer in the WIN.INI file. This procedure takes three or four parameters, as follows
·	Printer list (Array--use parameter 1 from GETPRINT.)
·	Number of printers (Numeric--use parameter 2 from GETPRINT.)
·	The new default print string (Put directly into the WIN.INI file.) or pass two numeric parameters (3 and 4) where:
·	Number of the printer in the printer array (Numeric.)
·	Port number (If a printer has more than one port [FILE:, LPT1:] the number will represent the number in the list order [FILE: = 1, LPT1: = 2]. If a third parameter is passed that is numeric, but no fourth parameter is passed, it will default to the first or only port.)
For example, after first executing the LISTPRINT procedure with the DO LISTPRINT command, the WIN.INI file's printer information is placed in the device array. The PUTPRINT procedure can then be used to change the default printer driver.
The following commands represent the two methods in which the PUTPRINT procedure can be called to change the default driver to Generic/Text Only, where there are a total of three printer drivers installed and the first driver listed is the Generic/Text Only driver.
DO PUTPRINT WITH device,3,"Generic / Text Only=TTY,LPT1:,15,45"
-or-
DO PUTPRINT WITH device,3,1
To verify that the correct driver has been set as the default, issue the following commands:
RELEASE device
DO LISTPRINT
Following is the code for the three main procedures, GETPRINT, LISTPRINT, and PUTPRINT, which make calls to three additional procedures, PUTPROSTRG, GETPROSTRG, and GETPORT.

*!*****************************************************************
*!
*!     Procedure: GETPRINT
*!
*!*****************************************************************

PROCEDURE getprint

* Query WIN.INI to get data on the installed and default printers.

PARAMETER DEVICE, NUMBER, dfltprnt

* Usage:
*    DIMENSION device[1]
*    numprinters = 0
*    defaultprnt = ""
*    DO GETPRINT WITH device, numprinters, defaultprnt
* The "device" array will be populated with the names and
* parameters for all installed print devices.
* The "device" array has this structure:
* Col 1: Printer name
* Col 2: Parameter for [device] section
* Col 3: Parameter for [PrinterPort] section (includes time-out
* parameters)
*
* The contents of "device" might look something like this after
* GETPRINT is called:
*   Col 1                      Col 2           Col 3
*   -------------------------  --------------  ----------------
*   Apple LaserWriter II NTX   pscript,LPT2:   pscript,LPT2:,15,90
*   Generic / Text Only=TTY    fred.prn        fred.prn,15,45
*   HP LaserJets(Level 5)      HPPCL5MS,LPT1:  HPPCL5MS,LPT1:,15,45
* "numprinters" in this case would be 3
* "defaultprnt" might be this:
*    HP LaserJets(Level 5),HPPCL5MS,LPT1:

   #DEFINE buflen 2048
   PRIVATE m.in_talk, m.dcount, m.retbuf, m.bytes, m.thisdevice

   IF PARAMETERS() < 3
      WAIT WINDOW "This procedure requires 3 parameters"
      RETURN
   ENDIF

   IF FILE(SYS(2004)+"FOXTOOLS.FLL")
      SET LIBRARY TO (SYS(2004)+"foxtools.fll") ADDITIVE
   ELSE
      WAIT WINDOW "GETPRINT requires the FoxTools library."
      RETURN
   ENDIF

   IF SET("TALK") = "ON"
      SET TALK OFF
      m.in_talk = "ON"
   ELSE
      m.in_talk = "OFF"
   ENDIF

   * Fill in the first column of the array with installed device
   * names.
   m.retbuf = REPLICATE(CHR(0),buflen)
   m.bytes = getprostrg("devices",0,CHR(0),@m.retbuf,buflen)
   m.dcount = 0
   m.retbuf = LEFT(m.retbuf,m.bytes)
   DO WHILE CHR(0) $ m.retbuf
      m.thisdevice = LEFT(m.retbuf,AT(CHR(0),m.retbuf)-1)
      IF LEFT(m.thisdevice,1) <> CHR(0)
         m.dcount = m.dcount + 1
         DIMENSION device[m.dcount,3]
         device[m.dcount,1] = m.thisdevice
      ENDIF
      m.retbuf = SUBSTR(m.retbuf,AT(CHR(0),m.retbuf)+1)
   ENDDO

   * Fill in the second and third columns of the device array with the
   * parameters of each installed device from the [devices] section
   * (column 2) and the [PrinterPorts] section (column 3).
   FOR m.j = 1 TO m.dcount
      retbuf = REPLICATE(CHR(0),256)
      m.bytes = ;
         getprostrg("devices",device[m.j,1],CHR(0),@m.retbuf,256)
      m.retbuf = LEFT(m.retbuf,m.bytes)
      device[m.j,2] = m.retbuf

      retbuf = REPLICATE(CHR(0),256)
      m.bytes = ;
         getprostrg("PrinterPorts",device[m.j,1],CHR(0),@m.retbuf,256)
      m.retbuf = LEFT(m.retbuf,m.bytes)
      device[m.j,3] = m.retbuf
   ENDFOR

   * Store the number of installed devices.
   m.number = m.dcount

   * Now get the default printer.
   retbuf = REPLICATE(CHR(0),256)
   m.bytes = getprostrg("windows","device",CHR(0),@m.retbuf,256)
   m.retbuf = LEFT(m.retbuf,m.bytes)
   m.dfltprnt = m.retbuf

   SET TALK &in_talk

   *!*****************************************************************
   *!
   *!     Procedure: LISTPRINT
   *!
   *!*****************************************************************
   PROCEDURE listprint
   PARAMETER DEVICE
   #DEFINE buflen 2048
   PRIVATE m.in_talk,m.i
   IF FILE(SYS(2004)+"FOXTOOLS.FLL")
      SET LIBRARY TO (SYS(2004)+"foxtools.fll") ADDITIVE
   ELSE
      WAIT WINDOW "LISTPRINT requires the FoxTools library."
      RETURN
   ENDIF
   IF SET("TALK") = "ON"
      SET TALK OFF
      m.in_talk = "ON"
   ELSE
      m.in_talk = "OFF"
   ENDIF

   IF PARAMETERS() = 0
      RELEASE DEVICE
      IF TYPE("device[1]") = "U"
         PUBLIC device[1]
      ENDIF
   ENDIF
   NUMBER = 0
   dflt = ""

   DO getprint WITH DEVICE, NUMBER, dflt
   CLEAR
   ? ALLTRIM(STR(m.number,3))+" installed printer devices in win.ini:"
   FOR m.i = 1 TO NUMBER
      ? "  "+device[m.i,1]+"="+device[m.i,3]
   ENDFOR
   ?
   ? "Default printer is "+dflt
   SET TALK &in_talk

   *!*****************************************************************
   *!
   *!     Procedure: PUTPRINT
   *!
   *!*****************************************************************
   PROCEDURE putprint
   PARAMETER DEVICE, NUMBER, dflt, portnum
   * Take the printer device information in the device array and
   * update WIN.INI with it.
   *
   * "Number" is the total number of printers listed in "device."
   * "dflt" is the string to write to the [windows] device= statement
   * to set the default printer, or if dflt is a number, the array row
   * to construct this statement from. Thus, to make the default
   * printer the third one listed in "device," either pass a 3 as dflt
   * or pass the actual string (something like HP LaserJets(Level
   * 5),HPPCL5MS,LPT1:). If the string is passed, it must exactly
   * match an entry in the [devices] section of the WIN.INI file.
   *
   * GETPRINT can be used to construct the device array.
   *
   IF FILE(SYS(2004)+"FOXTOOLS.FLL")
      SET LIBRARY TO (SYS(2004)+"foxtools.fll") ADDITIVE
   ELSE
      WAIT WINDOW "PUTPRINT requires the FoxTools library."
      RETURN
   ENDIF

   IF TYPE("dflt") = "N"
      IF PARAMETERS() < 4
         portnum = 1
      ENDIF
      m.strg = device[m.dflt,1]+","+getport(device[m.dflt,2],portnum)
   ELSE
      m.strg = m.dflt
   ENDIF

   * Set the default printer
   =putprostrg("windows","device",m.strg)

   * Delete all existing device and PrinterPort entries.
   =putprostrg("devices",0,CHR(0))
   =putprostrg("PrinterPort",0,CHR(0))

   FOR m.i = 1 TO NUMBER
      =putprostrg("devices",device[m.i,1],device[m.i,2])
      =putprostrg("PrinterPorts",device[m.i,1],device[m.i,3])
   ENDFOR

   *!*****************************************************************
   *!
   *!     Function: PUTPROSTRG
   *!
   *!*****************************************************************
   FUNCTION putprostrg
   PARAMETER SECTION, entry, string
   fn = regfn("WRITEPROFILESTRING","CCC","I")
   RETURN callfn(fn,SECTION,entry,string)

   *!*****************************************************************
   *!
   *!     Function: GETPROSTRG
   *!
   *!*****************************************************************
   FUNCTION getprostrg
   PARAMETER SECTION, entry, dflt, buffer, blen
   fn = regfn("GETPROFILESTRING","CCC@CI","I")
   RETURN callfn(fn,SECTION,entry,dflt,@buffer,blen)

   *!*****************************************************************
   *!
   *!     Function: GETPORT
   *!
   *!*****************************************************************
   FUNCTION getport
   PARAMETER m.pstrg, m.pnum
   * Get the first "port" from a printer string.

   * First get the printer driver name (e.g., pscript) and the comma.
   m.retstrg = SUBSTR(m.pstrg,1,AT(',',m.pstrg))

   * Now get the port designation (e.g., LPT1: or FILE:)

   * Check if the port passed is greater than the ports available.
   IF OCCURS(',',m.pstrg) >= m.pnum
      m.portstrg = SUBSTR(m.pstrg,AT(',',m.pstrg,m.pnum)+1)
   ELSE
      m.portstrg = SUBSTR(m.pstrg,AT(',',m.pstrg,1)+1)
   ENDIFg

   IF AT(',',m.portstrg) > 0g
      m.portstrg = LEFT(m.portstrg,AT(',',m.portstrg)-1)
   ENDIF

   RETURN m.retstrg + m.portstrg
Bypassing Window’s Printer Drivers
FoxPro for Windows cannot send control codes directly to a printer using a Windows printer driver. It is possible, however, to bypass the Windows printer drivers and send control codes directly to the printer with @ ... SAY commands. Two ways to accomplish this are:
·	Open the printer in "raw mode”
·	Write all output to a text file, then copy the text file to a printer port.
Writing FoxPro for Windows programs that send control codes directly to a printer is not recommended. Only use these techniques when adapting existing FoxPro for MS-DOS programs that send control codes to the printer.
NOTE:     The following examples send control codes to switch an Epson FX-80 dot-matrix printer's compressed mode on and off.
Printing in “Raw Mode”
Two ways to open the printer in raw mode are by using the triple question mark (???) command followed by a null string or using the SET PDSETUP TO <driver name> command.
Using ??? " "
1.	Issue SET DEVICE TO PRINT and SET PRINTER TO commands to turn off all open print commands.
2.	Issue a ??? " " command to open the printer in raw mode.
3.	Issue @ ... SAY commands to send control codes and text to the printer.
4.	After printing, issue a SET PRINTER TO command to flush the print buffer and return control to the Windows printer driver.
The following code demonstrates these steps:
SET DEVICE TO PRINT
SET PRINTER TO
??? " "
@ 1,0 SAY 'NORMAL TEXT'
@ 2,0 SAY CHR(15) 		&& Control code to turn compressed mode on
@ 2,0 SAY 'CONDENSED text?'
@ 3,0 SAY CHR(18) 		&& Control code to turn compressed mode off
@ 3,0 SAY 'NORMAL again'
SET DEVICE TO SCREEN
SET PRINTER TO
NOTE:     In some circumstances, the last @ ... SAY command may not print. If this problem occurs, add an additional @ ... SAY command that prints a blank line (so that the lost line is just a blank).
Using the SET PDSETUP TO <Driver Name> Command
1.	Issue a SET PRINTER TO command to clear the print buffer of all open print commands.
2.	Issue a SET PDSETUP TO <driver name> command to open the printer in raw mode. You MUST already have an existing FoxPro 2.0 or 2.5 for MS-DOS printer driver in the current resource file.
NOTE:     The important property of this command is that setting a FoxPro for MS-DOS printer driver opens the printer in raw mode. The printer driver itself is not used to control printing (that is done through control codes). In fact, any FoxPro for MS-DOS printer driver can be used.
3.	Issue a SET DEVICE TO PRINTER command to direct the @ ... SAY commands to the printer.
4.	Issue @ ... SAY commands to send control codes and text to the printer.
5.	After printing, issue a SET DEVICE TO SCREEN command to redirect the output to the screen.
6.	Issue a SET PRINTER TO command to flush the print buffer and return control to the Windows printer driver.
The following code demonstrates these steps:
SET PRINTER TO
SET PDSETUP TO "Epson"
SET DEVICE TO PRINT

@ 1, 0 SAY 'NORMAL text'
@ 2, 0 SAY CHR(15) && Control code to turn compressed mode on
@ 2, 0 SAY 'CONDENSED text'
@ 3, 0 SAY CHR(18) && Control code to turn compressed mode on
@ 3, 0 SAY 'normal again'

SET DEVICE TO SCREEN
SET PRINT TO
Write to Text File and Copy to Printer Port
The basic steps to write output to a file and then copy it to the printer port are:
1.	Issue a SET DEVICE TO FILE <file name> command to direct output to a file.
2.	Issue @ ... SAY commands to send control codes or text to the printer.
3.	Issue a SET DEVICE TO SCREEN command to redirect output to the screen.
4.	Issue a SET SAFETY OFF command so FoxPro for Windows does not confirm copying the output file to the printer port (which it believes to be a file).
5.	Issue a COPY FILE <file name> TO <printer port>.DOS command to copy the output file to the printer port.
6.	Issue a SET SAFETY ON command to turn safety on again.
The following code demonstrates these steps:
SET DEVICE TO FILE output.prn

@ 1,0 SAY 'NORMAL text'
@ 2,0 SAY CHR(15) && Control code to turn compressed mode on
@ 2,0 SAY 'CONDENSED text'
@ 3, 0 SAY CHR(18) && Control code to turn compressed mode off
@ 3, 0 SAY 'Back to normal text'

SET DEVICE TO SCREEN
SET SAFETY OFF
COPY FILE output.prn TO lpt1.dos
SET SAFETY ON
NOTE:     For information on printing with question mark (?) output, see the Reports and Report Writer section of this Resource Kit.
Troubleshooting Printing Problems
Many of the printing problems reported to Microsoft Product Support Services are actually hardware or configuration problems not related to FoxPro or Windows. This section provides tips for general troubleshooting for printing, then provides solutions to some specific problems related to printing in FoxPro.
If you have problems printing, begin with these steps to isolate the problem.
·	Make sure you can print from MS-DOS.
·	Check the printer’s cable connections.
·	Make sure your SET TEMP= statement in the AUTOEXEC.BAT file is pointing to a valid directory. Do not point SET TEMP to the root directory of the drive.
·	Make sure you are using the correct printer driver. This is set up in Control Panel, Printers.
·	If you print to a COM port, you might need to configure the COM port with the MS-DOS Mode command or with Control Panel settings. (See section below on Printer Time-Outs)
·	Check the communication settings for serial printers.
·	Make sure you can print from Write in the Accessories Group.
·	Select a different printer driver (ex: Generic/Text Only) in Control Panel, Printers and/or reinstall the printer driver from your original diskettes.
·	Select a different video driver (ex: VGA), from the Windows Setup icon in the Main group or reinstall these drivers from the original diskettes.
·	Check the font you are using in the Control Panel, Fonts icon to see if it has become corrupted. You can do this by double clicking on the font name in the Installed Fonts box. If the font does not display correctly in the sample box, reinstall the font files from the original diskettes
Printer Time-Outs
If you receive a “Printer not ready. Retry? (Y/N)” message, the time-out for the printer may need to be increased. A time-out is the amount of time that any program waits for the print device to receive a character. 
FoxPro for Windows has an item called TIME that can be adjusted to increase the amount of time FoxPro has to send characters to a print device. The default for TIME is 6,000.
To change the TIME item:
·	In some text editor, open the CONFIG.FPW in the main FoxPro for Windows directory.
·	Add in a line that reads TIME=999999.
·	Save the file and exit the text editor.
·	Restart FoxPro for Windows.
Many times, the print device needs to be correctly configured for the operating environment of the PC. To do this, a MODE command may be used. MODE is used in the AUTOEXEC.BAT file of your computer.
For a parallel printer, you might use a similar command in the AUTOEXEC.BAT:
mode lpt1:,,p
This command lets the operating system know that a print device is on LPT1. Also, the operating system should perpetually retry to send output to the print device.
For a serial printer, you might use a similar command in the AUTOEXEC.BAT:
mode com1:9600,N,8,1,p
This command specifies 9600 as the baud rate, no parity, 8 data bits, 1 stop bit, and perpetual retry for sending output to the print device.
See your MS-DOS User’s Guide and Reference for more information on configuring print devices and the MODE command.
Cannot Print to File with PROMPT Clause
The dialog box displayed when using the clause [PROMPT] offers a check box for printing to a file. FoxPro for Windows ignores this check box and will proceed to send the print job to the printer. To avoid this, use the TO FILE <filename> clause when you need to direct output to a file.
Additional Information
Additional information concerning printing can be found by looking at the following articles:

Q101056 | Q99621 | Q101154
Q99094 | Q99542 | Q99556
Q99595 | |