从实战中获取的打印技巧和窍门






4.75/5 (24投票s)
精选的打印技巧和窍门,可帮助简化MFC应用程序中的打印操作
引言
从我的许多其他文章来看,我似乎成了CodeProject上的“打印”大师。因此,我想收集一些我通过反复试验而积累的打印技巧和窍门。
- 仅在
CPrintInfo::m_rectDraw
区域内打印
您的打印代码不应假设应在页面上的哪个位置打印,而应正确使用CPrintInfo::m_rectDraw
变量。这可以确保您不会覆盖可能在主OnPrint
过程中之外打印的页边距/页眉/页脚。
pDC->TextOut(pInfo->m_rectDraw.left, pInfo->m_rectDraw.top,
"Only draw inside the reported m_rectDraw area") ;
当在CView
派生类中调用OnPreparePrinting()
时,通常需要设置打印文档所需的输出页数,除非您使用的是CPrintInfo::m_bContinuePrinting
方法。但是,如果没有有关打印机分辨率或页面大小的信息,这可能很困难。因此,此时需要获取将要使用的打印机DC对象。由于MFC打印体系结构在调用OnBeginPrinting()
函数之前不会创建它,因此您必须自己创建一个,并在计算所需的页数后将其释放。要创建这样的打印机DC,可以使用此代码
CDC dc ;
AfxGetApp()->CreatePrinterDC(dc) ;
...
// when finished with the DC, you should delete it
dc.DeleteDC() ;
这将为应用程序选择的默认打印机创建打印机DC。要在代码中切换到其他打印机,请参阅我的文章在MFC应用程序中以编程方式设置默认打印机。
打印机上页面的可打印区域通常包含在CPrintInfo::m_rectDraw
成员变量中。CPrintInfo
对象会传递到您重写的CView
虚拟函数中。但在某些情况下,例如在OnPreparePrinting()
,OnBeginPrinting()
中,此成员变量尚未初始化。因此,您必须自己进行初始化。
pInfo->m_rectDraw.SetRect(0, 0, pDC->GetDeviceCaps(HORZRES), pDC->GetDeviceCaps(VERTRES)) ;
这会获取打印机页面可打印区域的大小。
在许多情况下,您可能希望在页面周围设置用户可编程的边距,以免在信头纸上覆盖公司徽标等,因此您可以设置一个用户可编程的边距范围(以英寸
为单位)。然后,您可以将其转换为设备单位,并通过更改CPrintInfo::m_rectDraw
变量的尺寸来保留页面上的空间。例如
double LeftOffset = 0.5 ; // in imperial inches! double TopOffset = 0.5 ; // in imperial inches! double RightOffset = 0.5 ; // in imperial inches! double BottomOffset = 0.5 ; // in imperial inches! pInfo->m_rectDraw.DeflateRect( (int)(pDC->GetDeviceCaps(LOGPIXELSX) * LeftOffset), (int)(pDC->GetDeviceCaps(LOGPIXELSY) * TopOffset), (int)(pDC->GetDeviceCaps(LOGPIXELSX) * RightOffset), (int)(pDC->GetDeviceCaps(LOGPIXELSY) * BottomOffset)) ;
您需要将这些更改应用于m_rectDraw
变量以打印的每一页,因为在MFC库代码的每个页面循环中都会重置该矩形。
在打印时,过去选择适合打印机分辨率的字体大小一直是一种碰运气的事情。我编写的代码在我自己的开发PC/打印机设置上能正常工作,但到了用户在日本的PC/打印机上就彻底失败(例如,生成的文本高度只有1像素)。通过根据打印机报告的分辨率选择字体大小,可以实现跨打印机的一致输出。
CFont font ; LOGFONT lf ; ::ZeroMemory(&lf, sizeof(LOGFONT)); // This aims to get a 12-point size font regardless of the // printer resolution lf.lfHeight = -MulDiv(12, pDC->GetDeviceCaps(LOGPIXELSY), 72); strcpy(lf.lfFaceName, "Arial"); // with face name "Arial". // make use of the font....
我们将LOGFONT::lfHeight
成员设置为负值,这样Windows就会为我们选择一个合适的宽度,从而得到一种漂亮的比例字体。
CPrintInfo::m_bContinuePrinting
如果在打印文档时,直到实际打印时才知道要打印多少页(因为计算实际页面使用量可能很困难),您可以设置MFC打印体系结构以继续请求打印页面,直到您完成所有输出为止。要做到这一点,您不应在CView::OnPreparePrinting()
函数中设置最大页数。
您可以在以下两个位置选择结束打印:
1:在您的CView::OnPrepareDC()
覆盖中
2:在您的CView::OnPrint()
函数的末尾,当您已打印完所有输出后
pInfo->m_bContinuePrinting = FALSE ;
DIB
而不是DDB
在将位图或图标打印到打印机DC时,应使用DIB
(设备无关位图)而不是DDB
(设备相关位图)。这是因为打印机设备驱动程序通常不支持BitBlt
。您可能会花费大量时间思考为什么位图会在打印预览中显示(因为屏幕DC支持BitBlt
),但在打印输出中却不显示(因为打印机驱动程序不支持)。因此,在打印时,请将图像转换为DIB
并使用StretchDIBBits
打印图像。我还没有遇到过这种技术无效的打印机。
以下是一些我从网上获得的有用函数。我不是这些函数的原始作者,但我记不清是从哪里得到的了。但它们是免费的!
// this procedure extracts a single image from an image list into a DIB HANDLE ImageToDIB( CImageList* pImageList, int iImageNumber, CWnd* pWnd) { // Local Variables CBitmap bitmap; CWindowDC dc( pWnd ); CDC memDC; CRect rect; CPalette pal; IMAGEINFO imageInfo; if (!pImageList->GetImageInfo( iImageNumber, &imageInfo )) { // Getting of the Imageinfos failed return NULL; } // Create compatible stuff and select Bitmap if (!memDC.CreateCompatibleDC(&dc )) { // Create failed return NULL; } if (!bitmap.CreateCompatibleBitmap(&dc, imageInfo.rcImage.bottom-imageInfo.rcImage.top, imageInfo.rcImage.right-imageInfo.rcImage.left)) { // Create failed memDC.DeleteDC() ; return NULL; } CBitmap* pOldBitmap = memDC.SelectObject( &bitmap ); if( NULL == pOldBitmap ) { // Select failed memDC.DeleteDC() ; return NULL; } // Local Variables for Draw CPoint point( 0, 0); UINT nStyle = ILD_NORMAL; // Draw Image to the compatible DC if(!pImageList->Draw( &memDC, iImageNumber, point, nStyle )) { // Drawing of the Image failed memDC.SelectObject(pOldBitmap) ; VERIFY(bitmap.DeleteObject()) ; memDC.DeleteDC() ; return NULL; } // Create logical palette if device support a palette 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 ); // Create the palette pal.CreatePalette( pLP ); // Free memory delete[] pLP; } memDC.SelectObject( pOldBitmap ); memDC.DeleteDC() ; // Convert the bitmap to a DIB HANDLE h = DDBToDIB(bitmap, BI_RGB, &pal ); VERIFY(bitmap.DeleteObject()) ; return h ; } // DDBToDIB - Creates a DIB from a DDB // bitmap - Device dependent bitmap // dwCompression - Type of compression - see BITMAPINFOHEADER // pPal - Logical palette 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() ); // The function has no arg for bitfields if( dwCompression == BI_BITFIELDS ) return NULL; // If a palette has not been supplied use defaul palette hPal = (HPALETTE) pPal->GetSafeHandle(); if (hPal==NULL) hPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE); // Get bitmap information bitmap.GetObject(sizeof(bm),(LPSTR)&bm); // Initialize the bitmapinfoheader 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; // Compute the size of the infoheader and the color table int nColors = 0; if(bi.biBitCount <= 8) { nColors = (1 << bi.biBitCount); } dwLen = bi.biSize + nColors * sizeof(RGBQUAD); // We need a device context to get the DIB from hDC = ::GetDC(NULL); hPal = SelectPalette(hDC,hPal,FALSE); RealizePalette(hDC); // Allocate enough memory to hold bitmapinfoheader and color table hDIB = GlobalAlloc(GMEM_FIXED,dwLen); if (!hDIB){ SelectPalette(hDC,hPal,FALSE); ::ReleaseDC(NULL,hDC); return NULL; } lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB); *lpbi = bi; // Call GetDIBits with a NULL lpBits param, so the device driver // will calculate the biSizeImage field GetDIBits(hDC, (HBITMAP)bitmap.GetSafeHandle(), 0L, (DWORD)bi.biHeight, (LPBYTE)NULL, (LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS); bi = *lpbi; // If the driver did not fill in the biSizeImage field, then compute it // Each scan line of the image is aligned on a DWORD (32bit) boundary if (bi.biSizeImage == 0){ bi.biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight; // If a compression scheme is used the result may infact be larger // Increase the size to account for this. if (dwCompression != BI_RGB) bi.biSizeImage = (bi.biSizeImage * 3) / 2; } // Realloc the buffer so that it can hold all the bits dwLen += bi.biSizeImage; handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE) ; if (handle != NULL) hDIB = handle; else { GlobalFree(hDIB); // Reselect the original palette SelectPalette(hDC,hPal,FALSE); ::ReleaseDC(NULL,hDC); return NULL; } // Get the bitmap bits lpbi = (LPBITMAPINFOHEADER)hDIB; // FINALLY get the DIB BOOL bGotBits = GetDIBits( hDC, (HBITMAP)bitmap.GetSafeHandle(), 0L, // Start scan line (DWORD)bi.biHeight, // # of scan lines (LPBYTE)lpbi // address for bitmap bits + (bi.biSize + nColors * sizeof(RGBQUAD)), (LPBITMAPINFO)lpbi, // address of bitmapinfo (DWORD)DIB_RGB_COLORS); // Use RGB for color table if( !bGotBits ) { GlobalFree(hDIB); SelectPalette(hDC,hPal,FALSE); ::ReleaseDC(NULL,hDC); return NULL; } SelectPalette(hDC,hPal,FALSE); ::ReleaseDC(NULL,hDC); return hDIB; }
使用上述函数作为示例代码可以是
if (iImage >= 0) { HANDLE hDib ; hDib = ImageToDIB(ℑ_list, iImage, this) ; // this is a dialog window in this example BITMAPINFOHEADER *pBMI ; pBMI = (BITMAPINFOHEADER*)GlobalLock(hDib) ; int nColors = 0; if (pBMI->biBitCount <= 8) { nColors = (1 << pBMI->biBitCount); } // print the correct image ::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); // free resources GlobalUnlock(hDib) ; GlobalFree(hDib) ; }
更新
- 2002年7月25日 - 添加了其他技巧,并对文档进行了一些改写。
我会在我获得更多技巧后更新本文档,如果您有自己的技巧,请发送给我!
尽情享用!