Win32 Print Code

I've spent the last two or three days learning how to print from the Win32 API. I used Petzold's examples, but I updated quite a bit from MSDN, the most important of which was going from the PRINTDLG structure to the PRINTDLGEX structure, which is quite a bit more complicated and a lot more robust.

I also added a margin capability, for Petzold's examples were only for bare-bones printing. I will post sections of code and comment on them very generally. If you take all of the code snippets and put them into one function you should be good to go.

I used VC++ 2010 Express, Multi-byte character set, with MT/d linking. This code is a real piece of code that I am using my main app, called "Bible Organizer Study Suite 2.0" (for use with BibleWorks only), which will be released sometime in the (hopefully) not too distant future. BOSS 1.0 was released about a year and half ago, and this current version is a very, very substantial update. Of course, I use the term "update" loosely, because all my software is freeware. So in other words, this function can be adapted very easily because it is really already a functioning function in a real program. I am using it now in my program, in other words.

1
2
BOOL BOSSPrintNormalProc(HWND hPrintWnd, int iBox, BOOL bList)
{


My program contains five main tabs, and one of the five main tabs contains an additional seven tabs (at present, that number may change upwards or downwards), and I therefore have approximately 10 or more edit boxes and 10 or more list boxes that the user can print from, hence the "iBox" parameter, which is passed into this function from the caller.

The "BOOL bList" parameter determines whether or not a list box or an edit box has been passed in. If bList=TRUE, then it's a list box. If bList=FALSE, it's an edit box, and as you'll see, these have to be handled quite differently.

1
2
3
4
5
6
7
TEXTMETRIC tm;
int iTotalLines=0;
DOCINFO di = { sizeof(DOCINFO) };
char szNoMem[]="Unable to allocate memory. Operation cancelled!";
char szNoPrint[]="Unable to access printer dialog. Operation cancelled!";
char szNoSelect[]="No List Box Selected!\r\n\r\nPlease Check A List Box And Try Again.";
const int COMPLETENUM=10, ONEINCH=12, PMARGIN=600; // bottom margin at 1 inch (actual lines), left and top at one inch (pixels) 


Most of my variables, as you will see, are declared inline, but the above are declared here, either because they occur inside conditional loops or the first time they are used is in a definition where they can't be declared inside of it.

The vars ONEINCH and PMARGIN deal with the printer margins. The ONEINCH var deals with the actual line numbers, hence 12 lines generally equals roughly 1 inch. The PMARGIN var is calculated from values using GetDeviceCaps() and is thus in pixels, and 600 pixels generally represents roughly 1 inch. You will see where and how these vars are used further down.

1
2
3
4
5
6
7
if(bQuickPrint)
{
	BOSSQuickPrint(hPrintWnd, iBox, bList);
	return FALSE;
}

	LPPRINTPAGERANGE pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, COMPLETENUM * sizeof(PRINTPAGERANGE));


The BOSSQuickPrint() function prints automatically with the default printer instead of calling the Windows Print Dialog, as we are about to do, so this function can be ignored here.

The LPPRINTPAGERANGE is not in Petzold because this applies to the updated structure. Read about it in MSDN for more info. For now, notice that we are allocating memory for a number of total structures in the above statement. Read more about it in MSDN.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//  Initialize the PRINTDLGEX structure.
PRINTDLGEX pdx;

pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hPrintWnd;
pdx.hDevMode = NULL;
pdx.hDevNames = NULL;
pdx.hDC = NULL;
pdx.Flags = PD_RETURNDC;// | PD_COLLATE;
pdx.Flags2 = 0;
pdx.ExclusionFlags = 0;
pdx.nPageRanges = 0;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.hInstance = 0;
pdx.lpPrintTemplateName = NULL;
pdx.lpCallback = NULL;
pdx.nPropertyPages = 0;
pdx.lphPropertyPages = NULL;
pdx.nStartPage = START_PAGE_GENERAL;
pdx.dwResultAction = 0;
    
//  Invoke the Windows Print Dialog Box    
HRESULT hResult=PrintDlgEx(&pdx);
	
if(hResult != S_OK)
{
	MessageBox(hPrintWnd, szNoPrint, szAppName, MB_ICONEXCLAMATION);
	return FALSE;
}


We have just initialized the entire structure, called the Windows Print Dialog, and then checked to make sure the function suceeded. As you can see, I have commented out the PD_COLLATE flag, as I don't want it, and to include it requires several additional lines of code in the print loop. See Petzold for more info on that, and see MSDN for more info on the PRINTDLGEX struct, as this is much more robust than the plain PRINTDLG struct in Petzold.

1
2
3
4
5
6
7
GetTextMetrics(pdx.hDC, &tm);

int yChar = tm.tmHeight + tm.tmExternalLeading;
int iLinesPerPage=GetDeviceCaps(pdx.hDC, VERTRES) / yChar;
int iCharsPerLine=GetDeviceCaps(pdx.hDC, HORZRES) / tm.tmAveCharWidth;

iLinesPerPage -= ONEINCH; // to set the bottom margin at 1 inch 


The above is obvious, so I'll dispense with comments here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if(bList)
{
	BOSSPrintListBox(hPrintWnd, &iBox);

	if(iBox >= IDL_MULTI1 && iBox <= IDL_MULTI5)
	{
		iTotalLines=SendDlgItemMessage(hPrintWnd,iBox,LB_GETCOUNT,0,0);
	}
	else // no box is checked, so free up memory and exit
	{
		if (pdx.hDevMode != NULL) 
			GlobalFree(pdx.hDevMode); 
		if (pdx.hDevNames != NULL) 
			GlobalFree(pdx.hDevNames); 
		if (pdx.lpPageRanges != NULL)
			GlobalFree(pPageRanges);

		// we don't want the messagebox is user clicked cancel
		if(pdx.dwResultAction != PD_RESULT_CANCEL && pdx.dwResultAction != PD_RESULT_APPLY)
			MessageBox(hPrintWnd,szNoSelect,szAppName,MB_ICONINFORMATION);
		return FALSE;
	}
}
else
{
	iTotalLines=SendDlgItemMessage(hPrintWnd,iBox,EM_GETLINECOUNT,0,0);
}

int iTotalPages = (iTotalLines + iLinesPerPage - 1) / iLinesPerPage;


As you can see, the list boxes and edit boxes must be handled differently. Read up on the PRINTDLGEX structure to understand the rest of the above code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PTSTR szBuffer=(PTSTR)malloc( (iCharsPerLine + 1) * sizeof(char) );

if( szBuffer==NULL )
{
	if (pdx.hDevMode != NULL) 
		GlobalFree(pdx.hDevMode); 
	if (pdx.hDevNames != NULL) 
		GlobalFree(pdx.hDevNames); 
	if (pdx.lpPageRanges != NULL)
		GlobalFree(pPageRanges);

	MessageBox(hPrintWnd, szNoMem, szAppName, MB_ICONEXCLAMATION);
	return FALSE;
}


Here we're just allocating our original block of memory and checking it.

Now we'll start our print loop and exit the function when it's completed...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
if(StartDoc(pdx.hDC, &di) > 0) // begin printing loop
{
	for(int iPage=0; iPage < iTotalPages; iPage++)
	{
		StartPage(pdx.hDC);

		for(int iLine=0; iLine < iLinesPerPage; iLine++)
		{
			int iLineNum = iLinesPerPage * iPage + iLine;

			if(iLineNum > iTotalLines)
				break;

			if(bList)
			{	
				*(int *)szBuffer=(SendDlgItemMessage(hPrintWnd,iBox,LB_GETTEXTLEN,0,0)-1);					
				SendDlgItemMessage(hPrintWnd,iBox,LB_GETTEXT,(WPARAM)iLine,(LPARAM)szBuffer);
			}
			else
			{
				*(int *)szBuffer=iCharsPerLine;
				SendDlgItemMessage(hPrintWnd,iBox,EM_GETLINE,(WPARAM)iLineNum,(LPARAM)szBuffer);
			}
			TextOut(pdx.hDC, PMARGIN, (yChar * iLine) + PMARGIN, szBuffer, strlen(szBuffer));
		}
		
		EndPage(pdx.hDC);
	}

	EndDoc(pdx.hDC);
}

if (pdx.hDevMode != NULL) 
     GlobalFree(pdx.hDevMode); 
if (pdx.hDevNames != NULL) 
     GlobalFree(pdx.hDevNames); 
if (pdx.lpPageRanges != NULL)
     GlobalFree(pPageRanges);

if(NULL != szBuffer)
	free(szBuffer);

return TRUE;
}


As you can see once again, the list boxes and the edit boxes must be handled differently. Instead of my explaing the above, it's all relatively straightforward. If you consult Petzold (Chapter 13), you'll be able to see what has been adapted and what has been mirrored.

In any case, that's all there is to it. Not so hard after all.
By the way, just ignore the BOSSPrintListBox() function in the first bList conditional statement, as this function merely determines which list box the user has selected for printing. Take it out of your code is you don't need to determine this.

Also, I meant to say "with /MTd runtime", but this board won't let me do the correction up there. I have had many problems with this forum board. It doesn't show me what messages are new, half the time I post it goes into a waiting loop and doesn't ever post, etc.

And it's not my computer because it happens on my other ones as well. I hope they work out whatever the problem is. It's been going on for quite a while now.
Last edited on
I've added the PD_COLLATE option. In addition to replacing the above print loop with the following, you need to UNcomment the PD_COLLATE flag in the structure initialization. Here is the new print loop --

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
if(StartDoc(pdx.hDC, &di) > 0) // begin printing loop
{
	for(WORD iColCopy=0; iColCopy < ((WORD) pdx.Flags && PD_COLLATE ? pdx.nCopies : 1); iColCopy++)
	{
		for(int iPage=0; iPage < iTotalPages; iPage++)
		{
			for(WORD iNoiColCopy=0; iNoiColCopy < (pdx.Flags && PD_COLLATE ? 1 : pdx.nCopies); iNoiColCopy++)
			{
				if( StartPage(pdx.hDC) < 0)
					break;

				for(int iLine=0; iLine < iLinesPerPage; iLine++)
				{
					int iLineNum = iLinesPerPage * iPage + iLine;
					if(iLineNum > iTotalLines)
						break;

					if(bList)
					{	
					         *(int *)szBuffer=(SendDlgItemMessage(hPrintWnd,iBox,LB_GETTEXTLEN,0,0)-1);					
						SendDlgItemMessage(hPrintWnd,iBox,LB_GETTEXT,(WPARAM)iLineNum,(LPARAM)szBuffer);
					}
					else
					{
						*(int *)szBuffer=iCharsPerLine;
						SendDlgItemMessage(hPrintWnd,iBox,EM_GETLINE,(WPARAM)iLineNum,(LPARAM)szBuffer);
					}
					TextOut(pdx.hDC, PMARGIN, (yChar * iLine) + PMARGIN, szBuffer, strlen(szBuffer));
				}
		
				if( EndPage(pdx.hDC) < 0)
					break;
			}
		}
	}
}

EndDoc(pdx.hDC);


The iColCopy and the iNoiColCopy are both required if you set the PD_COLLATE flag. I took these two loops verbatim from Petzold, except Petzold used "pd" for PRINTDLG and I used "pdx" instead, for PRINTDLGEX. Also, Petzold declared his variables at the top of his function while I have declared iColCopy and iNoiColCopy inline, as I have done with most of the others, where ever possible.

Notice also that I moved the EndDoc() function outside of the entire print loop.
Last edited on
If you copied the above code when I first posted it you need to recopy it, or change the "iLine" variable in the two SendDlgItemMessage() functions with "iLineNum", because if you don't it will only print the first page multiple times.
Sorry to keep pumping this thread, but I have discoverd that if I choose 2 copies in the Collate section of the Windows Print Dialog, that both of my printers print 4 copies instead of 2.

That almost certainly means the error is in the above print loop, but thus far I have been unable to work it out.

If anybody knows, please let me know. Remeber, I've taken this from Petzold, so I've either done something wrong in my transpostion, as I have modified a fair amount, or Petzold did something wrong. It's probably me, but I don't know what it is. I've compared it carefully to Petzold, and I think I've got it right as far as the transcription.
Last edited on
Topic archived. No new replies allowed.