Contents of the PRINTER.DOC file
DOCUMENTATION FOR TURBO PASCAL PRINTER UNITS
These units are for use in Turbo Pascal Windows Programs. They are shareware. You may use
these units in any non-commercial products freely. You may copy the programs and the documentation
and distribute them freely. If these units are to be incorporated into a commercial product, or if you wish
source on disk, please send $25.00 to OptiCom, Inc, Box 16-1127, Miami, FL 33116. Our phone
number is (305) 271-5138, and our CompuServe ID is 71250,71. If you register the product, I will send
you irregular updates of these objects as I improve them.
These objects, all source code and the contents of this document are copyright (c) 1991 OptiCom,
Inc. We would sincerely welcome any comments/criticism/ideas you might have about these objects.
The idea for these objects and the information in this document came from several sources. The
objects themselves are loosely based on the printer classes contained in the Actor Development
Environment, an excellent product of the Whitewater Group. Other material came from Charles Petzold's
Programming Windows, the Microsoft SDK, the Turbo Windows Manuals, Compuserve and several
friends and associates.
Files on this disk:
pDevice.pas Formal printer object, tPrnDevice.
printer.pas Printer Object, tPrinter.
repPrn.pas example of a report printer derived from the printer class.
testPrn1.pas example file printer program, using the tPrinter object.
testPrn2.pas example file printer program, using the reportPrinter object.
test.res resource file
prt.res resurce file
printer.doc This document
These objects are designed to provide an interface between a printer and an application running
under Windows. The structure of these objects is such that you should not have to modify them to add
functionality or change their behavior. You should create descendants of the objects to add additional
features. An example of this is the ReportPrinter object, in the file RepPrn.pas. The reportPrinter object
adds several features to the printer unit to handle headings and page size. Having learned the basics of
OOP in the Actor environment, I have come to appreciate the advantages of adhering as strictly as
possible to the principles of OOP. The Actor environment is pure OOP. That means that everything
(and I mean everything!) is an object. The main advantage of OOP programming is the ease of
modifying and extending the objects that you create. I hope these objects will prove to be good examples
of this type of programming.
These objects have been written with the OOP philosophy in mind. There is no access to the
instance variables of an object outside that object's scope. All access to instance variables is through the
methods of the object. Modifications to be made to the objects are made through descendants, not by
modifying the code of the object. This is illustrated by the ReportPrinter object.
This document is divided into several sections. The first discusses the relationship between
Windows and the printer. It covers, in a general manner, the calls to Windows contained in the objects.
I have tried to provide enough information to make clear what I am doing in each object, and so that the
objects can be extended to suit your own needs. The other sections discuss the objects themselves and
the sample programs. Each object section contains an explanation of the declared types in the unit, the
object's instance variables and methods, and a bit about what the object is supposed to do. The sections
on the sample programs discuss the nature of the programs, and how they use the objects to print.
PRINTING IN WINDOWS
It used to be that all you had to do to print something was USE the printer, and let fly. With
some well placed WriteLn statements and judicious spacing, you could produce reasonable output. Not
any more. Under Windows, printing requires almost as much programming as some small applications!
To a Windows application, sending output to a printer is not much different than sending output
to a window. The application actually sends its output to Windows, and Windows handles the mechanism
of getting it where it is supposed to go. In general, printing under Windows requires the following steps:
1. The application must retrieve information about the current printer from the WIN.INI file.
2. The application must create a device context (a handle) for the printer.
3. Output is sent to the device context.
4. The application communicates with the printer device driver using escape codes.
5. Windows activates the print spooler to manage the print job.
In addition to these basic steps, an application can also display a dialog box to allow a user to
cancel a print job that is being spooled. This requires the instantiation of an Abort procedure to notify
windows about the status of the print job.
Windows' management of each print job revolves around the concept of a document. You can
think of each, complete print job as a document. This applies no matter how large the output will be.
Each time a new print job is started, that print job is one document. Since it is possible that more that
one document may be printing at the same time, each individual print job must be kept separate from any
other print job to avoid mixing them up during output. The application must notify Windows when a
document is going to start. When printing has finished, the application must notify Windows that the
document has ended. This notification is done using escape calls.
In the following section, some of the concepts introduced above will be discussed in greater
ABORT PROCEDURE: An abort procedure is known as a callback function. It is a function in an
application that is called by Windows during a print job. The function returns a boolean value indicating
whether the user has canceled the print job. During normal processing, messages sent to the abort
function are translated and dispatched back to Windows. As long as the print job is not canceled, the
function returns a TRUE value to Windows. If the print job is canceled, the function returns FALSE,
letting Windows know the job was canceled.
In order to implement an abort procedure, it is necessary to provide a dialog box and an abort
function. The dialog box generally contains a brief message ('Sending job to spooler...') and a cancel
button. Pressing the cancel button should set a variable accessible to the abort function to FALSE. The
abort procedure must be declared as a EXPORTed function, and must return a boolean value. This is
done in the AbortProc method of the printer object. The abort function may not be a method of an
object, since object methods may not be exported. This also means that the print dialog may not be
contained in an instance variable of an object, since the Abort procedure would not have access to it.
Finally, the variable set by the Cancel method of the print dialog must be global to the unit in which these
two items are declared. Again, the process necessary for creating an abort mechanism may be seen in
the Printer.pas file.
The next step is to notify Windows of the existence of the abort function. This is done by
sending the SETABORTPROC escape code to Windows, with a far pointer to the abort function as a
parameter. This pointer is obtained through the MakeProcInstance method. MakeProcInstance creates
a far pointer that is bound to the data segment of the instance of the application that contains the abort
function. This is so that when Windows calls the abort function, it uses data in the current instance. The
application instance refers to the instance handle of the current application. A handle is a number that
uniquely identifies a program running under Windows. Since multiple instances of a program share code,
it is necessary for Windows to keep track of who is doing what. The call to MakeProcInstance returns
a tFarPointer which is then used in the SetAbortProc escape call. The SetAbortProc escape call passes
the pointer to the abort function to Windows.
If an application does not set an abort procedure, it is quite likely that any errors will cause the
program to crash. One common error is a lack of disk space for spooling. In the abort procedure, the
application can decide whether to wait for some disk space to be freed, or to cancel the print job. If an
print job terminates abnormally, or if it is canceled, the GDI automatically cancels the print job. In this
case, the application should not attempt to end the print job using escape calls.
Both the abort procedure and the print dialog box must be instantiated prior to issuing the
STARTDOC escape call to create the document.
DEVICE CONTEXT: When an application wants to use a device, it must obtain a handle for that device
from Windows. This handle is known as the device context. Through the device context, an application
has access to various devices attached to the computer. The device context is actually a data structure
maintained by the GDI, containing information about the particular device to which it belongs. For
example, when output is sent to the printer, the GDI uses the information contained in this data structure
to properly format the output. A device context works very much like a file in a Pascal program. Once
you have assigned a file name to a file variable, all access to the file passes through the file variable.
In a Windows program, all access to the device must 'pass through' the device context handle.
Device Mode: The device mode function is part of the printer device driver. These drivers are supplied
by the manufacturer and distributed with Windows. When you call DeviceMode, you are calling a
routine in a DLL. The deviceMode call is responsible for displaying the printer dialog box, allowing a
user to change the printer parameters. The name of the function in a driver written for Windows 3 is
ExtDeviceMode. In drivers for earlier versions of Windows, the function is DeviceMode. There are
several differences between these two functions. The most important one is that in the DeviceMode call,
any changes to the device parameters are reflected in the WIN.INI file. These changes will affect all
current and future sessions of Windows. In the ExtDeviceMode procedure, the programmer has the
option to make the changes permanent, or to maintain them for the current session only.
ESCAPE CALLS: Escape calls allow an application to communicate with a printer device driver. The
escape sequences contain commands and information for the driver. They can also be used to obtain
device-specific information. There are quite a number of escape codes that can be used (57, I believe).
These are listed in the Turbo Windows Reference guide under Printer Escape Codes.
GDI (Graphics Device Interface): The GDI is a programming language that acts as an interface between
an application, the printer device driver and the print manager. An application does not have to know
anything about the devices with which it will communicate. As long as the application uses the GDI to
access the devices, all of the output functions will be handled by Windows. This is known as device-
independence. As you will see, close communication is necessary between an application, Windows and
the printer driver to print a document.
WIN.INI file: The WIN.INI file plays an important part in the printing process. In the method
GetPrinterParms, this file is queried to obtain information about the current printer. Information in the
WIN.INI file is stored under application name and key name headings. Printer information is contained
under the application name '[windows]' and the key name 'device'. The key name is followed by the
currently selected printer, the name of it's device driver, and the port to which the printer is connected.
For example, a typical device keyword might look like this:
This tells us that the currently selected printer is an IBM Proprinter, the driver is
PROPRINT.DRV, and the printer is connected to LPT1. The settings in this file can be modified by the
DeviceMode call to the printer device driver. It is also possible, using the ClearEnv method, to force
a device to read the WIN.INI file to obtain the current printer parameters.
PRINTER OBJECT REFERENCE
tPrnDevice, Contained in file pDevice.Pas
hPrintDC Holds the printer device context. This variable is instantiated by the DCcreated method.
hWindow Contains the handle to the parent window from which the printer object was created.
This variable is instantiated by the Init method, using a value passed from the application.
docName The name of the document that is being printed. This variable is instantiated in the
BeginDoc method. The document name is used with the STARTDOC escape call to
uniquely identify the document being printed. This allows the print manager to manage
separate print jobs, without their interfering with one another. When the print manager
is active, this name appears on the status line for the selected printer.
Device The name of the printer. This is obtained through a call to getPrinterParms, which
retrieves the information from the WIN.INI file
Driver The name of the device driver associated with the device. This information is also
obtained through the getPrinterParms call
dMode a tDevMode data structure. Although it is not used extensively in these objects, this
structure holds a great deal of information about the printer and device driver. It is used
by DeviceCapabilities and by the getExtDeviceMode to hold information about a printer
noSpooler A boolean variable that is set in the GetPrinterParms method, based on the value for
spooler in the WIN.INI file. It indicates whether the spooler is active or not.
prnPort The port to which the object will print. This information is obtained from the WIN.INI
file through a call to getPrinterParms.
devArray An 80 character string used in the unit to hold various values.
prnErrors Enumerated type defining the different errors that can occur during the printing process.
These are used as parameters when calling the PrnError method.
pPrnDevice A pointer to the tPrnDevice Object.
tPrnDevice The formal printer object. You should not create an instance of this class. Instantiate
tGetDevMode This is a template for a windows function call to display the device driver dialog box for
drivers not written for windows 3.0.
tGetExtDevMode This is a template for a windows function call to display the device driver dialog
box for device drivers written for windows 3.0.
AbortPrn Aborts the current print job.
BeginDoc Sends the STARTDOC escape call to windows to indicate the start of a document. This
is an important call. Included in the Escape call is the name of the document to be
printed. This name is used by the GDI to identify a unique print job, and prevents
documents longer than one page from being mixed in with other documents. The job
name appears in the print manager list of jobs. The STARTDOC call should always be
paired with an ENDDOC or ABORTDOC escape call.
CallEscape Sends an escape code to the GDI, using passed parameters. The syntax of an Escape call
Escape(DC: hDC; Escape, Count: Integer; InData, OutData: Pointer): Integer
where DC is the selected device context, Escape is the escape code, count is the number
of bytes in InData, and outData is the data structure to receive data from the escape call,
if requested. The function returns an integer value that is positive if the escape call was
successful, zero if escape is not implemented, and negative if an error occurred. This
method centralizes the escape calls to allow errors resulting from the calls to be handled
from one location. Most errors occur on a NewFrame boundary, which signifies the end
of a page.
ClearEnv Clears the device's environment. This forces the printer driver to read the current printer
settings from the WIN.INI file.
DCcreated Creates a device context for the printer by calling the CreateDC method. CreateDC
returns a word value greater than 0 if the call was successful.
DeleteContext Deletes the device context created for the printer.
DeviceName Returns the value of the Device instance variable.
DocAbort Sends the ABORTDOC escape code. This code terminates the current job and erases
everything the application has written to the device since the last ENDDOC code. This
code is normally used to terminate print jobs that have not implemented an abort
procedure, or those that have not reached their first NewFrame escape call. If a print
job terminates due to cancellation or an error, this call should not be used. The GDI
automatically terminates print jobs under these circumstances. If the application has
created a cancellation dialog, the ABORTDOC code must be sent before the dialog box
is destroyed, and before freeing an abort procedure-instance.
Done Destructor method. Calls the parent (tObject) destructor.
DoNewFrame Sends the NEWFRAME escape code to Windows. This code lets the printer know that
the application has finished writing to a page. It causes a form feed to be issued. The
NEWFRAME call restores the default values of the device context. If a font other than
the default font has been selected, that font must be reselected after the NEWFRAME
DraftModeOff Turns the printer draft mode off by sending the DRAFTMODE escape code. This
method illustrates the use of the InData structure, sending a word value indicating the
desired state of the draft mode. The value is passed in the high portion of the word. A
value of 0 turns draft mode off, 01 turns it on. The draft mode of the printer can only
be changed at page boundaries, such as after a NEWFRAME escape call.
DraftModeOn Turns the printer draft mode on by sending the DRAFTMODE escape code. This
method illustrates the use of the InData structure, sending a word value indicating the
desired state of the draft mode. The value is passed in the high portion of the word. A
value of 0 turns draft mode off, 01 turns it on. Since the value is passed in the high
portion of the word, the value passed is 1000. In the method draftModeOff, the passed
value is zero. The draft mode of the printer can only be changed at page boundaries,
such as after a NEWFRAME escape call.
DriverName Returns the value of the Driver instance variable.
EndDocument Sends the ENDDOC Escape code to Windows. This signals that the current document
has completed. If the print job has terminated abnormally, this method should not be
called. In cases of abnormal termination, the GDI terminates the operation. If the
application displays a print cancel dialog, the ENDDOC code must be sent before the
dialog box is destroyed, and before freeing the procedure instance of an abort procedure.
The ENDDOC or ABORTDOC escape calls should always be paired with the
STARTDOC escape call.
EndOfFile Ends the document. Causes a newPage, sends the ESCAPEDOC escape call, and deletes
the printer device context.
FlushPrn Sends the FLUSHOUTPUT escape code, which flushes the printer's output buffer.
GetPrinterParms Retrieves the printer parameters from the WIN.INI file. The syntax for the call
to GetProfileString is:
GetProfileString(appName,keyName,default,RetunString: pChar; Size: Integer)
where appName is the heading name to search for in the WIN.INI file, keyName is the
key to search for under the heading, default is the default value if keyName is not found,
ReturnString is the buffer in which the value of KeyName will be stored, and Size is the
size of the buffer. The appName is usually 'windows', the keyName is usually 'device'.
On returning from the call, ReturnString contains the name of the printer, the
name of the printer driver and the port to which the printer is connected. These values
are separated by commas. The returned values are copied to instance variables, since
they are used by other methods in the object.
Finally, there is another call to GetProfileString, this time searching for the
keyName, 'spooler'. The value at this location is then used to set the boolean variable,
noSpooler to true or false. This variable indicates whether the print spooler is active.
Init Constructor method. Calls the parent (tObject) constructor.
InitAbortProc Sends the SETABORTPROC escape code to initialize the abort procedure. The address
for this procedure is passed in the procAddr variable. This illustrates the use of the
InData data structure to send information to Windows. See the discussion about abort
procedures, above, for more information.
okPrint Returns true if the device context is greater than zero.
PrinterPort Returns the value of the prnPort instance variable.
prnDeviceMode This method displays the printer device driver dialog box, allowing the user to
change the printer settings. This method has several variables:
dHandle Handle of the load library for the current printer.
drvName File name of the driver used to get dHandle.
pAddr address of the procedure in the device DLL that will be called.
A call to GetPrinterParms initializes the variables device and driver. Using the
value in driver, a filename for the device driver is created by concatenating the extension
'.drv' to the driver name. This is stored in the local variable, drvName. The call to
LoadLibrary loads the device driver into memory, and returns a tHandle value that
specifies the library module instance. If the value is less than 32, an error occurred.
Note that in this version of the printer object, I am not trapping errors resulting from this
call. The first call to GetProcAddress searches the DLL for a function named
ExtDeviceMode. If that function is located, a tFarProc value is returned, representing
the address of the function in the DLL. This function is present only in device drivers
written for windows 3.0. If an address is returned, the ExtDeviceMode function is
called, using the tGetExtDevMode function type as a template. The syntax for a call to
where hWnd is the parent window of the caller, hDriver is the device driver module,
lpDevModeOutput is a tDevMode data structure. The driver will write the initialization
information supplied in the lpDevModeInput parameter to this structure. lpDeviceName
is the name of the printer device, lpPort is the port to which the printer is connected,
lpDevModeInput is a tDevMode structure that supplies initialization information to the
printer driver, lpProfile contains the name of a file with initialization information. If this
parameter is nil, WIN.INI is used. wMode is a mask that determines what types of
operations the function will perform. If wMode is 0, the call to the function returns the
number of bytes required by the printer device driver structure. Otherwise, this parameter
must have one or more of the following values (multiple values should be OR'd
dm_Copy Writes the printer driver settings to the tDevMode record passed
dm_Modify Modifies the printer driver settings to the values passed in the
dm_Prompt Displays the device mode dialog box, and changes the printer
settings to what the user specified
dm_Update Updates the printer environment and the WIN.INI to reflect the
changes made by the user to the printer settings.
If you do not include dm_update in the call to DevMode, any changes made to
the printer settings will be for the current session only, and will not affect other,
If the value returned from the first call to GetProcAddress is nil, indicating that
the driver was not written for Windows 3, a second call is made to GetProcAddress to
get the address of the DeviceMode function. The DeviceMode function always updates
the WIN.INI file when it's parameters are changed. The syntax for a call to DeviceMode
The call to FreeLibrary releases the loaded library module, and any memory
associated with it.
PrnError Calls an error routine based on the value of msgNum. Each of the error routines has
been coded separately to allow them to be overridden in descendant objects. The error
routines are not listed here. Each one calls the messageBox routine, passing the address
of the appropriate error string and dialog title. These strings are declared as constants
(eMsg) at the start of the implementation section.
The tPrnDevice object is a formal object; that is, you should never create an instance of it. It's
purpose is to manage the lower level interface between Windows, an application, and the device driver.
The tPrinter object, discussed next should be instantiated instead. It provides the higher level interface
between the application and the tPrnDevice object.
THE TPRINTER OBJECT
HInst Contains the application instance value passed from the calling application. This variable
is used in the NewAbortProc method when the call to MakeProcInstance is made. See
the discussion concerning abort procedures at the beginning of this document.
LpAbortProc Receives the tFarProc value returned from the MakeProcInstance call in the
maxX Holds the maximum line length, in pixels. This variable is initialized in the Start
maxY Holds the maximum page length, in pixels. This variable is initialized in the Start
Metrics A tTextMetrics data structure. This structure contains information about the selected
font. It include the average dimensions of the characters, the number of characters in the
font and the character set on which the font is based. Text metrics are most often used
to determine the line spacing of the printed output. In this unit, the height of a line is
returned by the height method. The height is the sum of the tmHeight field, which is the
height of each character cell, and the tmExternalLeading fields, which is the
recommended spacing between the bottom of one character cell and the top of the next.
OkToPrint This variable is used as a flag to determine whether various steps in the initialization
process were completed successfully.
posX Holds the horizontal position of the print head on a line, in pixels.
posY Holds the vertical position of the print head on the page, in pixels.
TheParent A pWindowsObject that is initialized in the Init method. This variable holds the parent
window from which this routine was instantiated. It's value is used in the printDialog
method to instantiate the print cancel dialog.
tPrinter The printer object. This is the lowest-level object that should be instantiated to print
under Windows. It is a descendant of the tPrnDevice object, which handles the lower
level interface with Windows.
tPrnDialog A descendant of tDialog. This is the print cancel dialog that appears when a print job
is being spooled. Pressing the cancel button sets the unit variable UserAbort to true,
causing the print job to be canceled.
PrintDialog A pointer to a tPrnDialog. This variable is not an instance variable, since it must be
accessible from the abortProc function. Since the AbortProc method has no 'knowledge'
of the printer object, it would not be able to intercept a message from the dialog.
UserAbort A global boolean variable that is set when the user presses cancel in the print dialog box.
This variable is checked by the AbortProc function to see if the print job has been
aborted. See the discussion of the Abort Procedure near the beginning of this document.
AbortProc This function is a callback function, called by windows during a print job. If the user
presses cancel in the Print Dialog box, this function notifies Windows that the print job
had been canceled.
CheckNewPage This method compares the value of posY (the vertical position of the print head
on the page) with MaxY (the maximum page length). If posY > maxY, the newPage
method is called.
CheckStart This method is responsible for some of the initialization that takes place within the printer
object. It's calls deal mainly with the interface between the application and the printer
object. The initialization between the printer object and Windows is handled by the Start
method. CheckStart is responsible for instantiating the print dialog and the abort
procedure, and disabling mouse and keyboard input to the parent window.
If these initializations are performed successfully, CheckStart then calls the
beginDoc method, which sends the STARTDOC escape call to the GDI. If any of the
Init calls fail, or if the document is not started, checkStart reverses those initializations
that were successfully completed.
DoNewFrame Calls the ancestor.doNewFrame method to force a form feed.
Finish This method is called at the end of a print job. It calls the EndOfFile method and the
stopPrinter method, and frees the procedure instance created for the abort procedure.
Height Returns the line spacing for the selected font. This is the sum of the metrics.tmHeight
and metrics.tmExternalLeading fields. See the discussion under metrics, above, for more
Init The Init method calls the ancestor.Init method, and initializes several instance variables.
LineWidth This method returns the width (line length) of the passed string, based on the currently
selected font. This is done through a call to GetTextExtent, which returns a longInt
value containing the height of the string in the high word, and the width of the string in
the low word. The lineWidth method returns only the width of the string. This value
is used in the print method to check if the length of the string currently being printed
causes the current line length to exceed the width of the page. If so, the offending text
is wrapped to the next line.
NewAbortProc This method calls the makeProcInstance function to obtain a far pointer to an abort
procedure, bound to the data segment of the current application instance. This value is
then passed in the inData parameter of the SetAbortProc escape call. This function
returns true if the escape call returns a value greater than zero.
It is important to note that all this method does is pass a pointer to a function.
It does not guarantee that the pointer is valid, or that the function itself is valid. For
example, if the EXPORT statement is left off of the AbortProc declaration, the program
will still run, but the cancel dialog box will not work.
NewLine NewLine causes a line feed by setting the posX variable to 0, and incrementing the posY
variable by the value returned by the height method.
NewPage NewPage starts a new page by resetting the values of posX and posY, and calling the
PageSize This method returns a tPoint type containing the size of the page in pixels. These values
are obtained by two calls to the Windows GetDeviceCaps method. GetDeviceCaps
allows you to determine the extent of the printer device text writing abilities. There are
about 28 different queries that can be made, including the horizontal and vertical
resolution (used here), the aspect ratios for line drawing, the number of fonts, and so on.
The possible parameters for calls to this method are listed in the Window reference
guide, under Device capabilities.
Print This method calls the printString method to send a line of text to the printer. Before
making this call, Print determines whether the line length has been exceeded, based on
the current position of the print head and the width of the string to be printed. If the
length has been exceeded, Print forces a new line. Note that the entire string is wrapped
to the next line from the beginning. The string is then sent to the printer by a call to
the printString method. If the printString call is successful, the value of posX is
incremented by the width of the string. Under normal circumstances, print does not issue
a line feed. The Print method is similar to the Write procedure in standard pascal, in
that it does not advance to the next line.
PrintDlg Creates the print dialog box, a modeless dialog present while the print job is in progress.
The purpose of the dialog box is to allow the user to cancel a print job.
PrintLine PrintLine is similar to the WriteLn procedure in standard Pascal. It prints a line of text,
followed by a line feed. It does this by calling the print method, followed by the
PrintString PrintString sends a null-terminated string of text to the selected device context using the
TextOut method. The TextOut method writes the character string using the currently
selected font. It is called by the Print method.
RemoveDlg Destroys the print dialog box and disposes of the printDialog pointer.
ResetPos Sets the posX and posY values to 0, indicating the start of a new page.
Start The start method takes care of initializing the printer object and its relation with
Windows. It is responsible for obtaining a device context, initializing most of the
instance variables, and calling the CheckStart method to complete the initialization. If
checkStart is successful, Start returns true.
StopPrinter This method re-enables the parent window, removes the print dialog box, and sets the
OkToPrint variable to false.
TextHeight Returns the value of the metrics.tmHeight field, which is the height of the character cell
of the currently selected font.
TextWidth Returns the average width of the characters in the currently selected character set.
Using the tPrinter object
The tPrinter object is the object to use in an application to obtain basic access to the printer. The
sample program TestPrn1 illustrates a simple file printing program that uses this object. Tprinter handles
the interface between an application and the tPrnDevice object. That object handles the interface between
the tPrinter object and the GDI. By maintaining this separation, it becomes much easier to extend the
functionality of either the tPrnDevice or the tPrinter. If you find that the two objects meet your basic
printing needs, but you need more features than they offer, the correct way to add new features is to
create descendant objects. You should avoid, as much as possible, modification of these parent objects.
The TestPrn1 sample program
This program allows you to print a file or display the device mode dialog for a printer. The
application object is tTestApp, the main window object is tPrnWindow. The printer object is instantiated
using the tPrnWindow instance variable aPrinter. The two methods that interest us are the setPrinter
method and the filePrint method.
This method displays the device mode dialog. It consists of three lines:
aPrinter := new(pPrinter,Init(hInstance,@self));
The first line instantiates aPrinter as a printer object. The hInstance variable contains the current
tTestApp instance. The @Self parameter is the tPrnWindow object. The second line calls the
prnDeviceMode method of the printer object. The hWindow parameter contains the device context for
the current tPrnWind. The third disposes of the printer object. In the printer object, the device mode call
has been set to change the environment only for the current session.
THE FILEPRINT METHOD
The filePrint method first displays a dialog box to get the name of a file to print. If the file name
is invalid, the method does nothing. (After all, this is to demonstrate printing!). Once a valid file name
is received, we are ready to print. As in the SetPrinter method, an instance of the tPrinter object is
created by the line
aPrinter := new(pPrinter, Init(hInstance, @self)
here, the hInstance represents the current instance of tTestApp. @self represent the tPrnWindow
instantiated by tTestApp. The value of these two parameters will be used in the instantiation of the
AbortProc method and the print dialog box. The next line calls the Start method:
if aPrinter^.Start('PrnTest',hWindow) then ...
here, 'PrnTst' is the document name that will be used to uniquely identify this document, and
hWindow is the current device context (tPrnWindow). This function call must return true in order for
printing to commence. If it returns false, an error message should display, indicating what problem
Once the document has been started, the application can send continuous output to the printer.
This is represented by the WHILE..DO loop, following the call to the Start method. This loop reads in
the next line of text from the file, and calls the printLine method, passing a pointer to the string as a
When the end of textFile has been reached, the Finish method is called to end the print job. This
call results in the print buffer being flushed, and a NewFrame call to advance the page. Finally, the
aPrinter variable is disposed, and the job is done.
While the print job is in progress, you should notice several things. First, the parent window to
which the print dialog belongs has been disabled. Mouse and keyboard input to that window have been
inhibited. It is still possible to access other windows on the screen, but the only active portion of the
print process is the cancel dialog box. Another thing to notice is that the printing does not start
immediatly. In fact, for smaller files, printing won't start until you have returned to the application.
This is because Windows and the print manager spool the output to disk or memory, prior to starting
output. Finally, note that any changes you make to the printer parameters affect only the current print
session (if the driver was written for Windows 3).
THE REPORTPRINTER OBJECT
The reportPrinter object descends from the tPrinter object. It does not modify any of tPrinter's
methods. Rather, it adds some behaviors to tPrinter to change the physical appearance of a printed
document. ReportPrinter adds user defined pagination, based on lines per page, and headings that are
printed on the top of each page. In the full implementation of this object in another system, it also
printed column headings, and a columnar report with totals.
The point of the reportPrinter object is to illustrate how the behavior of an object is modified by
a descendant. It is intended as an example, not a full implementation of an application.
Heading1 Holds the first heading line.
Heading2 Holds the second heading line.
lineCount Tracks how many lines have been printed. This variable is used to determine when to
advance to a new page. Note that, in this implementation, if a line is wrapped by the
Print method, line count is not affected.
maxLines Maximum number of lines per page. This variable is set in the Init method.
pageCount Keeps track of the page number. This count is included in heading line 2.
rDateTime Holds the run date and time of the report. This value is included in heading line 2.
Init Calls the ancestor(tPrinter) Init method to set the application instance and parent values.
Init then initializes several other of the reportPrinter instance variables.
SetDefaults SetDefaults initializes the values of the two heading lines. Heading1 is set to the value
of the passed parameter, heading2 is set to the run date and time by a call to
SetHeading2 Initializes heading2 to the run date and time instance variable and the constant PAGE.
SetPageNum Strings the current page count into the local variable pCount, then copies it into the
proper position in heading2.
printMainHeadings Calls setPageNum, then sends heading1 and heading2 to the printer.
StartNewPage Page break. If the page is greater than zero, calls new page, then prints the headings.
NewPage causes a form feed. In this version of the object, pageCount is initially set to
zero. This routine does not issue a form feed for the first page. (I was running out of
OutText Check lineCount against maxLines to see if it is time for a new page. If so,
startNewPage is called. The text passed as a parameter is sent to the printer with the
tPrinter.PrintLine method, and pageNum in incremented by 1.
SetRunTime A method to get the current date and time, format them, and copy them to the rDateTime
SAMPLE PROGRAM TESTPRN2
This application is an example of the use of the reportPrinter object. The majority of the code
is identical to that in TestPrn1, so I will not repeat that discussion. One difference is that the
tPrnWindow instance variable aPrinter is now declared to be of type reportPrinter. The other differences
are in the instantiation of aPrinter in the body of the program, and the object methods that are called
In the method filePrint, the init call for aPrinter now includes an addition parameter, that
for the lines per page. This value sets the reportPrinter instance variable maxLines, which is checked
each time the outText method is called. Next, the reportPrinter method setDefaults is called to initialize
the headings. The default heading passes is simply the word heading. If a nil value is passes, an empty
string will be printer for heading1. The second heading line will be the run date and time, and the page
number. The start method is called to begin the document. As the file is read, text is sent to the
reportPrinter method outText, rather that the printLine method as was done in the previous example.
This is so reportPrinter can manage the lines per page. The balance of the execution is identical to the
WINDOWS PRINTING TIPS
Keep in mind that tPrinter handles text on a line basis; that is, an application sends ready to print
lines to the print or printLine methods. Remember that these are printer objects. Their only purpose is
to get information from the application to the printer. If the output must be formatted in a special
manner, it should be done before the print or printLine methods are called. That is the technique used
in the original implementation of reportPrinter. The reportPrinter object formatted the text in to columns,
formatted the subtotals and totals received from other objects, and passed a complete line of text to the
tPrinter object. The overriding reason for doing this is that you know for certain that if an error in
formatting text occurs, it occurs before the line gets to the printer unit. It makes report creation and
debugging infinitely easier. Remember also that object-oriented programming takes the black box concept
several steps farther. Any object descended from the printer object should concern itself only with
printing. Any tasks not required for formatting a line, or printing it out do not belong in a printer object.
I sincerely hope that these objects, examples and documentation prove useful to your quest for
the perfect Windows application. I have learned a great deal about Windows programming while creating
them. I look forward to corresponding with you on the Compuserve forum. Happy Computing!