Click here to Skip to main content
15,896,537 members
Articles / Desktop Programming / MFC

MultiPage PrintPreview Enhancements for MFC Doc/View Applications

Rate me:
Please Sign up or sign in to vote.
4.73/5 (13 votes)
27 Apr 2002CPOL3 min read 166.4K   5.2K   34  
How to improve the standard MFC print preview options to allow preview from 1 to 9 pages at a time
// MultiPagePreview.cpp 

#include "stdafx.h"
#include "MultiPagePreview.h"
#include "MultiPagePrintPreview.h"
#include "resource.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

IMPLEMENT_DYNCREATE(CMultiPagePreviewView, CPreviewView)

CMultiPagePreviewView::CMultiPagePreviewView()
{
	// replace the PAGE_INFO array with our one to make sure its large enough
	m_pPageInfo = m_pageInfoArray2;
	m_Across = 2 ;			// default number of pages across the screen
	m_Down = 1 ;			// default number of pages down the screen
	m_nPages = 2 ;
}

CMultiPagePreviewView::~CMultiPagePreviewView()
{
}

BEGIN_MESSAGE_MAP(CMultiPagePreviewView, CPreviewView)
	//{{AFX_MSG_MAP(CMultiPagePreviewView)
	ON_COMMAND(AFX_ID_PREVIEW_NUMPAGE, OnNumPageChange)
	ON_COMMAND(AFX_ID_PREVIEW_ZOOMIN, OnZoomIn)
	ON_COMMAND(AFX_ID_PREVIEW_ZOOMOUT, OnZoomOut)
	ON_UPDATE_COMMAND_UI(AFX_ID_PREVIEW_NUMPAGE, OnUpdateNumPageChange)
	ON_UPDATE_COMMAND_UI(AFX_ID_PREVIEW_ZOOMIN, OnUpdateZoomIn)
	ON_UPDATE_COMMAND_UI(AFX_ID_PREVIEW_ZOOMOUT, OnUpdateZoomOut)
	ON_WM_CREATE()
	ON_WM_LBUTTONDOWN()
	ON_WM_SETCURSOR()
	//}}AFX_MSG_MAP
	ON_WM_VSCROLL()
	ON_COMMAND(ID_PREVIEW_PAGES, OnPreviewPages)
	ON_COMMAND(IDC_LANDSCAPE, OnLandscape)
END_MESSAGE_MAP()

BOOL CMultiPagePreviewView::PreCreateWindow(CREATESTRUCT& cs)
{
	if (cs.lpszClass == NULL)
		cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW|CS_BYTEALIGNCLIENT);
	return CView::PreCreateWindow(cs);
}

#ifdef _DEBUG
void CMultiPagePreviewView::AssertValid() const
{
	// Bug nearby Article ID: Q192853 
	//	CPreviewView::AssertValid();
}

void CMultiPagePreviewView::Dump(CDumpContext& dc) const
{
	TRACE("Dump\n") ;
	CPreviewView::Dump(dc);
}
#endif //_DEBUG

BOOL CMultiPagePreviewView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	if (nHitTest != HTCLIENT)
		return CScrollView::OnSetCursor(pWnd, nHitTest, message);
	
	CPoint point;
	::GetCursorPos(&point);
	ScreenToClient(&point);     // client coordinates of mouse position
	
	UINT nPage;
	if (m_nZoomState != ZOOM_IN_400 && 
		FindPageRect(point, nPage))
	{                       // On a page and not zoomed all the way in
		if (m_hMagnifyCursor == NULL)
		{
			HINSTANCE hInst = AfxFindResourceHandle(
				MAKEINTRESOURCE(AFX_IDC_MAGNIFY), RT_GROUP_CURSOR);
			m_hMagnifyCursor = ::LoadCursor(hInst,
				MAKEINTRESOURCE(AFX_IDC_MAGNIFY));
		}
		::SetCursor(m_hMagnifyCursor);
	}
	else
	{
		::SetCursor(::LoadCursor(NULL, IDC_ARROW));
	}
	return 0;
}

void CMultiPagePreviewView::OnLButtonDown(UINT, CPoint point)
{
	UINT nPage;
	if (!FindPageRect(point, nPage))
		{
		return;                         // Didn't click on a page
		}
	
	// Set new zoom state
	SetZoomState((m_nZoomState == ZOOM_IN_400) ? ZOOM_OUT : m_nZoomState + 1, nPage, point);
	if (m_nZoomState == ZOOM_OUT)
		{
		// make sure the current page does not cause blank pages to be shown
		if (m_nCurrentPage >= m_pPreviewInfo->GetMaxPage() - m_nPages)
			m_nCurrentPage = m_pPreviewInfo->GetMaxPage() - m_nPages + 1;
		if (m_nCurrentPage < 0)
			m_nCurrentPage = 0 ;
		}
}

void CMultiPagePreviewView::SetZoomState(UINT nNewState, UINT nPage, CPoint point)
{
	if (m_nZoomState != nNewState)
	{
		m_nZoomState = nNewState;
		DoZoom(nPage, point);
	}
}

// Actual zoom code.
void CMultiPagePreviewView::DoZoom(UINT nPage, CPoint point)
{
	if (m_nZoomState == ZOOM_OUT)
	{
		// taking over scroll bars
		m_nPages = m_nZoomOutPages;
		ShowScrollBar(SB_HORZ, FALSE);      // hide the horizontal bar
		
		BOOL bShowBar = m_pPreviewInfo->GetMaxPage() < 0x8000 &&
			m_pPreviewInfo->GetMaxPage() -
			m_pPreviewInfo->GetMinPage() <= 32767U;
		
		ShowScrollBar(SB_VERT, bShowBar);       // Show the vertical bar
		
		if (bShowBar)
		{
			SCROLLINFO info;
			info.fMask = SIF_PAGE | SIF_RANGE;
			info.nMin = m_pPreviewInfo->GetMinPage();
			info.nMax = m_pPreviewInfo->GetMaxPage();
			info.nPage = 1;
			if (!SetScrollInfo(SB_VERT, &info, FALSE))
				SetScrollRange(SB_VERT, info.nMin, info.nMax, FALSE);
		}
		
		SetCurrentPage(m_nCurrentPage, TRUE);
		SetupScrollbar() ;
	}
	else
	{
		m_nPages = 1;       // only one page in zoomed states
		m_pPageInfo[0].sizeZoomOutRatio = m_pPageInfo[nPage].sizeZoomOutRatio;
		m_pPageInfo[0].sizeUnscaled = m_pPageInfo[nPage].sizeUnscaled;
		
		// Sets the printer page
		SetCurrentPage(m_nCurrentPage + nPage, FALSE);
		
		SetScaledSize(0);
		
		CSize* pRatio = &m_pPageInfo[nPage].sizeScaleRatio;
		
		// convert Hit Point from screen 1:1
		point.x = MulDiv(point.x, pRatio->cx, pRatio->cy);
		point.y = MulDiv(point.y, pRatio->cx, pRatio->cy);
		
		// Adjust point for page position
		point += (CSize)m_pPageInfo[0].rectScreen.TopLeft();
		
		// Scroll to center
		CenterOnPoint(point);
	}
}

void CMultiPagePreviewView::PositionPage(UINT nPage)
{
	CSize windowSize = CalcPageDisplaySize();
	
	VERIFY(m_dcPrint.Escape(GETPHYSPAGESIZE, 0, NULL, (LPVOID)&m_pPageInfo[nPage].sizeUnscaled));
	
	CSize* pSize = &m_pPageInfo[nPage].sizeUnscaled;
	
	// Convert page size to screen coordinates
	pSize->cx = MulDiv(pSize->cx, afxData.cxPixelsPerInch, m_sizePrinterPPI.cx);
	pSize->cy = MulDiv(pSize->cy, afxData.cyPixelsPerInch, m_sizePrinterPPI.cy);
	
	m_pPageInfo[nPage].sizeZoomOutRatio = CalcScaleRatio(windowSize, *pSize);
	
	SetScaledSize(nPage);
}

#define PREVIEW_MARGIN  8
#define PREVIEW_PAGEGAP 8

void CMultiPagePreviewView::SetScaledSize(UINT nPage)
{
	CSize* pSize = &m_pPageInfo[nPage].sizeUnscaled;
	CSize* pRatio = &m_pPageInfo[nPage].sizeScaleRatio;
	CSize* pZoomOutRatio = &m_pPageInfo[nPage].sizeZoomOutRatio;
	CSize windowSize = CalcPageDisplaySize();
	BOOL bPaperLarger = pZoomOutRatio->cx < pZoomOutRatio->cy;
	// whether the paper is larger than the screen, or vice versa
	
	switch (m_nZoomState)
	{
		case ZOOM_OUT:
			*pRatio = *pZoomOutRatio;
			break;
			
		case ZOOM_MIDDLE:
			// the middle zoom state is a ratio between cx/cy and
			// 1/1 (or cy/cy).  It is, therefore:
			//
			// (cx + cy)/2
			// -----------
			//     cy
			//
			// if the paper is larger than the screen, or
			//
			// (3*cx - cy)/2
			// -------------
			//      cy
			//
			// if the paper is smaller than the screen.
			if (bPaperLarger)
			{
				pRatio->cy = pZoomOutRatio->cy;
				pRatio->cx = (pZoomOutRatio->cx + pRatio->cy) / 2;
			}
			else
			{
				pRatio->cy = pZoomOutRatio->cy;
				pRatio->cx = (3*pZoomOutRatio->cx - pRatio->cy) / 2;
			}
			break;
			
		case ZOOM_IN:
			if (bPaperLarger)
			{
				pRatio->cx = pRatio->cy = 1;
			}
			else
			{
				// if the paper is smaller than the screen space we're displaying
				// it in, then using a ratio of 1/1 will result in a smaller image
				// on the screen, not a larger one. To get a larger image in this
				// case we double the zoom out ratio.
				pRatio->cy = pZoomOutRatio->cy;
				pRatio->cx = 2*pZoomOutRatio->cx - pZoomOutRatio->cy;
			}
					
			break;
		case ZOOM_IN_150:
			pRatio->cx = 15;
			pRatio->cy = 10;
			break;
			
		case ZOOM_IN_200:
			pRatio->cx = 25;
			pRatio->cy = 10;
			break;
			
		case ZOOM_IN_400:
			pRatio->cx = 4;
			pRatio->cy = 1;
			break;
			
		default:
			ASSERT(FALSE);
	}
	
	// Convert to scaled size
	CSize scaledSize;
	scaledSize.cx = MulDiv(pSize->cx, pRatio->cx, pRatio->cy);
	scaledSize.cy = MulDiv(pSize->cy, pRatio->cx, pRatio->cy);
	
	CRect* pRect = &m_pPageInfo[nPage].rectScreen;
	pRect->SetRect(PREVIEW_MARGIN, PREVIEW_MARGIN,
		scaledSize.cx + PREVIEW_MARGIN + 3,
		scaledSize.cy + PREVIEW_MARGIN + 3);
	
	if (m_nZoomState == ZOOM_OUT)
	{
		pRect->OffsetRect((windowSize.cx - pRect->Size().cx) / 2 - 1, (windowSize.cy - pRect->Size().cy) / 2 - 1);
		
		// we need to offste the page multiple times
		int local = nPage % m_Across ;
		while (local-- >= 1)
			{
			pRect->OffsetRect(m_PageOffset.x, 0);
			}
		local = nPage / m_Across ;
		while (local-- > 0)
			{
			pRect->OffsetRect(0, m_PageOffset.y);
			}
	}
	else
	{
		// set up scroll size
		
		SetScrollSizes(MM_TEXT, pRect->Size() +
			CSize(PREVIEW_MARGIN * 2, PREVIEW_MARGIN * 2), windowSize);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CMultiPagePreviewView

void CMultiPagePreviewView::OnUpdateNumPageChange(CCmdUI* pCmdUI)
{
	// button has been removed from toolbar
	//UINT nPages = m_nZoomState == ZOOM_OUT ? m_nPages : m_nZoomOutPages;
	//pCmdUI->Enable(m_nZoomState == ZOOM_OUT && m_nMaxPages != 1 && (m_pPreviewInfo->GetMaxPage() > 1 || m_nPages > 1));
	//	CPreviewView::OnUpdateNumPageChange(pCmdUI);
}

void CMultiPagePreviewView::OnUpdateZoomIn(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_nZoomState != ZOOM_IN_400);
}

void CMultiPagePreviewView::OnUpdateZoomOut(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_nZoomState != ZOOM_OUT);
}

void CMultiPagePreviewView::OnNumPageChange() 
{
	// doesn't do anything any more
	// button has been removed from toolbar
}

void CMultiPagePreviewView::OnZoomIn() 
{
	if (m_nZoomState != ZOOM_IN_400)
		SetZoomState(m_nZoomState + 1, 0, CPoint(0, 0));
}

void CMultiPagePreviewView::OnZoomOut() 
{
	if (m_nZoomState != ZOOM_OUT)
		SetZoomState(m_nZoomState - 1, 0, CPoint(0, 0));
}

int CMultiPagePreviewView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CPreviewView::OnCreate(lpCreateStruct) == -1)
		return -1;

	m_pToolBar->EnableToolTips( TRUE );

	// make the buttons on the dialog bar bitmap buttons

	m_print.AutoLoad( AFX_ID_PREVIEW_PRINT, m_pToolBar, IDB_PREV_PRINT );
	m_next.AutoLoad( AFX_ID_PREVIEW_NEXT, m_pToolBar, IDB_PREV_NEXT );
	m_previous.AutoLoad( AFX_ID_PREVIEW_PREV, m_pToolBar, IDB_PREV_PREVIOUS );
	m_zoomIn.AutoLoad( AFX_ID_PREVIEW_ZOOMIN, m_pToolBar, IDB_PREV_ZOOMIN );
	m_zoomOut.AutoLoad( AFX_ID_PREVIEW_ZOOMOUT, m_pToolBar, IDB_PREV_ZOOMOUT );
	m_pages.AutoLoad(ID_PREVIEW_PAGES, m_pToolBar, IDB_PAGES );
	
	return 0;
}


CSize CMultiPagePreviewView::CalcPageDisplaySize()
	// calculate the current page size
	// MFC used to set 'm_nSecondPageOffset' to start of second page
	// as we have multiple pages we use m_PageOffset which holds the
	// diferences across and down the pages
	// return size of current page less margins
{
	// just checking...
	ASSERT(m_Down >= 1) ;
	ASSERT(m_Across >= 1) ;
	ASSERT(m_nPages >= 1) ;

	CSize windowSize, scrollSize;
	GetTrueClientSize(windowSize, scrollSize);

	// subtract out vertical scrollbar if zoomed out and page range is known
	// and there is more than one page.
	if (m_nZoomState == ZOOM_OUT && (m_pPreviewInfo->GetMaxPage() != 0xffff) && (m_pPreviewInfo->GetMaxPage() - m_pPreviewInfo->GetMinPage() != 0))
		windowSize.cx -= scrollSize.cx;
	m_PageOffset.y = 0 ;
	if (m_Down > 1)
		{
		// we need to make room for more pages under the first
		windowSize.cy = (windowSize.cy - (PREVIEW_MARGIN * (m_Down - 1))) / m_Down ;
		m_PageOffset.y = windowSize.cy + PREVIEW_MARGIN ;
		}
	else
		{
		// its a single page down, it uses all the area previouslyy calculated
		}

	if (m_Across <= 2)
		m_PageOffset.x = (windowSize.cx - PREVIEW_MARGIN) / 2;
	else
		m_PageOffset.x = (windowSize.cx - PREVIEW_MARGIN) / m_Across ;

	// make sure all pages across fit in the screen area
	windowSize.cx = (windowSize.cx - ((m_Across + 1) * PREVIEW_MARGIN)) / m_Across ;
	//windowSize.cx = (m_nPages == 2) ? (windowSize.cx - 3*PREVIEW_MARGIN) / 2 :
	//								windowSize.cx - 2*PREVIEW_MARGIN;

	windowSize.cy -= 2*PREVIEW_MARGIN;
	return windowSize;
}

void CMultiPagePreviewView::OnPreviewPages()
{
	CPoint		point ;
	CRect		rect ;

	CWnd *pWnd = m_pToolBar->GetDlgItem(ID_PREVIEW_PAGES) ;
	if (pWnd != NULL)
		{
		// place the menu just below the button
		pWnd->GetWindowRect(&rect) ;
		point = CPoint(rect.left, rect.bottom) ;
		}
	else
		::GetCursorPos(&point) ;			// failed to get window, use the mouse position
	CMenu	menu ;
	CMenu	*pSub ;

	// popup a menu to get the number of pages to display
	VERIFY(menu.LoadMenu(IDR_PREVIEW_PAGES)) ;
	pSub = menu.GetSubMenu(0) ;

	int command = pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, point.x, point.y, this) ;

	switch (command)
		{
		case ID_PAGES_1PAGE :
			m_Across = 1 ;
			m_Down = 1 ;
			m_nPages = 1 ;
			break ;
		case ID_PAGES_2PAGES :
			m_Across = 2 ;
			m_Down = 1 ;
			m_nPages = 2 ;
			break ;
		case ID_PAGES_3PAGES :
			m_Across = 3 ;
			m_Down = 1 ;
			m_nPages = 3 ;
			break ;
		case ID_PAGES_4PAGES :
			m_Across = 2 ;
			m_Down = 2 ;
			m_nPages = 4 ;
			break ;
		case ID_PAGES_6PAGES :
			m_Across = 3 ;
			m_Down = 2 ;
			m_nPages = 6 ;
			break ;
		case ID_PAGES_9PAGES :
			m_Across = 3 ;
			m_Down = 3 ;
			m_nPages = 9 ;
			break ;
		default :
			return ;
		}
	AfxGetApp()->m_nNumPreviewPages = m_nPages;
	m_nZoomOutPages = m_nPages;
	m_nMaxPages = m_nPages ;
	if (m_nZoomState == ZOOM_OUT)
		{
		// make sure the current page does not cause blank pages to be shown
		if (m_nCurrentPage >= m_pPreviewInfo->GetMaxPage() - m_nPages)
			m_nCurrentPage = m_pPreviewInfo->GetMaxPage() - m_nPages + 1;
		if (m_nCurrentPage < 0)
			m_nCurrentPage = 0 ;
		}
	// Just do this to set the status correctly and invalidate
	SetCurrentPage(m_nCurrentPage, TRUE);
	SetupScrollbar() ;
}

void CMultiPagePreviewView::SetupScrollbar()
{
	// this procedure makes sure that the scroll bar does not allow us to scroll the window
	// such that we end up displaying blank pages
	// correctly range the scroll bars
	if (m_pPreviewInfo->GetMaxPage() < 0x8000 && m_pPreviewInfo->GetMaxPage() - m_pPreviewInfo->GetMinPage() <= 32767U)
		{
		SCROLLINFO info;
		info.fMask = SIF_PAGE|SIF_RANGE;
		info.nMin = m_pPreviewInfo->GetMinPage();
		info.nMax = m_pPreviewInfo->GetMaxPage() - (m_nPages - 1) ;
		info.nPage = 1;
		if (!SetScrollInfo(SB_VERT, &info, FALSE))
			SetScrollRange(SB_VERT, info.nMin, info.nMax, FALSE);
		}
	else
		ShowScrollBar(SB_VERT, FALSE);      // if no range specified, or too
}

void CMultiPagePreviewView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (m_nZoomState != ZOOM_OUT)
	{
		CScrollView::OnVScroll(nSBCode, nPos, pScrollBar);
		return;
	}
	SetupScrollbar() ;
	switch (nSBCode)
	{
	case SB_BOTTOM:
		SetCurrentPage(m_pPreviewInfo->GetMaxPage(), TRUE);
		break;

	case SB_TOP:
		SetCurrentPage(m_pPreviewInfo->GetMinPage(), TRUE);
		break;

	case SB_PAGEDOWN:
		SetCurrentPage(m_nCurrentPage +
			(m_pPreviewInfo->GetMaxPage() - m_pPreviewInfo->GetMinPage() + 9) / 10, TRUE);
		break;

	case SB_PAGEUP:
		SetCurrentPage(m_nCurrentPage -
			(m_pPreviewInfo->GetMaxPage() - m_pPreviewInfo->GetMinPage() + 9) / 10, TRUE);
		break;

	case SB_LINEDOWN:
		if (m_nCurrentPage <= m_pPreviewInfo->GetMaxPage() - m_nPages)
			SetCurrentPage(m_nCurrentPage + 1, TRUE);
		break;

	case SB_LINEUP:
		if (m_nCurrentPage > 0)
			SetCurrentPage(m_nCurrentPage - 1, TRUE);
		break;

	case SB_THUMBPOSITION:
		SetCurrentPage(nPos, TRUE);
		break;
	}
}

void CMultiPagePreviewView::OnLandscape()
{
	// switch the preview DC between lanscape and portraite mode.
	// we also need to setup the app printer defaults to switch between portrait and
	// landscape, so any print action will use the correct settings

	// we need to delete the current printer DC and setup a new one after changing the print mode bewteen landscape / portrait
	CWnd	*pWnd = m_pToolBar->GetDlgItem(IDC_LANDSCAPE) ;
	ASSERT(pWnd) ;										// the item should exist in the toolbar. If this assert check your resources
	CButton *pButton = static_cast<CButton*>(pWnd) ;	// get a pointer to the button
	ASSERT(pButton) ;									// not a button object? If it asserts, then wrong control type in toolbar resource
	int		state = pButton->GetCheck() ;				// portrait or landscape mode?

	// get the current CPrintInfo object
	CPrintInfo *pInfo = m_pPreviewInfo ;
	m_pPreviewInfo = NULL ;
	// switch the print mode
	CWinApp *pApp = AfxGetApp() ;
	CMultiPagePrintPreviewApp *pOurApp = static_cast<CMultiPagePrintPreviewApp*>(pApp) ;
	ASSERT(pOurApp) ;
	pOurApp->SetPrintOrientation(state ? DMORIENT_LANDSCAPE : DMORIENT_PORTRAIT) ;
	m_dcPrint.Detach();         // print DC is deleted by CPrintInfo destructor
	delete pInfo ;
	SetPrintView(m_pPrintView) ;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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