Printing in Visual dBASE with
Vic McClung’s Printer Class
by Vic McClung

Shortly after I started using Visual dBASE (VdB), I realized that the methods of printing provided were pretty limited. I suppose that because a version of Crystal Reports Writer (CRW) was included with VdB, I began to use CRW for most of my printing needs but soon ran up against a wall. I upgraded to CRW 4.5 which was a little better, but as I have always found, any report writer has its limitations. I was writing a application that required printing on pre-printed forms, such as the “Missouri Application for Title and License”. The text had to be printed at exactly the right location. This is very difficult to do with VdB’s printing and not any better with CRW.

Having programmed in “C”, I became very interested in the `EXTERN` command in VdB. Actually, most anything is possible in VdB if you use the capability to call a function in a “C” DLL. I studied the work of the several Windows API “gurus” on the Borland Visual dBASE news group (now dbase news groups) and with the help of Charles Petzolds’ “Programming Windows” and Daniel Appleman’s “Visual Basic Programmer’s Guide to the Windows API” books, I was able to grasp the basics of using the Windows API for accessing the printer from VdB.

From there on it was just a matter of adding feature after feature. When 32 bit Visual dBASE came out and had many shortcomings in its report writer, I decided to convert the printer class to 32 bits. After a lot of work I realized it was much easier to maintain a 16/32 bit version. The current version of the printer class is 7.1 and will work the same on both versions of Visual dBASE.

How do you use the printer class? It is really pretty simple if you don’t try to make it hard. The class contains a sample program called skelprt.prg which is just that: a skeleton of a printing program. Here is the basic code required to print in either version of VdB:

| `* ------------- 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` `* or:` `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)` `release object p` `p = null` `close procedure printer.prg` `return` `endif` `* 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)` `release object p` `p = null` `close procedure printer.prg` `return` `endif` `* 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!` `p.StartDoc()` `* now start the page` `p.StartPage()` `* printing commands go here` `p.AtSay(1, 1, 'Hello, World')` `* now eject the page, you can have as many StartPage/EndPage's as` `* you have pages in your report.` `p.EndPage()` `* 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)` `endif` `* after each page is printed, issue the EndDoc to end the print spooling` `* and send it to the printer.` `p.EndDoc()` `* clean up to prevent memory leaks` `release object p` `p = null` `close procedure printer.prg` `* ------------- copy and paste this code ---------------------------`

That’s IT! The famous “Hello, World” printing program. I know this is only a rudimentary program, but it really only requires adding a few other methods to be able to print anything you desire. Some of the other methods that can be used are:`AtSayCenter()`,`AtSayRight()`,`AtSayImage()`,`AtSayWrap()`,`AtSayWindow()`. I named them “AtSay” because they act like the xBase`@ 1,1 say "Hello, World"` commands, i.e.`AtSay(row, col, string)`. The printer class by default uses inches (down to the thousandth) for units of measure, but with the `p.SetMapMode(cMode)` this can be changed to centimeters or millimeters. It even has properties and methods that keep track of rows (`p.nextRow(nRows)`) that allow you to print just like `prow()` or whatever that function was.

For a more useful example, let’s write a program to print mailing labels. We will use the clients.dbf table that is included in the samples directory of Visual dBASE 5.x. Notice that I just copy the main parts of the simple example above and then add what I need. I will assume that we are printing on Avery 5160 ( 3 across x 10 down) address labels.

| `* ------------- copy and paste this code ---------------------------` `local i, k, nRowHeight` `local nDown, nhPitch, nvPitch, nAcross, nSideMargin, nTopMargin` `local nLabelHeight, nLabelWidth, nRow, nCol, nColAdj, nRowAdj` `* open the printer.prg` `set procedure to printer.prg additive` `*` `* use the new operator to create the object from the class:` `* p = new printer(.t.) && the parameter is for the printer selection dialog` `* or:` `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)` `release object p` `p = null` `close procedure printer.prg` `return` `endif` `* give it a title` `* this is what will show up in the print manager` `p.Title = 'Avery 5160 Labels'` `* 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)` `release object p` `p = null` `close procedure printer.prg` `return` `endif` `* Define the fonts you are going to use` `p.defineFont('Font 1','Times New Roman', -12, .f., .f., .f.)` `p.setFont('Font 1')` `* now open the table 'clients.dbf'` `use c:\visualdb\samples\clients` `nDown = 10` `nAcross = 3` `nhPitch = 2.75` `nvPitch = 1.00` `nTopMargin = 0.5` `nSideMargin = 0.19` `nLabelHeight = 1.0` `nLabelWidth = 2.63` `nRowHeight = p.GetStringHeight('H')` `p.StartDoc()` `do while .t.` `p.StartPage()` `nRow = nTopMargin` `nCol = nSideMargin +.2` `nRowAdj = 0` `for i = 1 to nDown` `nColAdj = 0` `for k = 1 to nAcross` `p.AtSay(nRow + (1*nRowHeight) + nRowAdj, nCol + nColAdj, clients->contact)` `p.AtSay(nRow + (2*nRowHeight) + nRowAdj, nCol + nColAdj, clients->company)` `p.AtSay(nRow + (3*nRowHeight) + nRowAdj, nCol + nColAdj, clients->address)` `p.AtSay(nRow + (4*nRowHeight) + nRowAdj, nCol + nColAdj, trim(clients->city) +;` `', '+trim(clients->state_prov)+' '+clients->zip_p_code)` `nColAdj = nColAdj + nhPitch` `skip` `if p.lAborted` `msgbox('Printing Cancelled by User', 'Cancel', 16)` `use in clients` `p.EndDoc()` `release object p` `close procedure printer.prg` `return` `endif` `if eof()` `exit` `endif` `next` `if eof()` `exit` `endif` `nRowAdj = nRowAdj + nvPitch` `next` `p.EndPage()` `if eof()` `exit` `endif` `enddo` `use in clients` `p.EndDoc()` `release object p` `close procedure printer.prg` `* ------------- copy and paste this code ---------------------------`

Perhaps in a later article I will go into more detail on printing with the printer class. For example, the class can be used to print various shapes such as ellipses (circle), squares, lines, rectangles, round cornered rectangles, pie sections, chords, and polygons. All of the closed shapes can be filled with various brushes (created with `defineBrush` ) and can be drawn with different pens (created with `definePen`). Functionality includes the ability to change orientation of the page, select paper bins, rotate fonts, change text color and background color. There is the capability to do just about anything you desire, and if it isn’t there now, it can be added without a lot of trouble.

Visual dBASE Printer Class and other Utilities at:
http://www.dbase.com/Docs/codelib.htm

About the author:

My name is Victor Alan McClung, I am married to Paula Jean and we have 4 children: John Paul 20, Laura 18, Caroline 14, and Gabriel 8. I am 56 years old ( July 12th). I work at a large aluminum smelter about 30 miles south of my home town of Sikeston, Mo. I am an electrical planner for the maintenance department. A friend got me started programming in dBASE II in CPM many years ago. I soon switched to Clipper Summer 87 and from there to 5.2. I wrote the Maintenance Information System which the plant used until we changed over to Walkers’ IMMPOWER CMMS about two years ago (the clipper program worked much better and cost millions less). I have been programming in xbase since 1986. I do a little work on the side and am in the midst of small automobile dealership program. Did you ever try print a Missouri Title Application form? Forget it without the printer class, that’s why I wrote it. Out of necessity!

I was trained in electronics/communications in the Air Force in 1965-1968 so I have always been interested in computers. I first taught myself to program in assembly language on a Radio Shack TRS-80 Model 3. I then upgraded to a Model 4 and learned “C” using Roy Soltoff’s (Misosys) “C” compiler for the trash 80. During a strike in 1986 at the plant I dug a multi-user CPM computer out the trash and set it up in the office and ran cables to all the other offices in the Maintenance Planning Department. The friend helped me get started writing the maintenance system in dBASE II. Later they bought us 286 PC’s and I used dBASE III+. I went from there to Summer ’87 and 5.2 as the system grew and grew. These days they don’t want us xbase programmers writing any code and won’t buy us Visual dBASE. Our IS manager has set a standard for the plant of MS Access and I have told them I won’t use Access. I hate it worse than I do Pascal.