Printing by MFC WITHOUT Document/View architecture






4.60/5 (3 votes)
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 proper app header file #include "MainFrm.h" // include proper mainframe header file #include "Print.h" #include <WinSpool.h> BOOL CALLBACK _AbortProc(HDC, int) { // TODO: Replace following line with your abort check function // I imagine this function is defined in the app class 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; // don't prompt the user if we're doing print preview, printing directly, // or printing via IPrint and have been instructed not to ask 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 no printer set then, get default printer DC and create DC without calling // print dialog. if (!pApp->GetPrinterDeviceDefaults(&pInfo->m_pPD->m_pd)) { // bring up dialog to alert the user they need to install a printer. 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) { // call CreatePrinterDC if DC was not created by above if (pInfo->m_pPD->CreatePrinterDC() == NULL) return FALSE; } } // set up From and To page range from Min and Max pInfo->m_pPD->m_pd.nFromPage = (WORD)pInfo->GetMinPage(); pInfo->m_pPD->m_pd.nToPage = (WORD)pInfo->GetMaxPage(); } else { // otherwise, bring up the print dialog and allow user to change things // preset From-To range same as Min-Max range 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; // do not print } 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; // get default print info CPrintInfo printInfo; ASSERT(printInfo.m_pPD != NULL); // must be set 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)) { // hDC must be set (did you remember to call DoPreparePrinting?) ASSERT(printInfo.m_pPD->m_pd.hDC != NULL); // gather file to print to if print-to-file selected CString strOutput; if (printInfo.m_pPD->m_pd.Flags & PD_PRINTTOFILE && !printInfo.m_bDocObject) { // construct CFileDialog for browsing 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; // set output device to resulting path name strOutput = dlg.GetPathName(); } // set up document info and start the document printing process 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); } // setup the printing DC CDC dcPrint; if (!printInfo.m_bDocObject) { dcPrint.Attach(printInfo.m_pPD->m_pd.hDC); // attach printer dc 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); // disable main window while printing & init printing status dialog // Store the Handle of the Window in a temp so that it can be enabled // once the printing is finished CWnd* hwndTemp = AfxGetMainWnd(); hwndTemp->EnableWindow(FALSE); //CPrintingDialog dlgPrintStatus(this); CString strTemp; // start document printing process if (!printInfo.m_bDocObject) { printInfo.m_nJobNumber = dcPrint.StartDoc(&docInfo); if (printInfo.m_nJobNumber == SP_ERROR) { // enable main window before proceeding hwndTemp->EnableWindow(TRUE); // cleanup and show error message pProvider->OnEndPrinting(&dcPrint, &printInfo); dcPrint.Detach(); // will be cleaned up by CPrintInfo destructor AfxMessageBox(IDS_FAILED_TO_START_PRINT); return; } } // Guarantee values are in the valid range 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)); // If it's a doc object, we don't loop page-by-page // because doc objects don't support that kind of levity. BOOL bError = FALSE; if (printInfo.m_bDocObject) { pProvider->OnPrepareDC(&dcPrint, &printInfo); pProvider->OnPrint(&dcPrint, &printInfo); } else { // TODO: Replace following line with your operation start function theApp.ProgressiveOperationBegan(TRUE, IDS_MESSAGE_PRINTING, nEndPage - nStartPage); // begin page printing loop for (printInfo.m_nCurPage = nStartPage; printInfo.m_nCurPage != nEndPage; printInfo.m_nCurPage += nStep) { pProvider->OnPrepareDC(&dcPrint, &printInfo); // check for end of print if (!printInfo.m_bContinuePrinting) break; // write current page TCHAR szBuf[80]; ATL_CRT_ERRORCHECK_SPRINTF(_sntprintf_s(szBuf, _countof(szBuf), _countof(szBuf) - 1, strTemp, printInfo.m_nCurPage)); // TODO: Replace following line with your operation report function theApp.SetProgressInfo(szBuf, printInfo.m_nCurPage - nStartPage); // set up drawing rect to entire page (in logical coordinates) printInfo.m_rectDraw.SetRect(0, 0, dcPrint.GetDeviceCaps(HORZRES), dcPrint.GetDeviceCaps(VERTRES)); dcPrint.DPtoLP(&printInfo.m_rectDraw); // attempt to start the current page if (dcPrint.StartPage() < 0) { bError = TRUE; break; } // must call OnPrepareDC on newer versions of Windows because // StartPage now resets the device attributes. pProvider->OnPrepareDC(&dcPrint, &printInfo); ASSERT(printInfo.m_bContinuePrinting); // page successfully started, so now render the page pProvider->OnPrint(&dcPrint, &printInfo); if ((nStep > 0) && // pages are printed in ascending order (nEndPage > printInfo.GetMaxPage() + nStep)) // out off pages { // OnPrint may have set the last page // because the end of the document was reached. // The loop must not continue with the next iteration. nEndPage = printInfo.GetMaxPage() + nStep; } // If the user restarts the job when it's spooling, all // subsequent calls to EndPage returns < 0. The first time // GetLastError returns ERROR_PRINT_CANCELLED 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 job status is restart, just continue if (!bRet || !(dwJobStatus & JOB_STATUS_RESTART)) { bError = TRUE; break; } } if (!_AbortProc(dcPrint.m_hDC, 0)) { bError = TRUE; break; } } // TODO: Replace following line with your operation finish function theApp.OperationFinished(); } // cleanup document printing process if (!printInfo.m_bDocObject) { if (!bError) dcPrint.EndDoc(); else dcPrint.AbortDoc(); } hwndTemp->EnableWindow(); // enable main window pProvider->OnEndPrinting(&dcPrint, &printInfo); // clean up after printing dcPrint.Detach(); // will be cleaned up by CPrintInfo destructor*/ } }
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