天天看點

如何在MFC中列印CFormView?

MFC的列印是如何工作的?

Before we go 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 page 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 on step 2, you can still use​

    ​CPrintInfo::m_bContinuePrinting​

    ​ member variable to terminate printing.
  • Calls​

    ​OnPrint​

    ​ to do actually printing

列印CFormView有兩種方法:

1.Capturing screen image of CFormView.

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 bad idea at first glance. Well, it turns out there is catch here. As MFC prompts a window to emulate Printer DC in preview mode, you could end up capturing wrong image in this mode. It is better to do it in ​

​OnFilePrint​

​​ and ​

​OnFilePrintPreview​

​. The actual code looks like this:

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 printer, but badly on the other. Seen 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.

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.

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 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 finding out that we select something in, but forget to select it out.

2.Another way WM_PRINT message

Ever heard of ​​

​WM_PRINT​

​​ message? It is not even in Visual C++ class wizard, but it seems promising everything we need for printing ​

​CFormView​

​​. Here is another way to print ​

​CFormView​

​:

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( );
}      

結論:

繼續閱讀