With The Case Of  Release The Power OF  Visual C++ !   HomeProducts | PurchaseSupport | Downloads  
XD++ Library
TFC Library
Free Products
Technical Support

Get Ready to Unleash the Power of UCanCode .NET

UCanCode Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!

"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"

MFC GDI Tutorials: GDI Printing, GDI+ Printing




This articles gives some hints on printing figure made with GDI+. As anybody knows, printing is one of the most mysterious feature in MFC (this is my point of view). It is not well (at all) documented and in fact, the best examples can be found in Printing section.

Since GDI+ is a new technology, it brings new problems when trying to print.

In the following, I will try to give some hints encountered when trying to print GDI+ stuff. I will suppose that you have some basic knowledge about printing, especially that you have read one of the following articles, so I won't have to discuss about getting a printer DC working.

In the following, dc denotes the printer dc and graphics denotes the GDI+ graphic context:

    CDC dc;
    Graphics graphics;

Setting the mapping modes

The mapping modes of dc and graphics must be tuned together:


With those setting, each logical unit is converted to 1 device unit (MM_TEXT) and one device unit is 1/300 inch (UnitDocument). So we get 300dpi printing, great.

What about other DPIs?

Gulp, we got it working for 300dpi, but what about 600 dpi? or 1200 ?

After a few try and error, I figured out we had to do the dirty job ourselves, that is check for the dpi of printer and scale accordingly the graphic:

  1. Get the dpi ratio dpiRatio:

        CPrintDialog MyPrintDialog;
         // the dpi of printer is enclosed in DEVMODE structure,
         DEVMODE* pDev=MyPrintDialog.GetDevMode();
        // getting dpi ratio between 300dpi and current printer dpi
        double dpiRatio=300.0/pDev->dmPrintQuality;
        // deallocating memory for pDev
  2. Setting page scale in to graphics


That ugly hack should do the work. Of course, any nicer method is welcome :-)

What about text?

Getting font size

Unfortunately, scaling the graphic is not sufficient and Dpi scaling has to be taken in account when computing the font size!

Here's a modification of the CreateFontSize of Barnhart article. As you see, the user has to pass the dpiRatio to scale the font accordingly:

int CreateFontSize(HDC hDC, int points, double dpiRatio)
	// This will calculate the font size for the printer that is specified
	// by a point size.
	// if points is:
	//  (-) negative uses height as value for Net Font Height (ie. point size)
	//	(+) positive height is Total Height plus Leading Height!
	POINT size;
	int logPixelsY=::GetDeviceCaps(hDC, LOGPIXELSY);
	size.x = size.y = MulDiv(points, logPixelsY, 72);

	// here we scale the font...
	return (float)floor(size.y*dpiRatio);

Create font for printing

When creating a font, use the following unit:

Unit fontUnit = m_pGraphics->GetPageUnit();
// if fontUnit is UnitDisplay, then specify UnitPixel, 
// otherwise you'll get a "InvalidParameter" from GDI+
if (fontUnit == UnitDisplay)
fontUnit = UnitPixel;
// classical constructor use, lfHeight is the font height
Font font(&fontFamily, CreateFontSize(hDC, 
          lfHeight, dpiRatio), FontStyleRegular, fontUnit);
Using the default properties supplied for printer output does not give consistent results. The printable region and the pixel density cause variations. If you are outputting pages for a formal report, consistent margins and font size are often required. The following functions provide a method for obtaining consistent output between printers.

Included functions are:

  1. UserPage which returns a CRect which defines a consistent printable area for each printer (Your margins must be within the printable region of all printers of course!)
  2. CreateFontSize which returns a CSize which defines the font attribute with respect to the desired point size and printer characteristics.

The remaining code shows sample usage of these functions.

//Input: desired margin
//Output: CRect to use for printing area
CRect CChildView::UserPage(CDC * pDC, float margin)
    // This function returns the area in device units to be used to
    // prints a page with a true boarder of "margin".
    // You could use individual margins for each edge
    // and apply below as needed.
    // Set Map Mode - We do not want device units
    // due to lack of consistency.
    // If you do not use TWIPS you will have to change
    // the scaling factor below.
    int OriginalMapMode = pDC->SetMapMode(MM_TWIPS);

    // Variable needed to store printer info.
    CSize PrintOffset,Physical,Printable;

    // This gets the Physical size of the page in Device Units = pDC->GetDeviceCaps(PHYSICALWIDTH); = pDC->GetDeviceCaps(PHYSICALHEIGHT);
    // convert to logical

    // This gets the offset of the printable area from the
    // top corner of the page in Device Units = pDC->GetDeviceCaps(PHYSICALOFFSETX); = pDC->GetDeviceCaps(PHYSICALOFFSETY);
    // convert to logical

    // Set Page scale to TWIPS, Which is 1440 per inch,
    // Zero/Zero is the upper left corner
    // Get Printable Page Size (This is in MM!) so convert to twips. =  (int)((float)pDC->GetDeviceCaps(HORZSIZE)*56.69); = (int)((float)pDC->GetDeviceCaps(VERTSIZE)*56.69);

    // Positive X -> RIGHT
    // Positive Y -> UP
    // Ref Zero is upper left corner
    int inch = 1440; // Scaling Factor Inches to TWIPS
    int Dx1, Dx2, Dy1, Dy2; // Distance printable area is from edge of paper
    Dx1 =;
    Dy1 =;
    // calculate remaining borders
    Dy2 =;
    Dx2 =;
    // Define the User Area's location
    CRect PageArea;
    PageArea.left = (long)(margin*inch-Dx1);
    PageArea.right = (long)(*inch+Dx2); = (int)-(margin*inch-Dy1); // My scale is inverted for y
    PageArea.bottom = (int)-(*inch+Dy2);
    // now put back to device units to return to the program.
    // return
    return PageArea;

 Input: pointer to device context for printer
// Input: desired point size of font (in points).
// Output: integer height to send to CreateFont function.
int CChildView::CreateFontSize(CDC *pdc, int points)
// This will calculate the font size for the printer that is specified
// by a point size.
// if points is:
// (-) negative uses height as value for Net Font Height
// (ie. point size)
// (+) positive height is Total Height plus Leading Height!
CSize size;
int perinch = pdc->GetDeviceCaps(LOGPIXELSY); = = (perinch*points)/72;

To use CreateFontSize, just insert the function call into the CreateFont function:

    BaseFont.CreateFont( -CreateFontSize(pdc,11), 0, 0, 0, FW_MEDIUM,
        DEFAULT_QUALITY, DEFAULT_PITCH , "Courier New" );

The UserPage function can be used internal to your OnPrint function. Where you call it, use the returned area rather than the region found from the pDC's GetDeviceCaps function. (Usually sent in the PrintInfo data.) Or you can call it to set the region in to be passed.

void CChildView::PrintSetup(int item)
// Create Standard windows dialog.
BOOL bStdSetUpDlg = TRUE;
// See PRINTDLG for flags to set defaults in dialog.
// Parent (may be NULL)
CWnd *pParent = this;
CPrintDialog MyPrintDlg(bStdSetUpDlg,dwFlags,pParent);
// Print Info
CPrintInfo MyPrintInfo;
// first link with dialog so data is shared
// Your input into min and max pages is now shared.
MyPrintInfo.m_pPD = &MyPrintDlg;
// Get Users Answer;
int MyAnswer;
MyAnswer = MyPrintDlg.DoModal();
// Allow the user to cancel
if(MyAnswer==IDCANCEL) return;
// Get the mode the printer is in from the Print Dialog.
// This memory block must be unlocked later.
DEVMODE *MyPrintMode;
MyPrintMode = MyPrintDlg.GetDevMode();
// Create our Printer Context
CDC MyPrintDC;
MyPrintDC.CreateDC(MyPrintDlg.GetDriverName(), // Ignored for Printer DC's
MyPrintDlg.GetDeviceName(), // The only required item for Printer DC's
MyPrintDlg.GetPortName(), // Ignored for Printer DC's
MyPrintMode); // Optional Item for Printer DC's
// Start the Document for our document
CString DocName;
// Start the document
int iErr = MyPrintDC.StartDoc(&MyDocInfo);
if(iErr < 0)
//success returns positive value
GlobalUnlock(MyPrintMode); // Release the print mode.
// success so set flag to printing
// Most programs us the device's printable region found with
// MyPrintDC.GetDevicecaps(****) functions.
// However this is not consistent between printers so -->
// The UserPage functions calculates what margins
// to specify so we have the
// actual distance from the edge of the page
// to be consistent between printers.
CRect MyArea;
// fixed margin in inches (you can change this)
MyArea = UserPage(&MyPrintDC, 0.9f);
// We are now into personal preferences based on your program needs.
// We can call OnBeginPrinting and OnEndPrinting functions
// to initialize and clean up
// and loop through calls to OnPrint
// (calling Startpage and EndPage functions)
// or as I have done here->
// Call the StartPage the first time EndPage at the end
// with the print fnuction handling the begin
// and end when needed internally.
// Start the page. (This allways sets the DC to device units!) 
// Set mode.
// We are now ready to print our data. Switch to the options allowed.
// For our usage we will end and restart
// each page in the functions called
// based on the location of the current print location on the page.
// Internal to the fucntions we need to call:
// pdc->EndPage();
// pdc->StartPage(); // Returns in Device units
// pdc->SetMapMode(MM_LOENGLISH); // Reset to our desired mode
// Reset position to draw, etc....
// as needed.
// We are all done. Clean up
// end last page
// end the document
// Release the device context
GlobalUnlock(MyPrintMode); // Release the print mode.

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->, 
"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(VERTRES)) ;
This gets the printable area of a printers page.

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!
(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 ;

::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, 
        // 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;
    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

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

    // Allocate enough memory to hold bitmapinfoheader and color table
    hDIB = GlobalAlloc(GMEM_FIXED,dwLen);

    if (!hDIB){
        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,

    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;

        // Reselect the original palette
        return NULL;

    // Get the bitmap bits

    // 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 )
        return NULL;

    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
        pBMI = (BITMAPINFOHEADER*)GlobalLock(hDib) ;
        int nColors = 0;
        if (pBMI->biBitCount <= 8)
            nColors = (1 << pBMI->biBitCount);
        // print the correct image
                   + * j, 
                            (LPBYTE)pBMI + (pBMI->biSize + nColors * sizeof(RGBQUAD)),
        // free resources
        GlobalUnlock(hDib) ;
        GlobalFree(hDib) ;



Copyright ?1998-2007 UCanCode.Net Software , all rights reserved.
Other product and company names herein may be the trademarks of their respective owners.

Please direct your questions or comments to