EZPRINT: No-Frills Printing in Visual Basic and C

Dennis Crain
Microsoft Developer Network Technology Group

Created: April 19, 1993

Click to open or copy the files in the EZPRINT sample application for this technical article.

Abstract

Printing from applications written for the Microsoft® Windows™ version 3.1 operating system can be trying at times. This article describes a convenient method for printing from applications using a dynamic-link library (DLL) called EZPRINT.DLL. EZPRINT provides three exported functions—StartDocument, PrintObject, and EndDocument—to start the printing job, print various objects on the page, and end the printing job. The sample DLL code and Visual Basic™ code that calls EZPRINT are included with this article. You may use the DLL as is or modify it to meet your needs. In its present form, EZPRINT prints text, rectangles, and lines with various attributes.

This article was written for developers who have relatively little printing experience and whose printing needs involve text placement and simple line drawing only. Experienced developers can also use EZPRINT as a framework for implementing more sophisticated printing capabilities.

Forgot Something, Didn't You?

It is two in the morning and you've just put the final touches on the user interface for your application. The back-end was finished months ago, and tonight you finally see the light at the end of the tunnel. Your application glows at you in the dimly lit room as a sense of accomplishment overcomes you. Gradually, the self-applause changes to concern as you realize that your application needs to print. Sadly, you recall that you decided to cross this bridge when you came to it. Well, here you are and just where do you start? You've heard something about StartDoc and EndDoc but they sound intimidating. And what was that AbortProc all about? At this point, you wish you were writing a character-based application.

Does this sound familiar? If it does, the EZPRINT dynamic-link library (DLL) and this article are just for you. The article provides in-depth documentation for using the DLL as is.

However, if you are accomplished at basic printing, you may be looking for a convenient framework for building more advanced printing capabilities. The article touches on the design and implementation of the DLL, which you can extend for your own purposes. I've included the code for EZPRINT as a sample of a specific implementation. The sample uses a pseudo-object-oriented approach that can be extended easily to support more complex drawing features, bitmaps, and metafiles.

Overview of EZPRINT

Simplicity is the prime goal of the EZPRINT printing method. Three functions are exported from EZPRINT.DLL: StartDocument, PrintObject, and EndDocument. These functions permit you to start the printing process, place various objects on the page, and end the printing process. Using EZPRINT's present form, you can print text objects, line objects, and rectangle objects with text style, fill, and pen attributes.

Keep It Simple

To achieve the goal of simplicity, EZPRINT employs three strategies:

The logical coordinate system

The use of a logical (world) coordinate system enhances the simplicity of EZPRINT. The page is treated as a 1000 x 1000-unit grid (Figure 1). The upper-left corner of the page is described as the x and y ordered pair (0,0). The lower-right corner is described as (1000, 1000). This coordinate system allows you to place objects on the printed page independently of page dimensions and paper orientation.

Figure 1. Logical (world) coordinate system of a page

Many developers deal with only one mapping mode, MM_TEXT, which maps one logical unit to one device unit. EZPRINT allows the application to experiment with the MM_ANISOTROPIC mapping mode, which provides full control over coordinate mapping. In the MM_ANISOTROPIC mode, the application sets the window and viewport extents to any desired value. For a comprehensive discussion of mapping modes, see the "Coordinate Mapping" technical article by Ron Gery on the Developer Network CD (look under Technical Articles, Windows Articles, GDI Articles, in the Source index).

By default, EZPRINT sets the logical coordinate system, mapping mode, origin, and extents of the printer device context (DC) as follows:

SetMapMode(pd.hDC, MM_ANISOTROPIC );
SetWindowOrg(pd.hDC, 0, 0 );
SetWindowExt(pd.hDC, 1000, 1000 );
SetViewportOrg(pd.hDC, PhysicalPage.XOrg, PhysicalPage.YOrg );
SetViewportExt(pd.hDC, PhysicalPage.XExt, PhysicalPage.YExt );

These settings allow you to specify the center of the page as the (500, 500) ordered pair, regardless of paper size and orientation.

In this implementation of the MM_ANISOTROPIC mapping mode, one logical unit is mapped to the total number of device units divided by 1000. For example, if the width of a page is 2000 device units, one logical unit maps to 2000/1000, or two device units.

The PrintDlg common dialog function

EZPRINT uses the PrintDlg function provided by the Windows common dialogs. If you are using EZPRINT as is, you can choose to display the Print dialog box before printing by setting the bEnablePrintDialog parameter of the StartDocument function to TRUE (see the "Technical Reference" section later in this article for more information).

By default, EZPRINT disables three features of the Print dialog box by setting bits in the Flags member of the PRINTDLG structure:

If you wish to enable these features, you must modify the source code for EZPRINT.

The PrintDlg function also eases the drudgery of obtaining a printer DC. Contrast the NewGetPrinterDC and OldGetPrinterDC functions: NewGetPrinterDC obtains a printer DC using PrintDlg, while OldGetPrinterDC uses the more convoluted method required before Windows 3.1. If your application must print in earlier versions of Windows (before version 3.1), you must either use OldGetPrinterDC or ship COMMDLG.DLL with your application.

PRINTDLG pd;

HDC NewGetPrinterDC()
{
    //Set all structure members to zero.
  memset(&pd, 0, sizeof(PRINTDLG));
    //Initialize the necessary PRINTDLG structure members.
  pd.lStructSize = sizeof(PRINTDLG);
    //Initialize flags.
  pd.Flags = PD_RETURNDC | PD_RETURNDEFAULT;
    //If successful, return hdc; else return NULL.
  return ( (PrintDlg(&pd) != 0) ? pd.hDC : NULL )
}

HDC OldGetPrinterDC()
{
  static char szPrinter [80] ;
  char *szDevice, *szDriver, *szOutput ;
  char szWin[] = "windows";
  char szDev[] = "device";

  GetProfileString ((LPSTR)szWin, (LPSTR)szDev, ",,,", szPrinter, 80) ;

  if (NULL != (szDevice = strtok (szPrinter, "," )) &&
    NULL != (szDriver = strtok (NULL,      ", ")) &&
    NULL != (szOutput = strtok (NULL,      ", ")))

  return CreateDC (szDriver, szDevice, szOutput, NULL) ;
}

Isolating the code in a single function

The PrintObject function is an entry point into EZPRINT.DLL. PrintObject calls the PrintObjectIndirect function, which wraps the printing functionality of EZPRINT. This single-function approach is not truly object-oriented, but it permits you to expand printing capabilities in a logical, coherent manner without rocking the boat too much. Each printable object is implemented as a case through a switch statement. For example, the code below implements a line object (PO_LINE). For simplicity, I've removed the code for all objects except lines. For the complete sample, see the PRINT.C file.

  //The local function in EZPRINT that is called by PrintObject handles 
  //all implementation-level stuff for printing an object.
int PrintObjectIndirect(LPPRINTSTRUCT lpPStruct)
{
  int nSuccess = 1;

  //Implement the implementation for each printable object.
  switch (lpPStruct->nPrintObject)  {
    case PO_TEXT:
      //See sample code for details.
    break;
    case PO_RECT:
      //See sample code for details.
    break;
    case PO_GRID:
      //See sample code for details.
    break;
    case PO_LINE:
    {
      HPEN hOldPen, hPen;
      POINT pt[2];
      int  nPenStyle;
      int  nLineWidth;

        //Specify the style of the pen based on the PRINTSTRUCT flags 
        //set by the application.
      nPenStyle = (lpPStruct->uiPenFlags & PEN_DASH) ? PS_DASH : 
                  (lpPStruct->uiPenFlags & PEN_DOT) ? PS_DOT :
                  (lpPStruct->uiPenFlags & PEN_DASHDOT) ? PS_DASHDOT :
                  (lpPStruct->uiPenFlags & PEN_DASHDOTDOT) ? 
                   PS_DASHDOTDOT : PS_SOLID;

        //Set the width of the pen. The default is 0, which forces
        //a pen width of 1 pixel regardless of the mapping mode.
        //If PEN_THICK is specified, we use a pen width of 2 (this
        //is not necessarily 2 pixels in MM_ANISOTROPIC mapping mode).
      nLineWidth = (lpPStruct->uiPenFlags & PEN_THICK) ? 2 : 0;

        //Create the pen based on the above attributes.
      hPen = CreatePen(nPenStyle, nLineWidth, BLACK);

        //x, y origin.
      pt[0].x = lpPStruct->nXOrg;
      pt[0].y = lpPStruct->nYOrg;
        //x, y extent. These are not really extents; they represent
        //a destination.
      pt[1].x = lpPStruct->nXExt;
      pt[1].y = lpPStruct->nYExt;

        //Select the pen into the printer DC.
      hOldPen = SelectObject(hPrtDC, hPen);
        //Draw the line.
      Polyline(hPrt, pt, sizeof(pt) / sizeof(POINT));
        //Delete the pen, now that drawing is complete.
      DeleteObject(SelectObject(hPrtDC, hOldPen));
    }
    break;
    case PO_NEWPAGE:
      //See sample code for details.
    break;
    default:
    break;
  }
  return(nSuccess)
}

Using EZPRINT in Visual Basic

You may call EZPRINT.DLL from any application written for Windows. The basic strategy is to place objects on a page at specific locations. Remember, the page is treated as a 1000 x 1000-unit grid. This section describes how to use EZPRINT.DLL in Visual Basic™.

You must include the following constants, user-defined type, and function declarations in your Visual Basic application:

Global Const TEXTLEN = 256
Global Const PO_TEXT = 0
Global Const PO_RECT = 1
Global Const PO_LINE = 2
Global Const PO_NEWPAGE = 4
Global Const PO_GRID = 5

' Object Flags
Global Const TEXT_NORMAL = &H0
Global Const TEXT_BOLD = &H1
Global Const TEXT_ITALIC = &H2
Global Const TEXT_UNDERLINED = &H4
Global Const TEXT_STRIKEOUT = &H8
Global Const TEXT_RECT = &H10
Global Const TEXT_LEFT = &H20
Global Const TEXT_RIGHT = &H40
Global Const TEXT_CENTER = &H80
Global Const TEXT_EXPANDTABS = &H100
Global Const TEXT_SINGLELINE = &H200
Global Const TEXT_NOCLIP = &H400

Global Const FILL_NONE = &H0
Global Const FILL_GRAY = &H1
Global Const FILL_LTGRAY = &H2
Global Const FILL_BLACK = &H4

Global Const PEN_SOLID = &H0
Global Const PEN_DOT = &H1
Global Const PEN_DASH = &H2
Global Const PEN_THIN = &H4
Global Const PEN_THICK = &H8
Global Const PEN_DASHDOT = &H10
Global Const PEN_DASHDOTDOT = &H20

Type PrintStruct
    PrintObject As Integer
    TextFlags As Integer
    FillFlags As Integer
    PenFlags As Integer
    XOrg As Integer
    YOrg As Integer
    XExt As Integer
    YExt As Integer
    PointSize As Integer
    Text As String * TEXTLEN
End Type

'Function Declarations

Declare Function StartDocument% Lib "EZPRINT.DLL" 
         (ByVal EnablePrintDlg As Integer)   'This should be on one line.
Declare Function EndDocument% Lib "EZPRINT.DLL" ()
Declare Function PrintObject% Lib "EZPRINT.DLL" (PS As PrintStruct)

The following Visual Basic code demonstrates how to print a single page with the text "Report Title" appearing at the top of the page, centered in a bounding rectangle the width of the page. The code allocates a print structure, and then calls the StartDocument function. Note that the Print dialog box is not enabled because the value of the second parameter in StartDocument is FALSE. Objects are then printed on the page at various x and y locations. To start a new page, set the PrintObject member of PrintStruct to PO_NEWPAGE. Once printing is complete, the code calls the EndDocument function.

Sub Form_Load ()
  'Allocate a print structure.
  Dim PS As PrintStruct

  'Start the document.
  nSuccess% = StartDocument(hWnd, FALSE)
  If nSuccess% = False Then End

  'Print objects. In this case, print a line of text. If you wanted to print
  'more objects (such as lines or more text), you would add code here.

  PS.PrintObject = PO_TEXT
  PS.XOrg = 0
  PS.YOrg = 12
  PS.XExt = 1000
  PS.YExt = 24
  PS.PointSize = 12
  PS.Text = ASCIIZ(" Report Title ")
  PS.TextFlags = TEXT_ITALIC Or TEXT_BOLD Or TEXT_CENTER
  linespacing% = PrintObject(PS)

  'End the document.
  EndDocument()
End Sub

Technical Reference

The PRINTSTRUCT Data Structure

The PRINTSTRUCT structure defines the type of object to be printed, its location (origin and extents), and its characteristics.

C Structure Visual Basic Defined Type
typedef struct tagPRINTSTRUCT
{
  int      nPrintObject
  UINT     uiTextFlags
  UINT     uiFillFlags
  UINT     uiPenFlags
  int      nXOrg
  int      nYOrg
  int      nXExt
  int      nYExt
  int      nPointSize
  char     szText[256]
} PRINTSTRUCT
Type PrintStruct
  PrintObject     As Integer
  TextFlags       As Integer
  FillFlags       As Integer
  PenFlags        As Integer
  XOrg            As Integer
  YOrg            As Integer
  XExt            As Integer
  YExt            As Integer
  PointSize       As Integer
  Text            As String*256
End Type

PRINTSTRUCT members are described below.

Member Description
nPrintObject Specifies the type of object to print or display. This member can take one of the following values:
Value Meaning
PO_GRID Prints a grid on the page. Used for formatting purposes only during the development of a report format.
PO_LINE Prints a line object.
PO_NEWPAGE Instructs the printer to eject the page or begin a new page.
PO_RECT Prints a rectangle object.
PO_TEXT Prints a text object.
uiTextFlags Specifies the attributes to be applied to text. This member can take any combination of the following values:
Value Meaning
TEXT_BOLD Uses a bold weight.
TEXT_CENTER Centers the text in the rectangle specified by the origins and extents provided in nXOrg, nYOrg, nXExt, and nYExt.
TEXT_EXPANDTABS Expands tab characters. The number of characters per tab is eight.
TEXT_ITALIC Uses the italic font.
TEXT_LEFT Aligns the text to the left of the point specified by nXOrg and nYOrg.
TEXT_NOCLIP Draws text without clipping it to the bounding rectangle.
TEXT_NORMAL Applies normal attributes to the font.
TEXT_RECT Encloses the text in a rectangle.
TEXT_RIGHT Aligns the text to the right of the point specified by nXOrg and nYOrg.
TEXT_SINGLELINE Specifies that only a single line of text is to be printed. The default is multiple lines of text.
TEXT_STRIKEOUT Uses the strikeout font.
TEXT_UNDERLINED Uses the underlined font.
uiFillFlags Specifies the type of fill to be used for rectangle objects. This member can take one of the following values:
Value Meaning
FILL_BLACK Uses a black fill.
FILL_DKGRAY Uses a dark gray fill.
FILL_GRAY Uses a gray fill.
FILL_LTGRAY Uses a light gray fill.
FILL_NONE Uses no fill.
uiPenFlags Specifies the size and style of the pen used for drawing lines and rectangle boundaries. This member can take one of the following values:
Value Meaning
LINE_DASH Uses a dashed-line style.
LINE_DOT Uses a dotted-line style.
LINE_SOLID Uses a solid-line style.
LINE_THICK Uses a double-pixel-width line.
LINE_THIN Uses a single-pixel-width line.
XOrg Specifies the x-origin, in logical units, of the rectangle that bounds the object to be printed. The point (0,0) is located in the upper-left corner of the page.
YOrg Specifies the y-origin, in logical units, of the rectangle that bounds the object to be printed. If the print object is PO_TEXT, this value specifies the upper-left corner of a rectangle bounding the text.
XExt Specifies the x-extent, in logical units, of the rectangle that bounds the object to be printed. The extents (1000, 1000) describe the point located at the lower-right corner of the page. Please remember that this is an "extent" (defined as the logical distance from XOrg that the rightmost boundary of the rectangle will extend to), and not a member of an ordered point.
YExt Specifies the y-extent, in logical units, of the rectangle that bounds the object to be printed. See my comments above regarding the nature of an extent.
nPointSize Specifies the point size of the font.
szText Consists of a null-terminated string representing the text that is printed when nPrintObject is set to PO_TEXT. This string may contain a maximum of 256 characters.

Functions Exported from EZPRINT

StartDocument

The StartDocument function checks for the presence of a printer and initializes it. This function must be called only once, before sending print objects to the printer.

Syntax

BOOL StartDocument(HWND hWnd, BOOL bEnablePrintDialog)

Parameters

hWnd Handle to a window that is the parent of the printer dialog box (generally the application's main overlapped window).
bEnablePrintDialog Flag (TRUE or FALSE) indicating whether the Print dialog box should be displayed.

Return value

TRUE if successful; FALSE if unsuccessful.

PrintObject

The PrintObject function draws lines, rectangles, and text to the printer.

Syntax

int PrintObject( lpPS )

Parameter

lpPS Pointer to the PRINTSTRUCT data structure discussed earlier in this section.

Return value

If the nPrintObject member PrintStruct is set to PO_TEXT, the PrintObject function returns the interline distance required for single line spacing. If the nPrintObject member of PRINTSTRUCT is set to any value other than PO_TEXT, the PrintObject function returns TRUE if the call was successful, and FALSE otherwise.

EndDocument

The EndDocument function terminates the printing process. This function must be called after the StartDocument function, and only after all print objects have been sent to the printer.

Syntax

BOOL EndDocument(HWND hWnd)

Parameter

hWnd Handle to a window that is the parent of the printer dialog box (generally the application's main overlapped window).

Return value

TRUE if successful; FALSE if unsuccessful.