Click here to Skip to main content
15,886,199 members
Articles / Desktop Programming / MFC

How to Print CFormView

Rate me:
Please Sign up or sign in to vote.
4.88/5 (20 votes)
18 Aug 20035 min read 94.4K   3.2K   27   7
This article gives two different methods to print CFormView

Introduction

Ever wondered how to print a CFormView in MFC? There are several articles on this subject, but they seem to be suffering from some common mistakes. In this article, two different ways are given to do that. In the last part of the article, we will point out the strength and shortcomings of each method.

How MFC Printing Works

Before we proceed further, we need to learn how OnPrintPreview and OnPrint work in MFC. Here is the scratch of the code.

  1. Calls OnPreparePrint to prompt dialog to setup actual printer and number of pages. Usually, we will let MFC do the work for us here. But if you want to skip the dialog and setup printer DC directly, you’d better do it here or totally rewrite OnPrint/OnPrintPreview.
  2. Calls OnBeginPrint. If we can decide how many pages we need to print, it is better to set the number of pages here if possible.
  3. Print each page in a loop.
    • In the loop, Calls OnPrepareDC. If you need to setup map mode and conversion ratio between logical pixel and physical pixel, such as like one inch in screen equals one inch in printer, you'd better do it here. One important thing to bear in mind is that if you can’t decide how many pages you need to print in step 2, you can still use CPrintInfo::m_bContinuePrinting member variable to terminate printing.
    • Calls OnPrint to do actually printing

OK, that is it. It is time to roll up our hand sleeves to do some dirty work. That is what we are paid for, right?

Capturing Screen Image of CFormView

Man, what is this guy doing here, wasting my time, ha? You ask yourself. It is natural for you to think in this way with some articles already on this subject. Objection heard, but do they do it right?

Like injecting any code into a framework, first you need to know where to add your code. Such type of question is always the toughest one when programming in MFC. In this case, the question is when to grab the image. How about doing it in OnBeginPrint? Not a bad idea at first glance. Well, it turns out that there is a catch here. As MFC prompts a window to emulate Printer DC in preview mode, you could end up capturing a wrong image in this mode. It is better to do it in OnFilePrint and OnFilePrintPreview. The actual code looks like this:

C++
void CFormViewPrintView::_grapImage( ) 
{
    //Grap Image
    CPoint oldPoint = GetScrollPosition( );
    //scroll to top left corner as CFormView is a Scroll view
    CPoint pt( 0, 0 );
    this->ScrollToPosition( pt );

    CClientDC dc(this);
    CRect rect;
    this->GetClientRect( rect );
    m_dib.Attach( GDIUtil::GrabDIB( &dc, rect ) );

    ScrollToPosition( oldPoint );
}

void CFormViewPrintView::OnFilePrintPreview() 
{
    // TODO: Add your command handler code here
    _grapImage( );
    CFormView::OnFilePrintPreview() ;
}

void CFormViewPrintView::OnFilePrint() 
{
    _grapImage( );
    CFormView::OnFilePrint() ;
}

Hmm, what does the GDIUtil::GradDIB do? It grabs Bitmap from the screen and converts it to DIB. Why DIB, not Bitmap directly? A bitmap always depends on DC and screen DC is different than Printer DC. Without such conversion, we are under the mercy of Printer Driver. It may work fine in some printers, but badly on others. See Roger Allen’s article on this.

Next, we need to deal with how to preserve something the same size as displayed on screen. Ever wondered why something turns terribly small when printing? Here is the reason, let’s say the resolution in printer is 600 pixel per inch, while we usually have 96 or 120 pixel per inch in the screen. If you simply print something “the same size” in pixel, it is not hard to imagine what will happen. That is also the reason why you should change font size when printing text. What we really want is to print something the same size in inch, not pixel. “Point taken, but where to put the code of such conversion?” You ask yourself and realize this is the same old “where” question again. This can be done by overriding the method OnPrepareDC. What Microsoft really means by the name is “Setup map mode here if needed”. This is also the place to decide whether to terminate printing or not, if you haven’t figured out the number of printing pages previously. Our OnPrepareDC looks like this.

C++
void CFormViewPrintView::OnPrepareDC(CDC* pDC, 
                      CPrintInfo* pInfo /* = NULL */)
{
    // TODO: Add your specialized code here and/or call the base class
    if( pInfo )
    {
        CClientDC dc( this );
        pDC->SetMapMode(MM_ANISOTROPIC);

        CSize sz( dc.GetDeviceCaps(LOGPIXELSX), 
                    dc.GetDeviceCaps(LOGPIXELSY) );
        pDC->SetWindowExt( sz );
        sz = CSize( pDC->GetDeviceCaps(LOGPIXELSX),
                        pDC->GetDeviceCaps(LOGPIXELSY) );
        pDC->SetViewportExt( sz );
    }
}

What does this code mean? It means one inch in screen, dc in this case, equals one inch in printer (could be pseudo one) and we don’t care about actual pixel size varies, say 120 ppi in screen vs 600 ppi in printer.

Last, the actual printing.

C++
void CFormViewPrintView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
    // TODO: add customized printing code here
    if( pInfo == NULL )
        return;

    if( m_dib.GetHandle( ) == NULL )
        return;
    {
        //Call GlobalLock in constructor, call Unlock when exists the block
        GLock lock( m_dib );
        BITMAPINFOHEADER *pBMI = (BITMAPINFOHEADER*)(LPVOID)lock;

        int nColors = 0;
        if( pBMI->biBitCount <= 8 )
            nColors = ( 1<< pBMI->biBitCount );

        ::StretchDIBits( pDC->GetSafeHdc( ),
        pInfo->m_rectDraw.left, 
        pInfo->m_rectDraw.top,
        pBMI->biWidth,
        pBMI->biHeight,
                0, 
                0, 
                pBMI->biWidth,
                pBMI->biHeight,
                (LPBYTE)pBMI + (pBMI->biSize + nColors * sizeof(RGBQUAD)),
                (BITMAPINFO*)pBMI,
                DIB_RGB_COLORS, 
                SRCCOPY);
    }
}

One thing to mention is that GLock in GUtil follows the same idea as AutoPtr in STD. I have no idea why Microsoft does the right thing in CClientDC and CPaintDC, while turning blind when dealing something like GlobalLock/Unlock or the notorious SelectObject. How many times have we scratched our head to detect GDI object resource leak, only to find out that we select something in, but forget to select it out.

Another Way WM_PRINT Message

Ever heard of WM_PRINT message? It is not even in Visual C++ class wizard, but it seems promising for everything we need for printing CFormView. Here is another way to print CFormView:

C++
void CFormViewPrint2View::_print( )
{
    CRect rect;
    this->GetClientRect( rect );
    CDC memDC;

    CClientDC dc( this );
    memDC.CreateCompatibleDC( &dc );

    CBitmap bitmap;
    bitmap.CreateCompatibleBitmap( &dc, rect.Width(), rect.Height() );
    {
        //This will force bitmap selected out of DC when exit this block
        LocalGDI local( &memDC, &bitmap );
        this->Print( &memDC, PRF_ERASEBKGND|PRF_CLIENT|PRF_CHILDREN );
    }
    m_dib.Attach( GDIUtil::DDBToDIB( bitmap ) );
}

void CFormViewPrint2View::OnFilePrintPreview() 
{
    // TODO: Add your command handler code here
    _print( );
    CFormView::OnFilePrintPreview( );
}

void CFormViewPrint2View::OnFilePrint() 
{
    // TODO: Add your command handler code here
    _print( );
    CFormView::OnFilePrint( );
}

Conclusion

So, what is the strength and weakness of each method? The first one doesn’t care about how many individual child controls you have and how to print each of them on Printer, but it can only print visual part of the screen. While the second one seems much better and cleaner than the first one, it even allows you to print all client area without displaying them on the screen. Unfortunately, there is a catch for it too. Some sub-classed Windows controls and user custom controls may forget to process WM_PRINT message at all, which is amazingly easy to implement if you can process WM_PAINT message.

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.


Written By
Web Developer
United States United States
I have coded in MFC for seven years if adding them together, about four years in Java, five years on Unix side.

Comments and Discussions

 
Generalproblem if I have a CView that draws opengl stuff... Pin
mimosa21-Oct-08 11:37
mimosa21-Oct-08 11:37 
QuestionWhat is the maximum count of windows in system? Pin
yph200401074-Dec-07 21:01
yph200401074-Dec-07 21:01 
Generalprint all controls Pin
Chiara Corridori1-May-06 2:27
Chiara Corridori1-May-06 2:27 
Generalprinting through RS232 port Pin
fadh25-Apr-05 16:18
fadh25-Apr-05 16:18 
QuestionSend to a file instead of a printer? Pin
tjm52713-Sep-04 11:59
tjm52713-Sep-04 11:59 
QuestionHow to print CFormView Pin
sdfdsfa5-Nov-03 14:59
sdfdsfa5-Nov-03 14:59 
GeneralWM_PRINT sample get here Pin
TW20-Aug-03 23:13
TW20-Aug-03 23:13 

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.