Introduction
It seems from many of my other articles that I have become the "Print" guru here at
CodeProject. So I though I would gather together a list of printing Tricks and Tips that
I have collected together over a period of time through trial and error.
- Only ever print inside the
CPrintInfo::m_rectDraw
area
Your printing code should not make assumptions about where it should print on the page,
and make proper use of the CPrintInfo::m_rectDraw
variable. This ensures that
you will not overwrite margins/headers/footers that may be printed outside of your main
OnPrint
procedure.
pDC->TextOut(pInfo->m_rectDraw.left, pInfo->m_rectDraw.top,
"Only draw inside the reported m_rectDraw area") ;
Getting a PrinterDC in OnPreparePrinting()
When OnPreparePrinting()
is called in your CView
derived class,
you are generally required to setup the number of pages of output your document will need
when printing, unless you are using the CPrintInfo::m_bContinuePrinting
method.
But it can be difficult to do this if you have no information on the printer resolution or page size.
So at this point you need to get hold of the printer DC object that will be used. As the MFC print
architecture would not create this until the OnBeginPrinting()
function would be called,
you have to create one yourself and release it after calculating the number of pages you want to print.
To create such a printer DC you can use this code:
CDC dc ;
AfxGetApp()->CreatePrinterDC(dc) ;
...
dc.DeleteDC() ;
This will create a printer DC for the default printer selected for you application. To switch to a
different printer in code you should see my article Setting the default printer
programmatically in an MFC application
Getting the size of the printable page area
The printable area of a page on a printer is normally contained in the CPrintInfo::m_rectDraw
member variable.
A CPrintInfo
object gets passed through to your CView
overridden virtual functions. But in some cases, like
in OnPreparePrinting(), OnBeginPrinting()
, this member variable will not yet have been intialised. So you have to do it
yourself.
pInfo->m_rectDraw.SetRect(0, 0,
pDC->GetDeviceCaps(HORZRES),
pDC->GetDeviceCaps(VERTRES)) ;
This gets the printable area of a printers page.
Margins
In many cases you may want to have a user programmable margin around a page so that you do not over-print company logo's etc on
headed paper, so you can set a user programmable range for you margins in inches
. You can then convert these to device
units and reserve that space on the page by changing the dimensions of the CPrintInfo::m_rectDraw
variable. For example:
double LeftOffset = 0.5 ;
double TopOffset = 0.5 ;
double RightOffset = 0.5 ;
double BottomOffset = 0.5 ;
pInfo->m_rectDraw.DeflateRect(
(int)(pDC->GetDeviceCaps(LOGPIXELSX) * LeftOffset),
(int)(pDC->GetDeviceCaps(LOGPIXELSY) * TopOffset),
(int)(pDC->GetDeviceCaps(LOGPIXELSX) * RightOffset),
(int)(pDC->GetDeviceCaps(LOGPIXELSY) * BottomOffset)) ;
You will need to apply these changes to the m_rectDraw
variable for every page printed, as the rectangle gets reset
for every page loop in the MFC stock library code.
Choosing a suitable font size for printing
When printing, choosing a font size that is suitable for the resolution of the printer in the past has been a hit and miss affair.
I have had code that worked correctly on my development PC/printer setup, only to die horribly on a users PC/printer in Japan
(e.g. the text generated was 1 pixel in height). Getting consistent output across printers can be done by selecting the font size based on the resolution
reported by the printer:
CFont font ;
LOGFONT lf ;
::ZeroMemory(&lf, sizeof(LOGFONT));
lf.lfHeight = -MulDiv(12, pDC->GetDeviceCaps(LOGPIXELSY), 72);
strcpy(lf.lfFaceName, "Arial");
We set the LOGFONT::lfHeight
member to a -ve value as this will get windows to select a good width
for us which will give a nice proportional font.
If you do not know how many pages you are going to print use CPrintInfo::m_bContinuePrinting
If, when printing your document, you did not know how many pages you were going to print until you actually printed
(as calculating the actual page usage can be difficult), you can set the MFC print architecture to continue to request
pages to print until you have finished with all your output. To do this, you should not sent a maximum page in your
CView::OnPreparePrinting()
function.
There are 2 places where you can choose to end the printing:
1: In your CView::OnPrepareDC()
override
2: At the end of your CView::OnPrint()
function when you have printed the last of your output
pInfo->m_bContinuePrinting = FALSE ;
Use DIB
's instead of DDB
's
When printing bitmaps or icons to a printer DC, you should use a DIB
(Device Independant Bitmap) rather than
a DDB
(Device Dependant Bitmap). This is because printer device drivers tend not to support BitBlt
.
You can end up spending a lot of time wondering why the bitmap appears in Print Preview (because the screen DC supports BitBlt
)
and not on your printed output (becuase the printer driver does not). So when printing, convert your image to a DIB
and
use StretchDIBBits
to print the image. I have yet to find a printer where this technique would not work.
Here are some helpful functions that I have acquired from the web. I am not the original author of these, but I forget just where I got them from. But they are free source!
HANDLE ImageToDIB( CImageList* pImageList, int iImageNumber, CWnd* pWnd)
{
CBitmap bitmap;
CWindowDC dc( pWnd );
CDC memDC;
CRect rect;
CPalette pal;
IMAGEINFO imageInfo;
if (!pImageList->GetImageInfo( iImageNumber, &imageInfo ))
{
return NULL;
}
if (!memDC.CreateCompatibleDC(&dc ))
{
return NULL;
}
if (!bitmap.CreateCompatibleBitmap(&dc,
imageInfo.rcImage.bottom-imageInfo.rcImage.top,
imageInfo.rcImage.right-imageInfo.rcImage.left))
{
memDC.DeleteDC() ;
return NULL;
}
CBitmap* pOldBitmap = memDC.SelectObject( &bitmap );
if( NULL == pOldBitmap )
{
memDC.DeleteDC() ;
return NULL;
}
CPoint point( 0, 0);
UINT nStyle = ILD_NORMAL;
if(!pImageList->Draw( &memDC, iImageNumber, point, nStyle ))
{
memDC.SelectObject(pOldBitmap) ;
VERIFY(bitmap.DeleteObject()) ;
memDC.DeleteDC() ;
return NULL;
}
if( dc.GetDeviceCaps( RASTERCAPS ) & RC_PALETTE )
{
UINT nSize = sizeof(LOGPALETTE) + ( sizeof(PALETTEENTRY) * 256 );
LOGPALETTE* pLP = (LOGPALETTE*)new BYTE[nSize];
pLP->palVersion = 0x300;
pLP->palNumEntries = (unsigned short)GetSystemPaletteEntries( dc, 0, 255,
pLP->palPalEntry );
pal.CreatePalette( pLP );
delete[] pLP;
}
memDC.SelectObject( pOldBitmap );
memDC.DeleteDC() ;
HANDLE h = DDBToDIB(bitmap, BI_RGB, &pal );
VERIFY(bitmap.DeleteObject()) ;
return h ;
}
HANDLE DDBToDIB( CBitmap& bitmap, DWORD dwCompression, CPalette* pPal )
{
BITMAP bm;
BITMAPINFOHEADER bi;
LPBITMAPINFOHEADER lpbi;
DWORD dwLen;
HANDLE hDIB;
HANDLE handle;
HDC hDC;
HPALETTE hPal;
ASSERT( bitmap.GetSafeHandle() );
if( dwCompression == BI_BITFIELDS )
return NULL;
hPal = (HPALETTE) pPal->GetSafeHandle();
if (hPal==NULL)
hPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE);
bitmap.GetObject(sizeof(bm),(LPSTR)&bm);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bm.bmWidth;
bi.biHeight = bm.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = (unsigned short)(bm.bmPlanes * bm.bmBitsPixel) ;
bi.biCompression = dwCompression;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
int nColors = 0;
if(bi.biBitCount <= 8)
{
nColors = (1 << bi.biBitCount);
}
dwLen = bi.biSize + nColors * sizeof(RGBQUAD);
hDC = ::GetDC(NULL);
hPal = SelectPalette(hDC,hPal,FALSE);
RealizePalette(hDC);
hDIB = GlobalAlloc(GMEM_FIXED,dwLen);
if (!hDIB){
SelectPalette(hDC,hPal,FALSE);
::ReleaseDC(NULL,hDC);
return NULL;
}
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);
*lpbi = bi;
GetDIBits(hDC, (HBITMAP)bitmap.GetSafeHandle(), 0L, (DWORD)bi.biHeight,
(LPBYTE)NULL, (LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS);
bi = *lpbi;
if (bi.biSizeImage == 0){
bi.biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8)
* bi.biHeight;
if (dwCompression != BI_RGB)
bi.biSizeImage = (bi.biSizeImage * 3) / 2;
}
dwLen += bi.biSizeImage;
handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE) ;
if (handle != NULL)
hDIB = handle;
else
{
GlobalFree(hDIB);
SelectPalette(hDC,hPal,FALSE);
::ReleaseDC(NULL,hDC);
return NULL;
}
lpbi = (LPBITMAPINFOHEADER)hDIB;
BOOL bGotBits = GetDIBits( hDC, (HBITMAP)bitmap.GetSafeHandle(),
0L,
(DWORD)bi.biHeight,
(LPBYTE)lpbi
+ (bi.biSize + nColors * sizeof(RGBQUAD)),
(LPBITMAPINFO)lpbi,
(DWORD)DIB_RGB_COLORS);
if( !bGotBits )
{
GlobalFree(hDIB);
SelectPalette(hDC,hPal,FALSE);
::ReleaseDC(NULL,hDC);
return NULL;
}
SelectPalette(hDC,hPal,FALSE);
::ReleaseDC(NULL,hDC);
return hDIB;
}
To use the above function(s) as an example code may be:
if (iImage >= 0)
{
HANDLE hDib ;
hDib = ImageToDIB(ℑ_list, iImage, this) ;
BITMAPINFOHEADER *pBMI ;
pBMI = (BITMAPINFOHEADER*)GlobalLock(hDib) ;
int nColors = 0;
if (pBMI->biBitCount <= 8)
{
nColors = (1 << pBMI->biBitCount);
}
::StretchDIBits(dc.m_hDC,
pInfo.m_rectDraw.left,
pInfo.m_rectDraw.top + cs.cy * j,
cs.cy,
cs.cy,
0,
0,
pBMI->biWidth,
pBMI->biHeight,
(LPBYTE)pBMI + (pBMI->biSize + nColors * sizeof(RGBQUAD)),
(BITMAPINFO*)pBMI,
DIB_RGB_COLORS,
SRCCOPY);
GlobalUnlock(hDib) ;
GlobalFree(hDib) ;
}
Updates
- 25/7/2002 - Additional tips added, and some document re-wording.
I will update this document with additional tips as I acquire them, so if you have any of your own, please send them in!
Enjoy!