Introduction
Hello
MFC support printing only in Doc/View architecture model. But if you don't like this model (Like me),
and you want printing, you encounter a problem. I in this article explain How to easily solve this problem.
The code originaly is by MFC but I modify it to use in our purpose.
Using the code
First step, we must declare an abstract class and name it to "CPrintContentBase". This class has only pure members.
class CPrintContentBase : public CObject
{
public:
virtual CString GetTitle() const = 0;
virtual void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo = NULL) = 0;
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo) = 0;
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo) = 0;
virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo) = 0;
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo) = 0;
};
Now, add following codes to the mainframe source file, (Recommended using a seperated file).
Before add this method to the mainframe class.
void DoPrint(CPrintContentBase* pProvider);
And create a header (i name it "print.h") file and add following code to it.
#pragma once
extern BOOL DoPreparePrinting(CPrintInfo* pInfo);
The source of print.cpp file
#include "stdafx.h"
#include "app.h"
#include "MainFrm.h"
#include "Print.h"
#include <WinSpool.h>
BOOL CALLBACK _AbortProc(HDC, int)
{
return !theApp.UserAborted();
}
BOOL DoPreparePrinting(CPrintInfo* pInfo)
{
ASSERT(pInfo != NULL);
ASSERT(pInfo->m_pPD != NULL);
if (pInfo->m_pPD->m_pd.nMinPage > pInfo->m_pPD->m_pd.nMaxPage)
pInfo->m_pPD->m_pd.nMaxPage = pInfo->m_pPD->m_pd.nMinPage;
CWinApp* pApp = AfxGetApp();
if (pInfo->m_bPreview || pInfo->m_bDirect ||
(pInfo->m_bDocObject && !(pInfo->m_dwFlags & PRINTFLAG_PROMPTUSER)))
{
if (pInfo->m_pPD->m_pd.hDC == NULL)
{
if (!pApp->GetPrinterDeviceDefaults(&pInfo->m_pPD->m_pd))
{
if (!pInfo->m_bDocObject || (pInfo->m_dwFlags & PRINTFLAG_MAYBOTHERUSER))
if (pApp->DoPrintDialog(pInfo->m_pPD) != IDOK)
return FALSE;
}
if (pInfo->m_pPD->m_pd.hDC == NULL)
{
if (pInfo->m_pPD->CreatePrinterDC() == NULL)
return FALSE;
}
}
pInfo->m_pPD->m_pd.nFromPage = (WORD)pInfo->GetMinPage();
pInfo->m_pPD->m_pd.nToPage = (WORD)pInfo->GetMaxPage();
}
else
{
pInfo->m_pPD->m_pd.nFromPage = (WORD)pInfo->GetMinPage();
pInfo->m_pPD->m_pd.nToPage = (WORD)pInfo->GetMaxPage();
if (pApp->DoPrintDialog(pInfo->m_pPD) != IDOK)
return FALSE;
}
ASSERT(pInfo->m_pPD != NULL);
ASSERT(pInfo->m_pPD->m_pd.hDC != NULL);
if (pInfo->m_pPD->m_pd.hDC == NULL)
return FALSE;
pInfo->m_nNumPreviewPages = pApp->m_nNumPreviewPages;
ENSURE(pInfo->m_strPageDesc.LoadString(IDS_PREVIEWPAGEDESC));
return TRUE;
}
void CMainFrame::DoPrint(CPrintContentBase* pProvider)
{
ASSERT(pProvider != NULL);
if (pProvider == NULL)
return;
CPrintInfo printInfo;
ASSERT(printInfo.m_pPD != NULL);
if (LOWORD(AfxGetMainWnd()->GetCurrentMessage()->wParam) == ID_FILE_PRINT_DIRECT)
{
CCommandLineInfo* pCmdInfo = AfxGetApp()->m_pCmdInfo;
if (pCmdInfo != NULL)
{
if (pCmdInfo->m_nShellCommand == CCommandLineInfo::FilePrintTo)
{
printInfo.m_pPD->m_pd.hDC = ::CreateDC(pCmdInfo->m_strDriverName,
pCmdInfo->m_strPrinterName, pCmdInfo->m_strPortName, NULL);
if (printInfo.m_pPD->m_pd.hDC == NULL)
{
AfxMessageBox(IDS_FAILED_TO_START_PRINT);
return;
}
}
}
printInfo.m_bDirect = TRUE;
}
if (pProvider->OnPreparePrinting(&printInfo))
{
ASSERT(printInfo.m_pPD->m_pd.hDC != NULL);
CString strOutput;
if (printInfo.m_pPD->m_pd.Flags & PD_PRINTTOFILE && !printInfo.m_bDocObject)
{
CString strDef(MAKEINTRESOURCE(IDS_PRINTDEFAULTEXT));
CString strPrintDef(MAKEINTRESOURCE(IDS_PRINTDEFAULT));
CString strFilter(MAKEINTRESOURCE(IDS_PRINTFILTER));
CString strCaption(MAKEINTRESOURCE(IDS_PRINTCAPTION));
CFileDialog dlg(FALSE, strDef, strPrintDef,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, strFilter, NULL, 0);
dlg.m_ofn.lpstrTitle = strCaption;
if (dlg.DoModal() != IDOK)
return;
strOutput = dlg.GetPathName();
}
CString strTitle;
strTitle = pProvider->GetTitle();
DOCINFO docInfo;
memset(&docInfo, 0, sizeof(DOCINFO));
docInfo.cbSize = sizeof(DOCINFO);
docInfo.lpszDocName = strTitle;
CString strPortName;
if (strOutput.IsEmpty())
{
docInfo.lpszOutput = NULL;
strPortName = printInfo.m_pPD->GetPortName();
}
else
{
docInfo.lpszOutput = strOutput;
::GetFileTitle(strOutput, strPortName.GetBuffer(_MAX_PATH), _MAX_PATH);
}
CDC dcPrint;
if (!printInfo.m_bDocObject)
{
dcPrint.Attach(printInfo.m_pPD->m_pd.hDC);
dcPrint.m_bPrinting = TRUE;
}
printInfo.m_rectDraw.SetRect(0, 0,
dcPrint.GetDeviceCaps(HORZRES),
dcPrint.GetDeviceCaps(VERTRES));
dcPrint.DPtoLP(&printInfo.m_rectDraw);
pProvider->OnBeginPrinting(&dcPrint, &printInfo);
if (!printInfo.m_bDocObject)
dcPrint.SetAbortProc(_AbortProc);
CWnd* hwndTemp = AfxGetMainWnd();
hwndTemp->EnableWindow(FALSE);
CString strTemp;
if (!printInfo.m_bDocObject)
{
printInfo.m_nJobNumber = dcPrint.StartDoc(&docInfo);
if (printInfo.m_nJobNumber == SP_ERROR)
{
hwndTemp->EnableWindow(TRUE);
pProvider->OnEndPrinting(&dcPrint, &printInfo);
dcPrint.Detach();
AfxMessageBox(IDS_FAILED_TO_START_PRINT);
return;
}
}
UINT nEndPage = printInfo.GetToPage();
UINT nStartPage = printInfo.GetFromPage();
if (nEndPage < printInfo.GetMinPage())
nEndPage = printInfo.GetMinPage();
if (nEndPage > printInfo.GetMaxPage())
nEndPage = printInfo.GetMaxPage();
if (nStartPage < printInfo.GetMinPage())
nStartPage = printInfo.GetMinPage();
if (nStartPage > printInfo.GetMaxPage())
nStartPage = printInfo.GetMaxPage();
int nStep = (nEndPage >= nStartPage) ? 1 : -1;
nEndPage = (nEndPage == 0xffff) ? 0xffff : nEndPage + nStep;
VERIFY(strTemp.LoadString(IDS_PRINTPAGENUM));
BOOL bError = FALSE;
if (printInfo.m_bDocObject)
{
pProvider->OnPrepareDC(&dcPrint, &printInfo);
pProvider->OnPrint(&dcPrint, &printInfo);
}
else
{
theApp.ProgressiveOperationBegan(TRUE, IDS_MESSAGE_PRINTING, nEndPage - nStartPage);
for (printInfo.m_nCurPage = nStartPage;
printInfo.m_nCurPage != nEndPage; printInfo.m_nCurPage += nStep)
{
pProvider->OnPrepareDC(&dcPrint, &printInfo);
if (!printInfo.m_bContinuePrinting)
break;
TCHAR szBuf[80];
ATL_CRT_ERRORCHECK_SPRINTF(_sntprintf_s(szBuf, _countof(szBuf), _countof(szBuf) - 1, strTemp, printInfo.m_nCurPage));
theApp.SetProgressInfo(szBuf, printInfo.m_nCurPage - nStartPage);
printInfo.m_rectDraw.SetRect(0, 0,
dcPrint.GetDeviceCaps(HORZRES),
dcPrint.GetDeviceCaps(VERTRES));
dcPrint.DPtoLP(&printInfo.m_rectDraw);
if (dcPrint.StartPage() < 0)
{
bError = TRUE;
break;
}
pProvider->OnPrepareDC(&dcPrint, &printInfo);
ASSERT(printInfo.m_bContinuePrinting);
pProvider->OnPrint(&dcPrint, &printInfo);
if ((nStep > 0) &&
(nEndPage > printInfo.GetMaxPage() + nStep))
{
nEndPage = printInfo.GetMaxPage() + nStep;
}
if (dcPrint.EndPage() < 0 && (GetLastError() != ERROR_SUCCESS))
{
HANDLE hPrinter;
if (!OpenPrinter(LPTSTR(printInfo.m_pPD->GetDeviceName().GetBuffer()), &hPrinter, NULL))
{
bError = TRUE;
break;
}
DWORD cBytesNeeded;
if (!GetJob(hPrinter, printInfo.m_nJobNumber, 1, NULL, 0, &cBytesNeeded))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
bError = TRUE;
break;
}
}
JOB_INFO_1 *pJobInfo;
if ((pJobInfo = (JOB_INFO_1 *)malloc(cBytesNeeded)) == NULL)
{
bError = TRUE;
break;
}
DWORD cBytesUsed;
BOOL bRet = GetJob(hPrinter, printInfo.m_nJobNumber, 1, LPBYTE(pJobInfo), cBytesNeeded, &cBytesUsed);
DWORD dwJobStatus = pJobInfo->Status;
free(pJobInfo);
pJobInfo = NULL;
if (!bRet || !(dwJobStatus & JOB_STATUS_RESTART))
{
bError = TRUE;
break;
}
}
if (!_AbortProc(dcPrint.m_hDC, 0))
{
bError = TRUE;
break;
}
}
theApp.OperationFinished();
}
if (!printInfo.m_bDocObject)
{
if (!bError)
dcPrint.EndDoc();
else
dcPrint.AbortDoc();
}
hwndTemp->EnableWindow();
pProvider->OnEndPrinting(&dcPrint, &printInfo);
dcPrint.Detach();
}
}
Note: In above code some strings use from string table. Please add following strings with the proper id to string table.
IDS_FAIL_INIT_PRINT "Could not initalize printer."
IDS_FAILED_TO_START_PRINT "Could not start print job."
IDS_PRINTDEFAULTEXT "prn"
IDS_PRINTDEFAULT "Output.prn"
IDS_PRINTFILTER "Printer Files (*.prn)|*.prn|All Files (*.*)|*.*||"
IDS_PRINTCAPTION "Print to File"
IDS_PREVIEWPAGEDESC "Page %u\nPages %u-%u\n"
IDS_PRINTPAGENUM "Page %u"
Example of use
In the mainframe code add a File->Print command handler like this. CxFrame is a MDI child frame window class. You Replace your own code. GetPrintContentProvider() method return an implemention of CPrintContentBase class.
void CMainFrame::OnFilePrint()
{
CxFrame* pActiveFrame = (CxFrame*)MDIGetActive();
if (pActiveFrame == NULL)
return;
CPrintContentBase* pProvider = pActiveFrame->GetPrintContentProvider();
if (pProvider == NULL)
return;
DoPrint(pProvider);
}
An Implemention of CPrintContentBase class:
#include "print.h"
class CSamplePrintProvider : public CPrintContentBase
{
public:
CSamplerPintProvider()
{
}
virtual ~CSamplePrintProvider()
{
}
virtual CString GetTitle() const {
return _T("Sample Title");
}
virtual void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo = NULL)
{
pDC->SetBkMode(TRANSPARENT);
pInfo->m_bContinuePrinting = TRUE;
}
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo)
{
pInfo->SetMinPage(1);
pInfo->SetMaxPage(1);
pInfo->m_pPD->m_pd.Flags |= PD_NOPAGENUMS;
return DoPreparePrinting(pInfo);
}
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)
{
}
virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
pDC->Rectangle(pInfo->m_rectDraw);
}
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo)
{
}
};
Thanks