Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / MFC
Article

Printing tips and tricks from the trenches

Rate me:
Please Sign up or sign in to vote.
4.75/5 (25 votes)
25 Jul 2002CPOL4 min read 271.9K   104   73
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(ℑ_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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Sirius Analytical Instruments
United Kingdom United Kingdom
A research and development programmer working for a pharmaceutical instrument company for the past 17 years.

I am one of those lucky people who enjoys his work and spends more time than he should either doing work or reseaching new stuff. I can also be found on playing DDO on the Cannith server (Send a tell to "Maetrim" who is my current main)

I am also a keep fit fanatic, doing cross country running and am seriously into [url]http://www.ryushinkan.co.uk/[/url] Karate at this time of my life, training from 4-6 times a week and recently achieved my 1st Dan after 6 years.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey18-Feb-12 0:09
professionalManoj Kumar Choubey18-Feb-12 0:09 
GeneralOne more tip if I may Pin
Announ10-Jun-10 20:42
Announ10-Jun-10 20:42 
GeneralYou are the MAN!! Pin
JohnChurchill12-Dec-08 2:37
JohnChurchill12-Dec-08 2:37 
QuestionPrinting challenge Pin
ProbeVision22-Jan-08 1:42
ProbeVision22-Jan-08 1:42 
GeneralLIST INSTALLED PRINTERS IN COMOBOX Pin
Hilal Bhat31-Oct-07 1:29
Hilal Bhat31-Oct-07 1:29 
Question4 invoices on one A4 paper - Dot-matrix printer ? Pin
ana_v12312-Jul-07 19:43
ana_v12312-Jul-07 19:43 
Answerwhy following code does not work on WinXP ? Pin
ana_v12317-Jul-07 8:20
ana_v12317-Jul-07 8:20 
QuestionPrint time pagination Pin
NILANKARAJA13-Mar-07 19:48
NILANKARAJA13-Mar-07 19:48 
AnswerRe: Print time pagination Pin
Nelek7-May-07 0:26
protectorNelek7-May-07 0:26 
Questionbut how copy cdc to a dcprint??? Pin
linweishang28-Dec-06 1:36
linweishang28-Dec-06 1:36 
GeneralThere is an Error in code of ImageToDIB + Response to reverse color Pin
zaac22-Aug-06 23:11
zaac22-Aug-06 23:11 
GeneralPrint to PDF-> Output file name spooling problem Pin
jayart27-Mar-06 2:21
jayart27-Mar-06 2:21 
QuestionProblem in printing arabic text Pin
HatemElbehairy22-Feb-06 5:17
HatemElbehairy22-Feb-06 5:17 
GeneralProblem with paper size Pin
Alberto_Canabal16-Jan-06 5:30
Alberto_Canabal16-Jan-06 5:30 
QuestionHow to set DEVMODE Pin
qqz9-Dec-05 8:07
qqz9-Dec-05 8:07 
QuestionHow Many Pages Printed Pin
ThangaDharma5-Oct-05 6:45
ThangaDharma5-Oct-05 6:45 
GeneralStrange behavior in some printer Pin
rajas16-Aug-05 11:43
rajas16-Aug-05 11:43 
GeneralStretchBlt code you gave not working for me ! :-( Pin
Vinutha Shantiraj21-Jul-05 23:07
Vinutha Shantiraj21-Jul-05 23:07 
GeneralRe: StretchBlt code you gave not working for me ! :-( Pin
ckandoth29-Nov-05 19:29
ckandoth29-Nov-05 19:29 
AnswerRe: StretchBlt code you gave not working for me ! :-( Pin
DKreusel@taylor-hobson.de4-Jul-07 3:56
DKreusel@taylor-hobson.de4-Jul-07 3:56 
GeneralPrinting Icons Pin
Crercio O. Silva16-Mar-05 3:36
Crercio O. Silva16-Mar-05 3:36 
QuestionPostscipt forms in win32 GDI ???????? Pin
ChrisRogers12-Dec-04 13:53
ChrisRogers12-Dec-04 13:53 
GeneralDisabling Print Dialog Pin
atin wadehra31-Oct-04 23:26
atin wadehra31-Oct-04 23:26 
GeneralFind the no of pages printed while a document is print job Pin
Anonymous28-Oct-04 1:00
Anonymous28-Oct-04 1:00 
GeneralInterception of an EMF. Pin
FastDolphin19-Oct-04 6:35
FastDolphin19-Oct-04 6:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.