Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Printing tips and tricks from the trenches

0.00/5 (No votes)
25 Jul 2002 1  
A selection of printing tips and tricks that can help make printing in an MFC application easier

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) ;
        
    ...
    
    // when finished with the DC, you should delete it
    
    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 ;       // 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)) ;

    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));
    
    // 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....

    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!

    // 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;
    }

    To use the above function(s) as an example code may be:

    if (iImage >= 0)
        {
        HANDLE hDib ;
        hDib = ImageToDIB(&#8465;_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) ;
        }

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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here