A Bare-Bones Printing Application
The EZPrint application shown in Figure 13-2 demonstrates the minimum amount of work a document/view application must do to support printing and print previewing.
Figure 13-2. The EZPrint application displaying a print preview.
An EZPrint "document" contains a blue circle 10 centimeters (1,000 units in the MM_LOMETRIC mapping mode) in diameter with a yellow interior. The application's File menu contains just four items: Print, Print Preview, Print Setup, and Exit. The Print and Print Preview commands are mapped to CView::OnFilePrint and CView::OnFilePrintPreview in CEZPrintView's message map, and the Print Setup command is mapped to CWinApp::OnFilePrintSetup in CEZPrintApp's message map. AppWizard performed all the message mapping. The Print command displays a Print dialog box in which the user can specify printing options such as the desired printer, the print range, and the number of copies. Print Preview puts the application in print preview mode. Print Setup displays a Print Setup dialog box. You can use the Print Setup dialog box to choose a printer, select a paper size, and specify the page orientation—portrait or landscape.
I used AppWizard to create the EZPrint project. In the Step 4 dialog box (shown in Figure 13-3), I checked the Printing And Print Preview box to add printing support. Checking this box prompts AppWizard to make three modifications to the code that it generates:
Add Print, Print Preview, and Print Setup commands to the File menu.
Modify the message map to connect the Print, Print Preview, and Print Setup commands to MFC-provided command handlers.
Override OnPreparePrinting, OnBeginPrinting, and OnEndPrinting in the view class.
AppWizard's OnPreparePrinting function includes a call to DoPreparePrinting. Its OnBeginPrinting and OnEndPrinting functions do nothing, so you can delete them if you don't use them. I left them in, but EZPrint would work just as well without them. All of EZPrint's printing code is found in the view class, whose source code is reproduced in Figure 13-4.
Figure 13-3. Using AppWizard to add printing and print previewing support.
There's not a lot to say about EZPrint's printing and print previewing capabilities other than that MFC does the bulk of the work. CEZPrintView::OnDraw renders all the output, regardless of whether that output is destined for the screen, a printer, or a print preview window. So that the circle will have the same proportions regardless of where it is drawn, OnDraw does all of its drawing using the MM_LOMETRIC mapping mode. That's important, because pixel-per-inch values for screens and printers are rarely the same. If you drew to the screen and the printer in the MM_TEXT mapping mode, the circle would be a lot smaller on a 600 dpi printer than it would be on the screen. To get WYSIWYG results, you'd have to scale the circle's height and width manually during printing and print previewing using ratios derived from pixel-per-inch counts for the screen and printer. Using a mapping mode in which logical units scale to physical distances rather than pixel counts allows the GDI to do the scaling and ensures that OnDraw can produce consistent results no matter where the output is rendered.
Figure 13-4. The EZPrint application.
EZPrintView.h// EZPrintView.h : interface of the CEZPrintView class
//
///////////////////////////////////////////////////////////////////////////
#if !defined(
AFX_EZPRINTVIEW_H__3A83FDED_A3E6_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_EZPRINTVIEW_H__3A83FDED_A3E6_11D2_8E53_006008A82731__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class CEZPrintView : public CView
{
protected: // create from serialization only
CEZPrintView();
DECLARE_DYNCREATE(CEZPrintView)
// Attributes
public:
CEZPrintDoc* GetDocument();
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CEZPrintView)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CEZPrintView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
//{{AFX_MSG(CEZPrintView)
// NOTE - the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
#ifndef _DEBUG // debug version in EZPrintView.cpp
inline CEZPrintDoc* CEZPrintView::GetDocument()
{ return (CEZPrintDoc*)m_pDocument; }
#endif
///////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations
// immediately before the previous line.
#endif
// !defined(
// AFX_EZPRINTVIEW_H__3A83FDED_A3E6_11D2_8E53_006008A82731__INCLUDED_)
EZPrintView.cpp// EZPrintView.cpp : implementation of the CEZPrintView class
//
#include "stdafx.h"
#include "EZPrint.h"
#include "EZPrintDoc.h"
#include "EZPrintView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///////////////////////////////////////////////////////////////////////////
// CEZPrintView
IMPLEMENT_DYNCREATE(CEZPrintView, CView)
BEGIN_MESSAGE_MAP(CEZPrintView, CView)
//{{AFX_MSG_MAP(CEZPrintView)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////////
// CEZPrintView construction/destruction
CEZPrintView::CEZPrintView()
{
}
CEZPrintView::~CEZPrintView()
{
}
BOOL CEZPrintView::PreCreateWindow(CREATESTRUCT& cs)
{
return CView::PreCreateWindow(cs);
}
///////////////////////////////////////////////////////////////////////////
// CEZPrintView drawing
void CEZPrintView::OnDraw(CDC* pDC)
{
CPen pen (PS_SOLID, 50, RGB (0, 0, 255));
CBrush brush (RGB (255, 255, 0));
pDC->SetMapMode (MM_LOMETRIC);
CPen* pOldPen = pDC->SelectObject (&pen);
CBrush* pOldBrush = pDC->SelectObject (&brush);
pDC->Ellipse (100, -100, 1100, -1100);
pDC->SelectObject (pOldBrush);
pDC->SelectObject (pOldPen);
}
///////////////////////////////////////////////////////////////////////////
// CEZPrintView printing
BOOL CEZPrintView::OnPreparePrinting(CPrintInfo* pInfo)
{
return DoPreparePrinting(pInfo);
}
void CEZPrintView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add extra initialization before printing
}
void CEZPrintView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add cleanup after printing
}
///////////////////////////////////////////////////////////////////////////
// CEZPrintView diagnostics
#ifdef _DEBUG
void CEZPrintView::AssertValid() const
{
CView::AssertValid();
}
void CEZPrintView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CEZPrintDoc* CEZPrintView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEZPrintDoc)));
return (CEZPrintDoc*)m_pDocument;
}
#endif //_DEBUG
///////////////////////////////////////////////////////////////////////////
// CEZPrintView message handlers
Black-and-White Print Previews
MFC's print preview support isn't perfect. EZPrint's preview page shows the circle in full-blown color even if the only printer attached to your PC is a black-and-white model. (Naturally, the circle will be printed in color if you print it on a color printer.) You can add a nice touch to your print preview code by doing your rendering in shades of gray if both the following conditions are true when OnPrint or OnDraw is called:
pInfo->m_bPreview is nonzero (OnPrint) or pDC->m_hDC is not equal to pDC->m_hAttribDC(OnDraw).
pDC->GetDeviceCaps (NUMCOLORS) returns 2, indicating that the printer is a monochrome device.
You can convert RGB color values into shades of gray with this formula:
r/g/b = (red * 0.30) + (green * 0.59) + (blue * 0.11)
The following statement creates a gray brush that simulates on the screen how yellow (RGB (255, 255, 0)) will look on a monochrome output device:
CBrush brush (RGB (227, 227, 227));
I got the value 227 by plugging the color components 255, 255, and 0 into the color conversion formula.
To see a simple demonstration of black-and-white print previewing, replace the lines
CPen pen (PS_SOLID, 50, RGB (0, 0, 255));
CBrush brush (RGB (255, 255, 0));
in EZPrint's CPrintView::OnDraw function with these:
BOOL bMono = (pDC->GetDeviceCaps (NUMCOLORS) == 2) &&
(pDC->m_hDC != pDC->m_hAttribDC); // True only for preview mode.
CPen pen (PS_SOLID, 50, bMono ? RGB (28, 28, 28) : RGB (0, 0, 255));
CBrush brush (bMono ? RGB (227, 227, 227) : RGB (255, 255, 0));
Print previews will now be rendered in shades of gray when the default printer is a black-and-white model. Comparing m_hDC to m_hAttribDC is a sneaky way to detect print preview mode when CPrintInfo information isn't available.