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.
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.
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.
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.
To achieve the goal of simplicity, EZPRINT employs three strategies:
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.
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) ;
}
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)
}
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
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 |
Type PrintStruct |
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. |
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.
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.
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.