In this article, I demonstrate how to print a user-drawn bitmap without relying on the
View's functions. After reading this, you’d see every step in manually getting a printer to work. Basically, we need to deal with a few things:
- Get a printer device context and calculate the total number of sheets to be printed before opening the printer dialog.
- Calculate the portion (dimension) of the bitmap to be sent to the printer to fit into one print sheet (paper page). Avoid printing distortion.
All the code segments are contained in a function (no parameter) under
memDC.CreateCompatibleDC( &dc );
CBitmap * bitmap = new CBitmap();
CBitmap * pOldBitmap = (CBitmap *) memDC.SelectObject( bitmap );
if (pOldBitmap == NULL)
AfxMessageBox("Not enough resource for the bitmap.....");
The above code draws an ellipse. For more sophisticated drawings, it can be done elsewhere, as long as we have the pointer to the bitmap. One "unusual" thing is the check on
pOldBitmap. One doesn’t see this very often. However, as the warning message says, it could happen when the Windows bottle is unable to hold the big picture.
Now we drive the printer to work. First get a printer DC from the default printer, and with this DC, collect some printer information. This is for calculating the actual pages to be printed and other parameters.
if( AfxGetApp()->GetPrinterDeviceDefaults(&printInfo.m_pPD->m_pd) )
HDC hDC = printInfo.m_pPD->m_pd.hDC;
if (hDC == NULL)
hDC = printInfo.m_pPD->CreatePrinterDC();
AfxMessageBox("Can not find printer.
Please check installed/default printers.");
Now we calculate the total pages to be printed (refer to source code file for the parameters). This way, we can activate the page info radio buttons in printer dialog:
int total_pages = (bmpWidth * ratio_x + paper_width - 1 ) / paper_width;
Finally, we stuff the printer dialog, and pop it up:
CPrintDialog prtDlg(FALSE, PD_PAGENUMS);
prtDlg.m_pd.nMinPage = 1;
prtDlg.m_pd.nMaxPage = total_pages;
prtDlg.m_pd.nFromPage = 1;
prtDlg.m_pd.nToPage = total_pages;
if(prtDlg.DoModal() == IDOK )
The following code segment is responsible for starting the print job, sending data to the printer, and ending the job when all is printed. Actual printer operation (the noise) is not started until
StartPage method is called, which sends data for printing one sheet (paper) only. To print more pages, repeat calling this method. In using
StretchBlt method, care is taken for the last page. Unlike other bitmap portions with default width corresponding to the paper width, we need to find its width and
StretchBlt it to appropriate dimensions on the paper, instead of filling the whole paper width, the latter may cause distortion.
if(prtDC.StartDoc(&di) == -1)
AfxMessageBox("Printing error occurred. Unable to find printer.");
int i = 0;
for(i = 0; i < total_pages; i++)
strPageNumber.Format("%d of %d", ++printed_pages, total_print_pages );
if( i == (total_pages - 1) && total_pages > 1 )
int last_bmpWidth = bmpWidth - paper_width / ratio_x * i;
prtDC.StretchBlt(0, 0, last_bmpWidth * ratio_x, bmpHeight* ratio_y,
&memDC, paper_width * i / ratio_x, 0,
last_bmpWidth, bmpHeight, SRCCOPY);
prtDC.StretchBlt(0, 0, paper_width, bmpHeight* ratio_y, &memDC,
paper_width * i / ratio_x, 0,
paper_width / ratio_x , bmpHeight, SRCCOPY);
prtDC.TextOut(page_info_rect.left, page_info_rect.top, strPageNumber );
That’s it. One thing I omitted (for clarity) is printing the selected pages (returned by the dialog). Also, one can add a progress dialog which shows the printing pages and gives the user a Cancel button to stop the loop. This had been well covered by other authors. See, for example, Paul DiLasci’s article in MSJ(7/98).