Printing Lines, Boxes and Images
with the Printer/Preview Class
by Vic McClung
IN THIS ARTICLE, I will demostrate how to draw various objects and images onto your report using the printer/preview class.  Here, once again is the basic code for printing using this class.  Just cut and paste this code and you are ready to start printing.  Down in the middle of this code you will see two lines that start with asterisks (*) and are followed with a line of equal signs (=). All of the examples I will use must be plugged into this location in the code to work properly.  This will prevent having to show the following code repeatedly.  Now skip on down below this code.
* ------------- copy and paste this code ---------------------------

* first you must load the printer.prg
set procedure to printer.prg additive && don't forget the 'additive'
* create a memory variable to hold the object reference
* local variable will work most of the time
local p && reference to the printer object
* use the new operator to create the object from the class
* p = new printer(.t.)  && the parameter is for the printer selection dialog
p = new preview(.t., 1) && use this line instead of above for preview
* if the user cancels, a -1 is returned
if p.hDC = -1
   msgbox('User Cancelled Print Job', 'Cancel', 16)
* give it a title
* this is what will show up in the print manager
p.Title = 'Test of Printer Class'
* next we must create the printer device context
* this is sort of like a piece of paper in memory for printing on
* if it returns 0 there was an error ( I have never had this happen)
if p.CreateDC() == 0
   msgbox('Error Accessing Printer', 'Error!', 16)
* next we need to define the fonts we want to use
* this is not absolutely necessary, as there is a default font.
* look in the printer.prg at the definefont method for the complete
* documentation for defining fonts.  You should define all of your fonts
* before you start printing, but this is not absolutely necessary.
p.defineFont('Font 1','Times New Roman', -12, .f., .f., .f.)
p.defineFont(2, 'Courier New', -10, .t., .f., .f.)
* next you select one of the fonts to be default
p.SetFont('Font 1')
* the next line starts the print spooler
* only one of these per document, please!
* now start the page
* printing commands go here
* ===================================================================
* ===================================================================
* now eject the page, you can have as many StartPage/EndPages as
* you have pages in your report.
* the following code should be inserted between pages or in long loops as
* it will break out of the printing if the user presses the 'cancel' button
* on the 'Printing' dialog box
if p.lAborted
   msgbox('Printing Canceled by User', 'Cancel', 16)
* after all pages are printed, issue the EndDoc to end the print spooling
* and send it to the printer.
* clean up to prevent memory leaks
release object p
p = null

* ------------- copy and paste this code ---------------------------

Drawing Lines

The first object we will look at is the line.  The syntax for the line is very simple:

  p.Line1(nTop, nLeft, nBottom, nRight, nPenTag)

Whoops, what is a pentag?  A pen tag is much the same as the font tag used in the defineFont() method.  A pen tag is simply a variable used to reference a pen. What is a pen?  A pen is an object used to draw lines in windows. A pen defines line width, color and style (solid, dashed, etc.)  To define the pen that you want to use to draw lines, you simply use the definePen() method:

  p.DefinePen(Tag, nStyle, nColor, nWidth)

Tag is either a number or a character string you want to use to keep track of a particular pen.  nStyle is one of the several styles identified in the printer.h file.  So you must remember to #include "printer.h" in any programs in which you are going to use pens.  Here is a list from the include file:
  * Pen Styles *
#define PS_SOLID            0
#define PS_DASH             1
#define PS_DOT              2
#define PS_DASHDOT          3
#define PS_DASHDOTDOT       4
#define PS_NULL             5
#define PS_INSIDEFRAME      6

Here is the definition of the various pen styles:

Value  Meaning
PS_SOLID Creates a solid pen.
PS_DASH Creates a dashed pen. (Valid only when the pen width is 1.)
PS_DOT Creates a dotted pen. (Valid only when the pen width is 1.)
PS_DASHDOT Creates a pen with alternating dashes and dots. (Valid only when the pen width is 1.)
PS_DASHDOTDOT Creates a pen with alternating dashes and double dots. (Valid only when the pen width is 1.)
PS_NULL Creates a null pen.
PS_INSIDEFRAME Creates a pen that draws a line inside the frame of closed shapes produced by graphics device interface (GDI) output functions that specify a bounding rectangle (for example, the Ellipse, Rectangle, RoundRect, Pie, and Chord functions). When this style is used with GDI output functions that do not specify a bounding rectangle (for example, the LineTo function), the drawing area of the pen is not limited by a frame.
The nColor parameter is an RGB number. The RGB macro is included in the printer.h include file. To create an RGB number, you can use the dBASE function getcolor() and plug the 3 values returned into the RGB macro:

  RGBNumber = RGB(nRed, nGreen, nBlue)

The last parameter, the nWidth,  is simply the width of the line in thousandths of an inch (millimeters if you are using the SetMapMode("MM")).

Now we are ready to print some lines on our report.  Remember to cut and paste the basic code into your .prg.  Then insert the following code into the spot I told you at the start of this article (the code spot).  Here is an example of creating a pen and drawing a line:

  p.definePen('red', PS_SOLID, RGB(255, 0, 0), .2)
  p.Line1(2, .3, 2, 7.5, 'red')

These two lines of code, plugged into the “code spot” should produce a red line 2 inches from the top of the page, 3 tenths of an inch from the left side, 7.5 inches wide.  Try it and see.  Don't forget the #include "printer.h".

One small note on pens: if you are going to use any style other that PS_SOLID, PS_NULL or PS_INSIDEFRAME, set the width to zero.  Any other style pens are always 1 pixel wide.

Drawing Boxes

The next object we want to take a look at is the box or rectangle.  It would be simple to draw a box with the line() method by just drawing 4 connecting lines. But since windows provides a API call to draw rectangles, it is easier just to use it.  The syntax for drawing a rectange is almost identical to drawing a line:

  p.rectangle(nTop, nLeft, nBottom, nRight, penTag, brushTag)

Here we introduce a new parameter, the brushTag.  The brushTag is the variable to hold the reference to an new item, the brush.  Any object that is enclosed, such as a rectangle, can have its interior painted using the brush object. Again, there is a new method for creating the brush:

  p.DefineBrush(Tag, nStyle, nColor, nHatch)

The tag and nColor parameters are the same as for the line method. The nStyle and nHatch parameters are from the definitions in the printer.h include file:
* Brush Styles
#define BS_SOLID            0
#define BS_NULL             1
#define BS_HOLLOW           BS_NULL
#define BS_HATCHED          2
#define BS_PATTERN          3
#define BS_INDEXED          4
#define BS_DIBPATTERN       5

* Hatch Styles
#define HS_HORIZONTAL       0
#define HS_VERTICAL         1
#define HS_FDIAGONAL        2
#define HS_BDIAGONAL        3
#define HS_CROSS            4
#define HS_DIAGCROSS        5

Here is the definition of the above from the Windows SDK (software development kit):

Value Meaning
BS_DIBPATTERN Specifies a pattern brush defined by a device-independent bitmap (DIB) specification.
BS_HATCHED Specifies a hatched brush.
BS_HOLLOW Specifies a hollow brush.
BS_PATTERN Specifies a pattern brush defined by a memory bitmap.
BS_NULL Equivalent to BS_HOLLOW.
BS_SOLID Specifies a solid brush.

Value Meaning
HS_BDIAGONAL 45-degree upward hatch (left to right)
HS_CROSS Horizontal and vertical cross-hatch
HS_DIAGCROSS 45-degree cross-hatch
HS_FDIAGONAL 45-degree downward hatch (left to right)
HS_HORIZONTAL Horizontal hatch
HS_VERTICAL Vertical hatch
If the Style is the BS_PATTERN style, nHatch must be a handle to the bitmap that defines the pattern. If the Style is the BS_SOLID or the BS_HOLLOW style, nHatch is ignored.

NOTE: Any type of brush using bitmaps IS NOT SUPPORTED by the printer class at this time.  (But maybe it will be in the not too distant future)

To print a example of an unfilled box, let's plug this code into our “code spot”:

  p.rectangle(2, 2, 4, 4, 'DEFAULT', 'DEFAULT')

This will produce a 2 inch square box located down 2 inches from the top of the page and 2 inches from the left margin.  The box will have no fill and the line used to draw the box will be .01 inches wide. Now the first question that should come to mind is: What are the “DEFAULT” pen and brush.  There are “DEFAULT” pens, fonts and brushes defined by the printer class upon instantiation of the object.  I simply used the default pen and brush, which will suffice many times.

In this next example we will define our own pen and brush:
p.definePen('blue', PS_SOLID, RGB(0, 0, 255), .05)
p.defineBrush('bluehatch', BS_HATCHED, RGB(0, 0, 255), HS_CROSS)
p.rectangle(2, 2, 4, 4, 'blue', 'bluehatch')

Now let's plug this into our “code spot” and run it.  It produces a box in the same location as the first example, except with a blue line and filled with blue hatching.

A variation on the rectangle is the round cornered rectangle.   This is produced using the roundRect() method in the printer class.   Here is the syntax for the roundRect:

  p.RoundRect(nTop, nLeft, nBottom, nRight, ;
    nEllipseWidth, nEllipseHeight, PenTag, BrushTag)

As you can see, we have added two parameters, nEllipseHeight and nEllipseWidth.  These parameters describe the height and width of the rounded corners of the roundrect.  You should just do a little experimenting to get the feel of it.  Plug this example into our “code spot” and try it on for size:
p.definePen('blue', PS_SOLID, RGB(0, 0, 255), .05)
p.defineBrush('bluehatch', BS_HATCHED, RGB(0, 0, 255), HS_CROSS)
p.RoundRect(2, 2, 4, 4, .25, .25, 'blue', 'bluehatch')

This example prints a rectangle just like the one above except it has quarter inch radius rounded corners.

Printing Images

Finally, let's print one or two of the various type graphics images that the printer class can handle.  It can print JPG, JIF, GIF, BMP, DIB, RLE, TGA and PCX format images.

  p.AtSayImage(nRow, nCol, xBitMap, nWidth, nHeight, nTopStart, nLeftStart)

Only nRow, nCol and xBitMap parameters are required.  If you are going to use the nTopStart  and nLeftStart, you must specify nWidth and nHeight. You will understand when you see the explanation of nTopStart and nLeftStart below.

This method prints the image contained in various file formats (see above) at the location specified by nRow, nCol (upper left hand corner of the image) and size it to nWidth and nHeight.  If nWidth and nHeight are not specified, it will attempt to print at the default size of the image.  The xBitMap parameter can be either the filename of the image, i.e. “picture.jpg” or the bitmap handle obtained by using the NViewLib.dll (32-bit) or NViewL16.dll (16-bit) from NView Library, which is free and downloadable from:

Here is the code for obtaining the hBitMap handle:

#ifdef __vdb__
   extern chandle NViewLibLoad(cstring, clogical) NViewLib.dll
   extern chandle NViewLibLoad(cstring, clogical) NViewL16.dll
hBitMap = NViewLibLoad(cImageFile, .f.)

One reason for not including the loading of the bitmap handle within this method is that you may want to print the same image on multiple pages, such as printing letterheads.  If that were the case and the method loaded the bitmap each time, it would considerably increase the print time.  You can better control this in your program code.  With this method (function) the AtSayBitMap() method becomes no longer needed.  The nTopStart and nLeftStart  parameters are used when you want to print a form that you have scanned in and its size is the same as a sheet of paper (8.5" x 11").  Most printers won't let you print to the very edge of the paper, so these parameters let you start printing at the left and upper limits of the ability of the printer.  This results in the image being printed in exactly the same place as the original form that was scanned. What is the benefit of this?  You can take a ruler and measure where on the original form you want to print text and it should be the same on the printed form.  If you don't use these parameters, the form will be shifted down and to the right of the original.

Here's and example of printing an image:

  p.AtSayImage(2, 2, "dBASE75.jpg", 3, 2)

Plug that code into our “code spot” and try it.  It will print the dBASE logo at the very upper printable edge of the paper, the size of the image will be 3 inches wide by 2 inches high.  That bitmap is included with this article just in case you don't have it on your drive.

As you can see, it is pretty simple to print lines, boxes and images at the exact location you desire onto your report using the methods provided in the printer/preview class.

Note: The author would like to thank Flip Young, my proof-reader, for the improvements she brought to this text.